Package org.openquark.gems.client

Source Code of org.openquark.gems.client.DisplayedGemRunner$RunSetupThread

/*
* 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);
    }
}

TOP

Related Classes of org.openquark.gems.client.DisplayedGemRunner$RunSetupThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.