/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * DisplayedGemRunner.java * Creation date: (Jun 14, 2002 4:48:45 PM) * By: Edward Lam */ package org.openquark.gems.client; import java.awt.Point; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.swing.JOptionPane; import org.openquark.cal.compiler.CompilerMessage; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.TypeConsApp; import org.openquark.cal.compiler.TypeException; import org.openquark.cal.compiler.TypeExpr; import org.openquark.cal.compiler.io.InputPolicy; import org.openquark.cal.machine.StatsGenerator; import org.openquark.cal.runtime.CALExecutorException; import org.openquark.cal.services.WorkspaceManager; import org.openquark.cal.valuenode.ForeignValueNode; import org.openquark.cal.valuenode.TargetRunner; import org.openquark.cal.valuenode.ValueNode; import org.openquark.cal.valuenode.ValueNodeBuilderHelper; import org.openquark.gems.client.Gem.PartInput; import org.openquark.gems.client.valueentry.ValueEditor; import org.openquark.gems.client.valueentry.ValueEditorAdapter; import org.openquark.gems.client.valueentry.ValueEditorContext; import org.openquark.gems.client.valueentry.ValueEditorDirector; import org.openquark.gems.client.valueentry.ValueEditorEvent; import org.openquark.gems.client.valueentry.ValueEditorHierarchyManager; import org.openquark.gems.client.valueentry.ValueEditorManager; import org.openquark.gems.client.valueentry.ValueEntryPanel; /** * The GemCutter class delegates to DisplayedGemRunner to coordinate the actual work of running a displayed gem. * Creation date: (Jun 14, 2002 4:48:45 PM) * @author Edward Lam */ class DisplayedGemRunner extends TargetRunner { private final GemCutter gemCutter; /** Since the displayed GemRunner essentially has its own ValueEditor hierarchy, it makes sense that it has its own ValueEditorHierarchyManager */ private final ValueEditorHierarchyManager valueEditorHierarchyManager; /** The displayed gem to run */ private DisplayedGem targetDisplayedGem; /** The thread responsible for gathering arguments and running. Null if not waiting for arguments. */ private RunSetupThread runSetupThread = null; /** * An ArgumentValueCommitter is responsible for listening to changes in the value of an argument, and * propagating any type changes to other arguments. * @author Edward Lam */ private static class ArgumentValueCommitter extends ValueEditorAdapter { /** The value editor manager in which the context exists. * Used for access to its value node builder helper and value node transformer. */ private final ValueEditorManager valueEditorManager; /** map from part input to the value editor whose value it's editing. */ private final Map<PartInput, ValueEditor> inputToEditorMap; /** * The default constructor for this ArgumentValueContext * @param valueEditorManager the editor manager for this listener * @param inputToEditorMap map from input to the editor for the value of that input. */ public ArgumentValueCommitter(ValueEditorManager valueEditorManager, Map<PartInput, ValueEditor> inputToEditorMap) { this.inputToEditorMap = new HashMap<PartInput, ValueEditor>(inputToEditorMap); this.valueEditorManager = valueEditorManager; } /** * {@inheritDoc} */ @Override public void valueCommitted(ValueEditorEvent evt) { ValueEditor committedEditor = (ValueEditor)evt.getSource(); ValueNode oldValue = evt.getOldValue(); ValueNode newValue = committedEditor.getValueNode(); if (!oldValue.sameValue(newValue)) { switchValueNodeTypeExpr(oldValue, newValue); committedEditor.setSize(committedEditor.getPreferredSize()); } } /** * This is used for switching the type of a ValueNode within a given context. * Basically, a switch is made between valueNode and newValueNode, but their types are different. * Consequently, other valueNodes may be modified. * * For example: * * Let's say you have "add 1 2", where 1, 2 are of type Integer and are represented by ValueNodes, * then to switch '1' to '1.0' (a Double), you must switch '2' to '2.0' to avoid type clashes. * * This method will handle the switching of the associated types within a type context, so it should so if you pass 1, 1.0 * it'll convert 2 to 2.0 . * * Note that you are able to pass in '5.0' as the new value if you'd like. * * @param oldValueNode your original ValueNode * @param newValueNode the valueNode that you want to arrive at */ private void switchValueNodeTypeExpr(ValueNode oldValueNode, ValueNode newValueNode) { // Create the map from input to value node Map<PartInput, ValueNode> inputToValueNodeMap = new HashMap<PartInput, ValueNode>(); for (final PartInput input : inputToEditorMap.keySet()) { ValueEditor editor = inputToEditorMap.get(input); inputToValueNodeMap.put(input, editor.getOwnerValueNode()); } // Create the type switcher InputValueTypeSwitchHelper switcher = new InputValueTypeSwitchHelper(valueEditorManager.getValueNodeCommitHelper()); // Get the switched values Map<PartInput, ValueNode> inputNewValueMap = switcher.getInputSwitchValues(oldValueNode, newValueNode, inputToValueNodeMap); // Now update with the switched values. for (final Map.Entry<PartInput, ValueNode> mapEntry : inputNewValueMap.entrySet()) { Gem.PartInput input = mapEntry.getKey(); ValueNode newInputValue = mapEntry.getValue(); ValueEditor editor = inputToEditorMap.get(input); editor.changeOwnerValue(newInputValue); editor.setSize(editor.getPreferredSize()); } } } /** * A class responsible for putting up argument controls, gathering arguments, and executing with those args. * When this thread is started, it either executes straight away (if no args are required) or * sets up the gui to enter arguments, then waits for notification that args are entered. * To execute, this thread starts another executor thread to actually perform the evaluation. * @author Edward Lam */ private class RunSetupThread extends Thread { /** * The run setup thread may display input panels and wait on this lock. * If so, on notify, the thread gathers the user-entered arguments and then executes. */ private final ExecuteLock runSetupLock = new ExecuteLock(); /** On notification, whether the thread should run with arguments. False results in termination. */ private boolean shouldRun; /** * Trivial class to implement a lock. * @author Edward Lam */ private class ExecuteLock { private volatile boolean shouldWait = false; public synchronized void executeSleep() { shouldWait = true; try { while (shouldWait) { wait(); } } catch (InterruptedException e) { } } public synchronized void executeWake(boolean shouldRun) { RunSetupThread.this.shouldRun = shouldRun; shouldWait = false; notify(); } } /** * {@inheritDoc} * This Runnable causes the GemCutter to enter the GUI state, and either * 1) Put up argument controls, wait for notification that args are entered, and executes with those args, or * 2) Executes without args if no args are necessary. * * Notification is given by calling executeWake() on the executeLock. */ @Override public void run() { // Use input policies for arguments. // We're going into the run GUI state gemCutter.enterGUIState(GemCutter.GUIState.RUN); InputPolicy[] inputPolicies = new InputPolicy[0]; TypeExpr[] argTypes = new TypeExpr[0]; Object[] args = new Object[0]; // Display arg controls and wait if there are args to enter. Otherwise, execute straight away. if (targetHasArgs()) { // Set transport controls status for running gemCutter.setTargetRunningButtons(true); // Set the status message saying we're at the start. gemCutter.getStatusMessageDisplayer().setMessageFromResource(this, "SM_AtStart", StatusMessageDisplayer.MessageType.DEFERENTIAL); // Put up the argument controls displayArgumentControls(); // Wait for the user to enter the arguments. waitForInput(); // set the setup thread to null. Do this first in case the next call fails. runSetupThread = null; if (!shouldRun) { // User pressed stop. clearGUIRunState(); return; } // get the args inputPolicies = getInputPolicies(); argTypes = getArgTypes(); args = getRuntimeArgumentValues(); if (args == null || inputPolicies == null) { // There was an error getting args. clearGUIRunState(); return; } } // set the setup thread to null runSetupThread = null; // Build the test program try { buildTestProgram(inputPolicies, argTypes); } catch (ProgramCompileException pce) { // A problem occurred CompilerMessage.Severity errorSeverity = pce.getMaxErrorSeverity(); if (errorSeverity.compareTo (CompilerMessage.Severity.FATAL) < 0) { // Some error in compiling String msgText = GemCutter.getResourceString("TestHadErrors") + "\n" + GemCutter.getResourceString("CompileErrorIntro") + "\n" + pce.getFirstError(); JOptionPane.showMessageDialog(gemCutter, msgText, GemCutter.getResourceString("WindowTitle"), JOptionPane.WARNING_MESSAGE); } else { // BIG problems String msgText = GemCutter.getResourceString("CompileGemError") + "\n" + GemCutter.getResourceString("CompileErrorIntro") + "\n" + pce.getFirstError(); JOptionPane.showMessageDialog(gemCutter, msgText, GemCutter.getResourceString("WindowTitle"), JOptionPane.ERROR_MESSAGE); } gemCutter.getTableTop().setRunning(targetDisplayedGem, false); clearGUIRunState(); return; } // Execute the new SC with the arguments. executeTestProgram(args, 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); } } /** * Positions the given value editor next to the connection point for the given input. * @param editor the editor to position * @param inputPart the input part to position it next to */ private void positionEditor(ValueEditor editor, Gem.PartInput inputPart) { // Determine position of the panel and make sure it will be within the parent Point xy = gemCutter.getTableTop().getDisplayedPartConnectable(inputPart).getConnectionPoint(); editor.setSize(editor.getPreferredSize()); // Make sure this is OK xy.x -= editor.getWidth(); xy.y -= editor.getHeight()/2; if (xy.x <= 0) { xy.x = 0; } if (xy.y <= 0) { xy.y = 0; } editor.setLocation(xy); } /** * See if we can reasonably use the values cached in the target's argument parts. * For now this just means if we can use the cached panels without performing any type * conversions of any sort. * Precondition: target and inputToTypeExprMap are set * Postcondition: none. * @return boolean true if it's ok to use the values cached in the target's arguments. */ private boolean canUseCachedArguments() { List<Gem.PartInput> argParts = targetDisplayedGem.getTargetArguments(); // Get the input types. TypeExpr[] targetPieces = new TypeExpr[argParts.size()]; for (int i = 0; i < targetPieces.length; i++) { targetPieces[i] = argParts.get(i).getType(); } TypeExpr[] typesFromCachedArgs = new TypeExpr[targetPieces.length]; // Step through args. i'th arg corresponds to the i'th piece. // Note that there may be more pieces than args if the output type is a function for (int i = 0; i < targetPieces.length; i++) { // Get this input, and any cached value Gem.PartInput sinkPart = argParts.get(i); ValueNode cachedVN = gemCutter.getTableTop().getCachedValue(sinkPart); // there may be no cached value for this argument if (cachedVN != null) { typesFromCachedArgs[i] = cachedVN.getTypeExpr(); } } ModuleTypeInfo contextModuleTypeInfo = gemCutter.getPerspective().getWorkingModuleTypeInfo(); return TypeExpr.canPatternMatchPieces(typesFromCachedArgs, targetPieces, contextModuleTypeInfo); } private InputPolicy[] getInputPolicies() { List<ValueEditor> argPanelList = getArgumentPanels(); InputPolicy[] ips = new InputPolicy[argPanelList.size()]; for (int i = 0, nArgs = argPanelList.size(); i < nArgs; i++) { ValueEditor argPanel = argPanelList.get(i); ips[i] = argPanel.getValueNode().getInputPolicy(); } return ips; } private TypeExpr[] getArgTypes() { List<ValueEditor> argPanelList = getArgumentPanels(); TypeExpr[] argTypes = new TypeExpr[argPanelList.size()]; for (int i = 0, nArgs = argPanelList.size(); i < nArgs; i++) { ValueEditor argPanel = argPanelList.get(i); argTypes[i] = argPanel.getValueNode().getTypeExpr(); } return argTypes; } private Object[] getRuntimeArgumentValues() { List<ValueEditor> argPanelList = getArgumentPanels(); ArrayList<Object> inputJavaValues = new ArrayList<Object>(); for (int i = 0, nArgs = argPanelList.size(); i < nArgs; i++) { ValueEditor argPanel = argPanelList.get(i); Object[] values = argPanel.getValueNode().getInputJavaValues(); for (final Object value: values) { inputJavaValues.add(value); } } return inputJavaValues.toArray(); } /** * Wait for user input. */ synchronized void waitForInput() { runSetupLock.executeSleep(); } } /** * Constructor for a displayed gem runner. * @param workspaceManager the workspaceManager on which this runner is based. * @param gemCutter the reference to the GemCutter */ public DisplayedGemRunner(WorkspaceManager workspaceManager, GemCutter gemCutter) { super(workspaceManager); if (gemCutter == null) { throw new NullPointerException("Argument 'gemCutter' cannot be null."); } this.gemCutter = gemCutter; valueEditorHierarchyManager = new ValueEditorHierarchyManager(gemCutter.getValueEditorManager()); } /** * Notify the setup thread to run with the arguments from current user input. */ private synchronized void gatherArgumentsAndRun() { runSetupThread.runSetupLock.executeWake(true); } /** * Execute the supercombinator defined by the Target. * @param targetDisplayedGem the gem to run. Pass null if just continuing execution. */ public void runTargetDisplayedGem(DisplayedGem targetDisplayedGem) { // Clear away any children value editors. This commits values from argument panels and ValueGem VEPs. commitActiveEntryPanels(); if (targetDisplayedGem == null) { // Either a start from argument entry, or a continue. (or an error..) if (runSetupThread != null) { // We're waiting for arguments. Get the setup thread to gather args and start execution gatherArgumentsAndRun(); } else { // Already executing, just return and let the executor to continue running } return; } setTargetDisplayedGem(targetDisplayedGem, gemCutter.getWorkingModuleName()); // If we're not executing, get going. We should be starting from edit mode... if (runtime == null) { TableTop tableTop = gemCutter.getTableTop(); // Must stop Intellicut mode. tableTop.getIntellicutManager().stopIntellicut(); // Must get rid of any open code gem editors. tableTop.hideAllCodeGemEditors(); // ensure the target is visible getTableTopPanel().scrollRectToVisible(this.targetDisplayedGem.getBounds()); // First, check that we can handle the output. // Then, check if this is can be sensibly tested (by supplying unbound args) if (!targetHasHandleableOutput()) { String msgText = GemCutter.getResourceString("OutputTypeNotHandled"); JOptionPane.showMessageDialog(gemCutter, msgText, GemCutter.getResourceString("CannotExec"), JOptionPane.INFORMATION_MESSAGE); } else if (targetHasProvidableArgs()) { // Target is running.. gemCutter.getTableTop().setRunning(targetDisplayedGem, true); // Setup the run. runSetupThread = new RunSetupThread(); runSetupThread.start(); } else { // Can't allow execution as we cannot provide default or user provided // arguments of the correct type to satisfy all unbound arguments String msgText = GemCutter.getResourceString("CannotProvideExecArgs"); JOptionPane.showMessageDialog(gemCutter, msgText, GemCutter.getResourceString("CannotExec"), JOptionPane.INFORMATION_MESSAGE); } } else { // For some reason, this is called with a target but we're already running. // Already executing, just return and let the executor to continue running } } /** * Set the displayed gem being run by this target runner. * @param targetDisplayedGem the displayed gem to run. * @param targetModule the module in which the target exists. */ void setTargetDisplayedGem(DisplayedGem targetDisplayedGem, ModuleName targetModule) { setTarget(targetDisplayedGem.getTarget(), targetModule); this.targetDisplayedGem = targetDisplayedGem; } /** * Clears the data types and values of the arguments of the running gem */ void resetArgumentValues() { // Loop thru the ValueEditors and reset their ValueNodes. for (final ValueEditor editor : valueEditorHierarchyManager.getTopValueEditors()) { // build the new ValueNode and give it to the argument panel TypeExpr leastConstrainedType = editor.getContext().getLeastConstrainedTypeExpr(); ValueNode newNode = valueEditorHierarchyManager.getValueEditorManager().getValueNodeBuilderHelper().getValueNodeForTypeExpr(leastConstrainedType); editor.changeOwnerValue(newNode); } } /** * Cache the values entered by the user in VEPs dynamically-displayed on VM start. */ void cacheArgumentValues() { // Collect the value entry panels which correspond to target arguments in the TableTop List<ValueEditor> argPanels = getArgumentPanels(); List<Gem.PartInput> argParts = targetDisplayedGem.getTargetArguments(); int numArgs = argParts.size(); // sanity check if (numArgs != argPanels.size()) { throw new IllegalStateException("Programming Error: matching " + numArgs + " arguments and " + argPanels.size() + " argument panels."); } for (int i = 0; i < numArgs; i++) { Gem.PartInput argPart = argParts.get(i); ValueEditor argPanel = argPanels.get(i); ValueNode argNode = argPanel.getValueNode(); gemCutter.getTableTop().cacheArgValue(argPart, argNode); } } /** * Remove any value entry controls for unbound arguments. */ private void removeArgumentControls() { // Remove the argument controls from the TableTop valueEditorHierarchyManager.removeValueEditors(getArgumentPanels(), true); // No longer need the argument controls. gemCutter.hideArgumentControls(); TableTopPanel tableTopPanel = getTableTopPanel(); // Re-enable ValueGems. tableTopPanel.setValueGemsEnabled(true); tableTopPanel.repaint(); } /** * Display execution results. Call this to display the results of the run when execution terminates. * Note: the runtime must still be available */ private void displayResults() { int messageIcon = -1; String messageTitle = null; String errorMessage = null; // Set up the icon and the title if (error == null) { messageIcon = JOptionPane.INFORMATION_MESSAGE; messageTitle = GemCutter.getResourceString("GemResults"); errorMessage = null; } else { errorMessage = error.getMessage(); CALExecutorException.Type resultStatus = error.getExceptionType(); if (resultStatus == CALExecutorException.Type.PRIM_THROW_FUNCTION_CALLED) { messageIcon = JOptionPane.ERROR_MESSAGE; messageTitle = GemCutter.getResourceString("GemError"); } else if (resultStatus == CALExecutorException.Type.FOREIGN_OR_PRIMITIVE_FUNCTION_EXCEPTION) { messageIcon = JOptionPane.ERROR_MESSAGE; messageTitle = GemCutter.getResourceString("ForeignFunctionError"); } else if (resultStatus == CALExecutorException.Type.ERROR_FUNCTION_CALL) { messageIcon = JOptionPane.ERROR_MESSAGE; messageTitle = GemCutter.getResourceString("GemError"); } else if (resultStatus == CALExecutorException.Type.PATTERN_MATCH_FAILURE) { messageIcon = JOptionPane.ERROR_MESSAGE; messageTitle = GemCutter.getResourceString("PatternMatchFailure"); } else if (resultStatus == CALExecutorException.Type.INTERNAL_RUNTIME_ERROR) { messageIcon = JOptionPane.ERROR_MESSAGE; messageTitle = GemCutter.getResourceString("RuntimeError"); } else if (resultStatus == CALExecutorException.Type.USER_TERMINATED) { messageIcon = JOptionPane.INFORMATION_MESSAGE; messageTitle = GemCutter.getResourceString("GemResults"); } else { throw new IllegalStateException("Unrecognized execution result status: " + resultStatus); } } if (executionResult != null) { try { executionResult.toString (); } catch (Exception e) { executionResult = null; if (errorMessage == null) { errorMessage = "Error trying to display result value."; } } } // If we're not in debug output mode, and there's no scary weirdness, then we use the // value entry mechanism. Else, just use the debug output. if (!gemCutter.isDebugOutputMode()// && (numValues > 0) //&& !programmaticErrorFlagged /*&& valueNodeBuilderHelper.isConstructorNodeArgumentStackEmpty()*/) { // If there's an extra message, print it out. ValueNode vn = getOutputValueNode(); if (executionResult == null) { // If we got a null result and no error, check for the case where the null represents a null foreign value. // foreignTypeConsApp will hold the TypeConsApp for the output value // if the value is a foreign value and the result is null. TypeConsApp foreignTypeConsApp = null; if (errorMessage == null && vn != null) { TypeConsApp typeConsApp = vn.getTypeExpr().rootTypeConsApp(); if (typeConsApp != null && typeConsApp.getForeignTypeInfo() != null) { foreignTypeConsApp = typeConsApp; } } if (foreignTypeConsApp != null) { // Make sure that the value node is a ForeignValueNode. // An example of a problem this prevents: // The ColorValueNode is encapsulates a foreign Java value java.awt.Color, and so has a foreign type. // However, the color output editor may assume that it has a non-null editor. if (!(vn instanceof ForeignValueNode)) { vn = new ForeignValueNode(null, foreignTypeConsApp); } vn.setOutputJavaValue(null); } else { vn = null; } } else { vn.setOutputJavaValue(executionResult); } // Make sure target is visible before we display results gemCutter.getTableTop().getTableTopPanel().scrollRectToVisible(targetDisplayedGem.getBounds()); gemCutter.displayOutput(vn, targetDisplayedGem, messageTitle, errorMessage); } else { // The main return is the 0th output value String message = (executionResult != null) ? executionResult.toString() : GemCutter.getResourceString("NoResults"); // Display the output, debug style. JOptionPane.showMessageDialog(gemCutter, message, messageTitle, messageIcon); } } /** * This method is called when the runtime is about to start evaluating. */ @Override public void enterRunningState() { List<ValueEditor> argPanelList = getArgumentPanels(); // If there are argument panels, we use these to specialize the target // type. int nPanels = argPanelList.size (); TypeExpr[] argPanelTypes = new TypeExpr[nPanels]; for (int i = 0; i < nPanels; i++) { ValueEditor argPanel = argPanelList.get (i); argPanelTypes[i] = argPanel.getValueNode ().getTypeExpr (); } gemCutter.getStatusMessageDisplayer ().clearMessage (this); gemCutter.setTargetRunningButtons (true); // System.out.println ("DisplayedGemRunner.run()"); } /** * {@inheritDoc} */ @Override public void exitRunningState() { // Can't run forward (can only restart or quit) gemCutter.setTargetRunningButtons(true); // Show results try { displayResults(); } catch (Throwable t) { t.printStackTrace(); } clearGUIRunState(); // Set the termination message String terminationMessage = getTerminationMessage(); gemCutter.getStatusMessageDisplayer().clearMessage(this); gemCutter.getStatusMessageDisplayer().setMessage(this, terminationMessage, StatusMessageDisplayer.MessageType.DEFERENTIAL); } /** * {@inheritDoc} */ @Override protected void stopExecution() { if (runSetupThread != null) { // waiting for arguments runSetupThread.runSetupLock.executeWake(false); runSetupThread = null; return; } // Executing. super.stopExecution(); } /** * Clear the GUI elements associated with the run state. */ private void clearGUIRunState() { // We're leaving the run state gemCutter.enterGUIState(GemCutter.GUIState.EDIT); gemCutter.getTableTop().setRunning(targetDisplayedGem, false); // cache the values entered in the VEPs in their corresponding parts cacheArgumentValues(); // Clear away any children value editors (but not top level ValueEditors). commitActiveEntryPanels(); // Remove the argument controls removeArgumentControls(); } /** * Get the termination message from the runtime. * This should only be called once the current runtime is terminated (but still available). * @return the related termination message */ private String getTerminationMessage() { StringBuilder terminationMessage = new StringBuilder (); if (error != null && error.getExceptionType() == CALExecutorException.Type.USER_TERMINATED) { // externally-requested termination terminationMessage.append (GemCutter.getResourceString("SM_TerminatedPrematurely")); } else { // runtime stopped of its own accord terminationMessage.append (GemCutter.getResourceString("SM_Terminated")); } if (execTimeGen != null) { for (final StatsGenerator.StatsObject stat : execTimeGen.getStatistics()) { terminationMessage.append (stat.generateShortMessage()); } } return terminationMessage.toString (); } /** * Get the TableTopPanel associated with this runner. * @return TableTopPanel */ private TableTopPanel getTableTopPanel() { return gemCutter.getTableTopPanel(); } /** * Returns whether the target's output type can be handled. * @return boolean */ private boolean targetHasHandleableOutput() { TypeExpr outputTypeExpr = targetDisplayedGem.getGem().getResultType(); return ValueNodeBuilderHelper.canHandleOutput(outputTypeExpr); } /** * Determine if the current target Gem has any arguments. * @return boolean whether the current target has any arguments. */ private boolean targetHasArgs() { return (!targetDisplayedGem.getTargetArguments().isEmpty()); } /** * 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; } /** * Returns the top level ValueEditors for arguments managed by this displayed gem runner. * @return The top-level argument panels */ private List<ValueEditor> getArgumentPanels() { return valueEditorHierarchyManager.getTopValueEditors(); } /** * Closes all children of the editors for arguments and value gems and commits their values. */ private void commitActiveEntryPanels() { valueEditorHierarchyManager.commitHierarchy(true); ValueEditorHierarchyManager gemCutterHierarchyManager = gemCutter.getValueEditorHierarchyManager(); gemCutterHierarchyManager.commitHierarchy(true); ValueEditorHierarchyManager explorerHierarchyManager = gemCutter.getTableTopExplorer().getValueEditorHierarchyManager(); explorerHierarchyManager.commitHierarchy(true); } }