/** * Translate from an array of indices of the burnable inputs to an array of indices of all inputs * to the gem * * @param unboundArgsToBurn An array of indices relative to the burnable (ie unburned, unbound) * inputs of the gem * @param sourceGem The gem that these are the inputs of * @return An array of indices of inputs relative to ALL inputs of the gem, corresponding to the * contents of unburnedArgsToBurn. */ private static int[] translateBurnCombo(int[] unboundArgsToBurn, Gem sourceGem) { int[] argsToBurn = new int[unboundArgsToBurn.length]; // loop indices: // i - index of the current input // j - counter to keep track of how many unbound and unburned inputs we have passed // k - index of the next element of unboudnArgsToBurn to check for (int i = 0, j = 0, k = 0; i < sourceGem.getNInputs() && k < unboundArgsToBurn.length; i++) { Gem.PartInput currentInput = sourceGem.getInputPart(i); if (currentInput.isConnected() || currentInput.isBurnt()) { // Skip over unburnable inputs continue; } // This is the j'th burnable input // Check to see if this argument should be burned, and if so, add its index relative to the // list of all inputs // to the argsToBurn array if (unboundArgsToBurn[k] == j) { argsToBurn[k] = i; k++; } j++; } return argsToBurn; }
/** * Determine if the current target Gem only has arguments that we can provide editors for. * * @return boolean true if we can provide all the arguments, false if we can't */ private boolean targetHasProvidableArgs() { // Get the target's arguments first List<Gem.PartInput> args = targetDisplayedGem.getTargetArguments(); // If we have no arguments, then we at least have providable args (we can easily // provided none at all!) if (args == null) { return true; } // Check all the argument types for being ones that we can provide int numArgs = args.size(); Gem.PartInput sinkPart; for (int i = 0; i < numArgs; i++) { // Get this part sinkPart = args.get(i); // Can we deal with this type? if (!valueEditorHierarchyManager .getValueEditorManager() .canInputDefaultValue(sinkPart.getType())) { // Oops, can't provide an editor for this type return false; } } // If we haven't found any dodgy ones, then we can provide all of them! return true; }
/** * Display value entry controls for all unbound arguments. Precondition: the target must be set. */ private void displayArgumentControls() { TableTopPanel tableTopPanel = getTableTopPanel(); // Build the list of value entry panels and place them onto the TableTop // Get the list of unbound arguments List<Gem.PartInput> argParts = targetDisplayedGem.getTargetArguments(); // see if it's ok to use the cached argument values boolean canUseCachedArgs = canUseCachedArguments(); // Figure out the types of the argument panels. Note that we must account for cached values int numArgs = argParts.size(); // make copies of the input types. TypeExpr[] inputTypes = new TypeExpr[numArgs]; for (int i = 0; i < numArgs; i++) { // Get this input, and its type Gem.PartInput inputPart = argParts.get(i); inputTypes[i] = inputPart.getType(); } TypeExpr[] specializedInputTypes; if (canUseCachedArgs) { // this is the array of types cached for each argument. If there is no cached type, we just // use the argument type. TypeExpr[] cachedTypes = new TypeExpr[numArgs]; for (int i = 0; i < numArgs; ++i) { // What we do next depends on whether we use cached sink values Gem.PartInput inputPart = argParts.get(i); ValueNode cachedVN = gemCutter.getTableTop().getCachedValue(inputPart); if (cachedVN != null) { cachedTypes[i] = cachedVN.getTypeExpr(); } } try { ModuleTypeInfo contextModuleTypeInfo = gemCutter.getPerspective().getWorkingModuleTypeInfo(); specializedInputTypes = TypeExpr.patternMatchPieces(cachedTypes, inputTypes, contextModuleTypeInfo); } catch (TypeException te) { throw new IllegalStateException("Error reusing cached args."); } } else { specializedInputTypes = TypeExpr.copyTypeExprs(inputTypes); } ValueEditorManager valueEditorManager = gemCutter.getValueEditorManager(); ValueEditorDirector valueEditorDirector = valueEditorManager.getValueEditorDirector(); Map<PartInput, ValueEditor> inputToEditorMap = new LinkedHashMap<PartInput, ValueEditor>(); // Step through args, create a new value input panel for each, with the correct value type for (int i = 0; i < numArgs; i++) { // Get the gem and input final Gem.PartInput inputPart = argParts.get(i); final Gem inputGem = inputPart.getGem(); int argumentNumber = inputPart.getInputNum(); QualifiedName scName = null; if (inputGem instanceof FunctionalAgentGem) { scName = ((FunctionalAgentGem) inputGem).getName(); } // What we do next depends on whether we use cached sink values ValueNode cachedVN; final ValueEditor editor; if (canUseCachedArgs && (cachedVN = gemCutter.getTableTop().getCachedValue(inputPart)) != null) { // use cached sink value to generate the VEP // get a copy of the cached VN but with the new type expr ValueNode newVN = cachedVN.copyValueNode(); // instantiate the VEP with the new VN (which has the old cached value) editor = valueEditorDirector.getRootValueEditor( valueEditorHierarchyManager, newVN, scName, argumentNumber, new GemCutterMetadataRunner(gemCutter, inputGem)); } else { // don't use cached sink value. Generate the default VEP for this sink. TypeExpr inputType = specializedInputTypes[i]; editor = valueEditorDirector.getRootValueEditor( valueEditorHierarchyManager, inputType, scName, argumentNumber, new GemCutterMetadataRunner(gemCutter, inputGem)); } // If this is a value entry panel then set the name of the input for use in tooltips if (editor instanceof ValueEntryPanel) { ((ValueEntryPanel) editor) .setArgumentName(inputPart.getArgumentName().getCompositeName()); } // Position the editor next to the connection point. positionEditor(editor, inputPart); // Now add the editor to the table top. tableTopPanel.add(editor, 0); // Add to the list of active entry panels valueEditorHierarchyManager.addTopValueEditor(editor); // Update the context and editor map editor.setContext( new ValueEditorContext() { public TypeExpr getLeastConstrainedTypeExpr() { return inputPart.getType(); } }); inputToEditorMap.put(inputPart, editor); // If the editor resizes because it's value changes, make sure it stays aligned to the // connection editor.addComponentListener( new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { positionEditor((ValueEditor) e.getComponent(), inputPart); } }); } // Must prevent ValueGems from being editable. tableTopPanel.setValueGemsEnabled(false); // Need to activate the value editor hierarchy to ensure correct highlighting. valueEditorHierarchyManager.activateCurrentEditor(); // Set the context and type switch listener for each new editor. ArgumentValueCommitter argumentValueCommitter = new ArgumentValueCommitter(valueEditorManager, inputToEditorMap); for (final ValueEditor argumentEditor : getArgumentPanels()) { argumentEditor.addValueEditorListener(argumentValueCommitter); } // May need to show argument controls. if (numArgs > 0) { gemCutter.showArgumentControls(inputToEditorMap); gemCutter.getResetAction().setEnabled(true); } else { gemCutter.getResetAction().setEnabled(false); } }
/** * Return whether autoburning will result in a unification. Only inputs on the given gem will be * considered for burning. * * @param destType The destination type to unify with. * @param gem The gem on which to attempt "autoburning". * @param info the typeCheck info to use. * @return AutoburnLogic.AutoburnInfo autoburnable result. */ public static AutoburnInfo getAutoburnInfo(TypeExpr destType, Gem gem, TypeCheckInfo info) { // see if we got a real type if (destType == null) { return AutoburnInfo.makeNoUnificationPossibleAutoburnInfo(); } int[] burnableArgIndices; List<TypeExpr> sourceTypeList = new ArrayList<TypeExpr>(); // input types, then output type. TypeExpr outType = (gem instanceof CollectorGem) ? ((CollectorGem) gem).getCollectingPart().getType() : gem.getOutputPart().getType(); TypeExpr[] outTypePieces = outType.getTypePieces(); // A collector gem's collecting part is not burnable. if (gem instanceof CollectorGem && !((CollectorGem) gem).isConnected()) { burnableArgIndices = new int[0]; } else { // declare the burnt arg indices array burnableArgIndices = new int[gem.getNInputs()]; if (burnableArgIndices.length != 0) { // get the unbound input parts of the gem and keep track of which ones are burnable int unburnedCount = 0; int burnedCount = 0; for (int i = 0, n = gem.getNInputs(); i < n; i++) { Gem.PartInput input = gem.getInputPart(i); if (!input.isConnected()) { if (input.isBurnt()) { sourceTypeList.add(outTypePieces[burnedCount]); burnedCount++; } else { sourceTypeList.add(input.getType()); burnableArgIndices[unburnedCount] = sourceTypeList.size() - 1; unburnedCount++; } } } // Calculate what the output type would be if none of the arguments were burned. TypeExpr newOutType = outTypePieces[outTypePieces.length - 1]; for (int i = outTypePieces.length - 2; i >= burnedCount; i--) { newOutType = TypeExpr.makeFunType(outTypePieces[i], newOutType); } outType = newOutType; // Assign to a new trimmed down array int[] newIndices = new int[unburnedCount]; System.arraycopy(burnableArgIndices, 0, newIndices, 0, unburnedCount); burnableArgIndices = newIndices; } } // Add the output type to the source types. sourceTypeList.add(outType); return getAutoburnInfoWorker( destType, sourceTypeList.toArray(new TypeExpr[0]), burnableArgIndices, info, gem); }