Package org.openquark.gems.client

Source Code of org.openquark.gems.client.CodeGem

/*
* 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.
*/


/*
* CodeGem.java
* Creation date: (1/18/01)
* By: Luke Evans
*/
package org.openquark.gems.client;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.openquark.cal.compiler.CodeAnalyser;
import org.openquark.cal.compiler.CodeQualificationMap;
import org.openquark.cal.compiler.CompositionNode;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.metadata.ArgumentMetadata;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.services.CALFeatureName;
import org.openquark.cal.services.CALPersistenceHelper;
import org.openquark.gems.client.Argument.NameTypePair;
import org.openquark.util.Pair;
import org.openquark.util.xml.BadXMLDocumentException;
import org.openquark.util.xml.XMLPersistenceConstants;
import org.openquark.util.xml.XMLPersistenceHelper;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;


/**
* A CodeGem is a FunctionalAgentGem which is defined from code which can be modified.
* @author Luke Evans
*/
public final class CodeGem extends Gem implements CompositionNode.Lambda, NamedGem {

    /** Listener for definition change events on this gem */
    private CodeGemDefinitionChangeListener definitionChangeListener;
   
    /**
     * A default name for a code gem.
     * This is needed to pass validation on creation, but we shouldn't see ever see this in the GemCutter.
     */
    private static final String DEFAULT_NAME = "unassignedCodeGem";
   
    /** Unqualified name of the code gem */
    private String unqualifiedName;
   
    /** Fully qualified code which this gem is responsible for. */
    private String qualifiedCode;
   
    /** Code visible to the user. This is a version of the qualified code which may contain unqualified identifiers. */
    private String visibleCode;

    //
    // Definition of the pseudo-gem entity.
    // The size of the arguments array gives the number of named arguments.
    // The type of the pseudo-entity is arg1type -> arg2type -> ... -> argntype -> codeResultType.
    //
   
    /** Arguments used in the codegem */
    private Argument.NameTypePair[] arguments = new Argument.NameTypePair[0];
   
    /** Result type of the code expression; null if broken. */
    private TypeExpr codeResultType;
   
    /** Gem broken state (appearance to indicate incompleteness) */
    private boolean broken;
   
    /*
     * Keys for fields in map used with state editable interface
     */
    private static final String BROKEN_KEY = "BrokenStateKey";
    private static final String INPUT_PARTS_KEY = "InputPartsStateKey";
    private static final String VISIBLECODE_KEY = "VisibleCodeStateKey";
    private static final String QUALIFICATIONMAP_KEY = "QualificationMapStateKey";
    private static final String ARGUMENTS_KEY = "ArgumentsStateKey";
    private static final String CODE_RESULT_TYPE_KEY = "CodeResultTypeStateKey";
    private static final String UNQUALIFIED_NAME_KEY = "UnqualifiedNameKey";
    private static final String QUALIFIED_CODE_KEY = "QualifiedCodeKey";
   
    /** Symbol mapping from unqualified to qualified names */
    private CodeQualificationMap qualificationMap;
   
   
    /**
     * Constructor for a CodeGem.
     */
    public CodeGem() {
        // Initialize with no inputs.
        super(0);

        setName(DEFAULT_NAME);
       
        // Create an empty qualification map
        qualificationMap = new CodeQualificationMap();
       
        // Initialize code and arguments to nothing
        qualifiedCode = "";
        visibleCode = "";

        // As this constructor creates an empty code object, this gem must start out broken
        setBroken(true);
    }

    /**
     * Constructor for a code gem with a given number of inputs, and whose connectable parts will have
     *   parametric type.
     * @param nInputs the number of inputs on the code gem.
     */
    CodeGem(int nInputs) {
        this();
       
        // Create the arguments.       
        Argument.NameTypePair[] codeGemArguments = new Argument.NameTypePair[nInputs];
        for (int i = 0; i < nInputs; i++) {
            String argName = "x" + i;
            codeGemArguments[i] = new Argument.NameTypePair(argName, TypeExpr.makeParametricType());
        }
       
        // Set the code
        setQualifiedCode(CAL_Prelude.Functions.error.getQualifiedName() + " \"Uninitialized code gem.\"");
        visibleCode = qualifiedCode;
        qualificationMap = new CodeQualificationMap();

        // Set the arguments
        setArguments(codeGemArguments);

        // Set the code result type.
        codeResultType = TypeExpr.makeParametricType();
       
        // Make appropriate inputs
        morphInputs(codeGemArguments, new HashMap<String, PartInput>());

        // Update the part types. 
        for (int i = 0; i < codeGemArguments.length; i++) {
            getInputPart(i).setType(codeGemArguments[i].getType());
        }
        getOutputPart().setType(codeResultType);

        // update the broken state
        setBroken(false);
    }

    /**
     * Constructor for a CodeGem.
     * The result type and argument info will be determined (if possible) using the specified code analyser.
     * @param codeAnalyser
     * @param visibleCode
     * @param varNamesWhichAreArgs
     */
    public CodeGem (CodeAnalyser codeAnalyser,
                    String visibleCode,
                    Set<String> varNamesWhichAreArgs) {
        this();

        this.visibleCode = visibleCode;
        CodeAnalyser.AnalysisResults results = codeAnalyser.analyseCode (visibleCode, varNamesWhichAreArgs, null);
        qualificationMap = results.getQualificationMap();

        // Find the new arguments in the appropriate order.
        TypeExpr newCodeGemType = results.getTypeExpr ();
        String [] argNamesFromCode = results.getAllArgumentNames();

        // Make an argument list
        int argCount = argNamesFromCode.length;
        Argument.NameTypePair[] argsFromCode = new Argument.NameTypePair[argCount];
   
        if (newCodeGemType != null) {
            TypeExpr[] typePieces = newCodeGemType.getTypePieces();
            for (int i = 0; i < argCount; i++) {
                argsFromCode[i] = new Argument.NameTypePair(argNamesFromCode[i], typePieces[i]);
            }
           
        } else {
            // No type - broken gem.
            for (int i = 0; i < argCount; i++) {
                argsFromCode[i] = new Argument.NameTypePair(argNamesFromCode[i], TypeExpr.makeParametricType());
            }
        }
       
        // Get the output type; note that the type expression contains only used argument types
        Map<String, PartInput> oldNameToInputMap = new HashMap<String, PartInput> ();
        TypeExpr codeResultType = results.getTypeExpr() == null ? null : results.getTypeExpr().dropFirstNArgs(argCount);

        // Update the code gem           
        definitionUpdate(results.getQualifiedCode(), argsFromCode, codeResultType, oldNameToInputMap, results.getQualificationMap(), visibleCode);
       
        // Update the part types. 
        for (int i = 0; i < argsFromCode.length; i++) {
            getInputPart(i).setType(argsFromCode[i].getType());
        }
        getOutputPart().setType(codeResultType);
    }

    /**
     * Obtain the code contribution of this CompositionNode, assuming this node is of an
     * appropriate code contributing type.
     * @return the code contribution
     */
    public String getLambda() {
        // CodeGems are lambda expressions.  Return the validated code only

        //todoBI remove this (taken from SupercombinatorFromCode.makeLambda())
        /*
         // Need to omit the trailing semicolon on the body
         String body;
         int lastChar = qualifiedCode.length() - 1;
         if (qualifiedCode.charAt(lastChar) == ';') {
         body = qualifiedCode.substring(0, lastChar);
         } else {
         body = qualifiedCode;
         }
         */
       
        String body = qualifiedCode;
       
        //constants such as "abc" are not written out as \ -> "abc", since this is invalid CAL syntax.
        if (arguments.length == 0) {
            return body;
        }
       
        // Create the root of a lambda definition
        StringBuilder lambda = new StringBuilder("\\");
       
        // Add formal parameters
        for (int i = 0; i < arguments.length; i++) {       
            // Get this argument
            Argument.NameTypePair arg = arguments[i];
           
            // Add space?
            if (i > 0) {
                lambda.append(' ');
            }
           
            // Add argument name (unqualified, as qualification yields bad CAL syntax)
            // The arguments come from the code, so the unqualified names unambiguously come from the current module
            lambda.append(arg.getName());
        }
       
        lambda.append(" -> " + body);
       
        // Return the final lambda
        return lambda.toString();
    }
   
    /**
     * Returns the fully qualified code contained in this code gem.
     */
    public String getCode () {
        return qualifiedCode;
    }
   
    /**
     * Get the arguments for this code gem.
     * @return Argument.NameTypePair[] the arguments.  This should never be null.
     */
    public Argument.NameTypePair[] getArguments() {
        return arguments.clone();
    }
   
    /**
     * Set the arguments for this code gem.
     * @param args the arguments
     */
    private void setArguments(Argument.NameTypePair[] args) {
        this.arguments = (args == null) ? new Argument.NameTypePair[0] : (Argument.NameTypePair[])args.clone();
    }
   
    /**
     * Returns the code visible to the user (may include unqualified names)
     */
    public String getVisibleCode() {
        return visibleCode;
    }

    /**
     * Get the unqualified name of the Gem (the name of the underlying supercombinator)
     * @return the name
     */
    public String getUnqualifiedName() {
        return unqualifiedName;
    }

    /**
     * Returns true if this Gem has been given a name.
     * @return boolean
     */
    boolean isNameInitialized() {
        // If the name is not the same object as the default name then it has been initialized.
        return DEFAULT_NAME != getUnqualifiedName();
    }

    /**
     * Returns whether this gem is broken
     * @return boolean true if this gem is broken
     */
    @Override
    public boolean isBroken() {
        return broken;
    }

    /**
     * Set the broken state of this Gem
     * @param newBroken boolean the new broken state of the Gem
     */   
    void setBroken(boolean newBroken) {
        if (broken != newBroken) {
            broken = newBroken;
            if (gemStateListener != null) {
                gemStateListener.brokenStateChanged(new GemStateEvent(this, GemStateEvent.EventType.BROKEN));
            }
        }
    }
   
    /**
     * Get an updated map from arg name to input.
     * @return Map an updated map from arg name to input.
     */
    public Map<String, PartInput> getArgNameToInputMap() {

        Map<String, PartInput> resultMap = new HashMap<String, PartInput>();

        int nArgs = getNInputs();
        for (int i = 0; i < nArgs; i++) {
            String argName = arguments[i].getName();
            PartInput input = getInputPart(i);
            resultMap.put(argName, input);
        }

        return resultMap;
    }

    /**
     * Indicate that the definition of the code gem function has changed.
     * Note that the brokenness characteristic of the code gem will be updated such that it reflects the validity of the code gem itself.
     * It will be up to the caller to further update the brokenness of the codegem according to whether its connections (if any) are valid.
     *
     * @param qualifiedCodeGemText the qualified expression code
     * @param newCodeGemArgs what the new list of arguments will be.  Argument types can be null
     *      if the new code gem state is broken.
     * @param codeResultType the result type from the code.  Null if broken.
     *   The type of the code will be argType_1 -> .. -> argType_n -> codeResultType.
     * @param argNameToInputMap the map from arg name to input from the last "good" state of the code gem
     */
    void definitionUpdate(String qualifiedCodeGemText, Argument.NameTypePair[] newCodeGemArgs, TypeExpr codeResultType,
            Map<String, PartInput> argNameToInputMap, CodeQualificationMap qualificationMap, String codeGemVisibleText) {

        // Set the code
        setQualifiedCode(qualifiedCodeGemText);
        visibleCode = codeGemVisibleText;
       
        // Set the qualification map
        this.qualificationMap = qualificationMap;

        // Set the arguments
        setArguments(newCodeGemArgs);

        // Set the result type.
        this.codeResultType = codeResultType;

        // Make appropriate inputs
        morphInputs(newCodeGemArgs, argNameToInputMap);

        // update the broken state, according to whether the code was ok.
        setBroken(codeResultType == null);

        // notify listeners of the definition change
        if (definitionChangeListener != null) {
            definitionChangeListener.codeGemDefinitionChanged(new CodeGemDefinitionChangeEvent(CodeGem.this));
        }
    }

    /**
     * Morph the number and type of the input parts.
     *
     * @param args the array of arguments in their new order
     * @param argNameToInputMap the map from old arg name to input. 
     *   We need this as a parameter (rather than generating it from current inputs) since we want to preserve inputs
     *   and the code gem may be in an intermediate invalid form
     */
    private void morphInputs(Argument.NameTypePair[] args, Map<String, PartInput> argNameToInputMap) {

        PartInput[] newInputPartArray = new PartInput[args.length];

        // make a list of the inputs with corresponding variables in the typed-in code
        PartInput input;
        for (int i = 0; i < args.length; i++) {
            // if you want to preserve the input, take the reference, else make up the type using args
            input = argNameToInputMap.get(args[i].getName());

            if (input != null) {
                input.setInputNum(i);                    // update input #
            } else {
                input = createInputPart(i);
                input.setArgumentName(new ArgumentName(args[i].getName()));
               
                ArgumentMetadata metadata = new ArgumentMetadata(CALFeatureName.getArgumentFeatureName(i), input.getDesignMetadata().getLocale());
                metadata.setDisplayName(args[i].getName());
                input.setDesignMetadata(metadata);
            }
            newInputPartArray[i] = input;
        }

        // newInputPartArray has the new input parts.  Switch the parts over
        setInputParts(newInputPartArray);
    }

    /**
     * Describe this Gem
     * @return the description
     */
    @Override
    public String toString() {
        return getUnqualifiedName();
    }

    /*
     * Methods to handle listeners     ****************************************************************
     */

    /**
     * Adds the specified gem definition listener to receive gem definition events from this gem.
     * If l is null, no exception is thrown and no action is performed.
     * @param l GemDefinitionListener the GemDefinitionListener to notify upon source code change
     */
    public void addDefinitionChangeListener(CodeGemDefinitionChangeListener l) {
        if (l == null) {
            return;
        }
        definitionChangeListener = GemEventMulticaster.add(definitionChangeListener, l);
    }

    /**
     * Removes the specified definition listener so that it no longer receives burn events from this gem.
     * This method performs no function, nor does it throw an exception, if the listener specified by
     * the argument was not previously added to this component.
     * If l is null, no exception is thrown and no action is performed.
     * @param l GemDefinitionListener the GemDefinitionListener to remove
     */
    public void removeDefinitionChangeListener(CodeGemDefinitionChangeListener l) {
        if (l == null) {
            return;
        }
        definitionChangeListener = GemEventMulticaster.remove(definitionChangeListener, l);
    }

    /*
     * Methods supporting javax.swing.undo.StateEditable ********************************************
     */

    /**
     * Restore the stored codegem state.
     * @param state the stored state
     */
    void restoreDefinitionState(Hashtable<?, ?> state) {

        Object stateValue;
       
        stateValue = state.get(new Pair<CodeGem, String>(CodeGem.this, QUALIFICATIONMAP_KEY));
        if (stateValue != null) {
            qualificationMap = ((CodeQualificationMap)stateValue).makeCopy();
        }

        stateValue = state.get(new Pair<CodeGem, String>(CodeGem.this, INPUT_PARTS_KEY));
        if (stateValue != null) {
            PartInput[] newInputParts = ((PartInput[])stateValue).clone();
           
            // Make sure inputs have the appropriate indices
            for (int i = 0; i < newInputParts.length; i++) {
                PartInput input = newInputParts[i];
                input.setInputNum(i);
            }
           
            setInputParts(newInputParts);
        }

        stateValue = state.get(new Pair<CodeGem, String>(CodeGem.this, BROKEN_KEY));
        if (stateValue != null) {
            broken = ((Boolean)stateValue).booleanValue();
        }
       
        stateValue = state.get(new Pair<CodeGem, String>(CodeGem.this, VISIBLECODE_KEY));
        if (stateValue != null) {
            visibleCode = (String)stateValue;
        }
       
        stateValue = state.get(new Pair<CodeGem, String>(CodeGem.this, ARGUMENTS_KEY));
        if (stateValue != null) {
            arguments = (Argument.NameTypePair[])stateValue;
        }
       
        stateValue = state.get(new Pair<CodeGem, String>(CodeGem.this, CODE_RESULT_TYPE_KEY));
        if (stateValue != null) {
            codeResultType = (TypeExpr)stateValue;
        }
       
        stateValue = state.get(new Pair<CodeGem, String>(CodeGem.this, UNQUALIFIED_NAME_KEY));
        if (stateValue != null) {
            unqualifiedName = (String)stateValue;
        }
       
        stateValue = state.get(new Pair<CodeGem, String>(CodeGem.this, QUALIFIED_CODE_KEY));
        if (stateValue != null) {
            qualifiedCode = (String)stateValue;
        }
       
        // notify listeners of the definition change
        if (definitionChangeListener != null) {
            definitionChangeListener.codeGemDefinitionChanged(new CodeGemDefinitionChangeEvent(CodeGem.this));
        }
    }

    /**
     * Save the codegem state.
     * @param state the table in which to store the codegem state
     */
    void storeDefinitionState(Hashtable<Object, Object> state) {
       
        // Now store all the states.  The state key we use is a pair: this code gem and the field key.
        state.put(new Pair<CodeGem, String>(CodeGem.this, QUALIFICATIONMAP_KEY), qualificationMap);
        state.put(new Pair<CodeGem, String>(CodeGem.this, INPUT_PARTS_KEY), getInputParts());
        state.put(new Pair<CodeGem, String>(CodeGem.this, BROKEN_KEY), Boolean.valueOf(isBroken()));
        state.put(new Pair<CodeGem, String>(CodeGem.this, VISIBLECODE_KEY), visibleCode);
        state.put(new Pair<CodeGem, String>(CodeGem.this, ARGUMENTS_KEY), arguments);
        state.put(new Pair<CodeGem, String>(CodeGem.this, UNQUALIFIED_NAME_KEY), unqualifiedName);
        state.put(new Pair<CodeGem, String>(CodeGem.this, QUALIFIED_CODE_KEY), qualifiedCode);
        if (codeResultType != null) {
            state.put(new Pair<CodeGem, String>(CodeGem.this, CODE_RESULT_TYPE_KEY), codeResultType);
        }
    }


    /*
     * Methods supporting XMLPersistable                 ********************************************
     *
     */

    /**
     * {@inheritDoc}
     */
    @Override
    public void saveXML(Node parentNode, GemContext gemContext) {
       
        Document document = (parentNode instanceof Document) ? (Document)parentNode : parentNode.getOwnerDocument();

        // Create the code gem element
        Element resultElement = document.createElementNS(GemPersistenceConstants.GEM_NS, GemPersistenceConstants.CODE_GEM_TAG);
        resultElement.setPrefix(GemPersistenceConstants.GEM_NS_PREFIX);
        parentNode.appendChild(resultElement);

        // Add info for the superclass gem.
        super.saveXML(resultElement, gemContext);


        // Now add CodeGem-specific info

        // Add an element for the name.
        Element nameElement = CALPersistenceHelper.unqualifiedNameToElement(getUnqualifiedName(), document);
        resultElement.appendChild(nameElement);

        // Add the code
        Element codeElement = document.createElement(GemPersistenceConstants.CODE_GEM_CODE_TAG);
        resultElement.appendChild(codeElement);
        String code = getVisibleCode();
        CDATASection codeNode = XMLPersistenceHelper.createCDATASection(document, code);
        codeElement.appendChild(codeNode);
       
        // Add the code qualification map
        qualificationMap.saveToXML(codeElement);

        // Add the args (tells us about any input reordering).
        Element argsElement = document.createElement(GemPersistenceConstants.CODE_GEM_ARGUMENTS_TAG);
        resultElement.appendChild(argsElement);

        for (final NameTypePair arg : arguments) {

            Element argElement = document.createElement(GemPersistenceConstants.CODE_GEM_ARGUMENT_TAG);
            argElement.setAttribute(GemPersistenceConstants.CODE_GEM_ARGUMENT_NAME_ATTR, arg.getName());

            argsElement.appendChild(argElement);
        }
       
        // Add brokenness
        resultElement.setAttribute(GemPersistenceConstants.CODE_GEM_BROKEN_ATTR, isBroken() ? XMLPersistenceConstants.TRUE_STRING : XMLPersistenceConstants.FALSE_STRING);
    }

    /**
     * Create a new CodeGem and loads its state from the specified XML element.
     * @param gemElement Element the element representing the structure to deserialize.
     * @param gemContext the context in which the gem is being instantiated.
     * @param codeAnalyser used to analyze the gem code
     * @return CodeGem
     * @throws BadXMLDocumentException
     */
    public static CodeGem getFromXML(Element gemElement,
                                     GemContext gemContext,
                                     CodeAnalyser codeAnalyser) throws BadXMLDocumentException {
        CodeGem gem = new CodeGem();
        gem.loadXML(gemElement, gemContext, codeAnalyser);
        return gem;
    }

    /**
     * Load this object's state.
     * Note: part types are not set in this method.
     * @param gemElement Element the element representing the structure to deserialize.
     * @param gemContext the context in which the gem is being instantiated.
     * @param codeAnalyser object to use for code analysis
     */
    void loadXML(Element gemElement,
                 GemContext gemContext,
                 CodeAnalyser codeAnalyser) throws BadXMLDocumentException {

        XMLPersistenceHelper.checkTag(gemElement, GemPersistenceConstants.CODE_GEM_TAG);
        XMLPersistenceHelper.checkPrefix(gemElement, GemPersistenceConstants.GEM_NS_PREFIX);

        List<Element> childElems = XMLPersistenceHelper.getChildElements(gemElement);
       
        // Get info for the underlying gem.
        Element superGemElem = (childElems.size() < 1) ? null : childElems.get(0);
        XMLPersistenceHelper.checkIsElement(superGemElem);
        super.loadXML(superGemElem, gemContext);

        // Get the name
        Element nameElem = (childElems.size() < 2) ? null : childElems.get(1);
        XMLPersistenceHelper.checkIsElement(nameElem);
        String gemName = CALPersistenceHelper.elementToUnqualifiedName(nameElem);
        setName(gemName);

        // Get the code
        Element codeElement = (childElems.size() < 3) ? null : childElems.get(2);
        XMLPersistenceHelper.checkIsElement(codeElement);
        XMLPersistenceHelper.checkTag(codeElement, GemPersistenceConstants.CODE_GEM_CODE_TAG);

        StringBuilder codeText = new StringBuilder();
        Node codeChild = codeElement.getFirstChild();
        XMLPersistenceHelper.getAdjacentCharacterData(codeChild, codeText);
        String codeString = codeText.toString();
        setQualifiedCode(codeString);
       
        // Get and check the qualification map
        // This check is done for backwards compatibility, in case we are loading
        // a code gem without a qualification map.
        if (XMLPersistenceHelper.getChildElement(codeElement, CodeQualificationMap.QUALIFICATION_MAP_TAG) != null) {
            qualificationMap.loadFromXML(codeElement);
            if (!codeAnalyser.checkQualificationMapValidity(qualificationMap)) {
                // The code qualification mappings have become invalid. We'll create a blank
                // qualification map and likely end up with a broken code gem.
                qualificationMap = new CodeQualificationMap();
            }
        } else {
            qualificationMap = new CodeQualificationMap();
        }
       
        // Get the args
        Element argsElement = (childElems.size() < 4) ? null : childElems.get(3);
        XMLPersistenceHelper.checkIsElement(argsElement);
        XMLPersistenceHelper.checkTag(argsElement, GemPersistenceConstants.CODE_GEM_ARGUMENTS_TAG);

        List<Element> argNodes = XMLPersistenceHelper.getChildElements(argsElement, GemPersistenceConstants.CODE_GEM_ARGUMENT_TAG);
        int numArgs = argNodes.size();
        String[] argNames = new String[numArgs];

        for (int i = 0; i < numArgs; i++) {
            Element argNode = argNodes.get(i);

            String argName = argNode.getAttribute(GemPersistenceConstants.CODE_GEM_ARGUMENT_NAME_ATTR);
            argNames[i] = argName;
        }
        Set<String> argNamesSet = new HashSet<String>(Arrays.asList(argNames));

        // Get whether this code gem is broken.
        boolean shouldBeBroken;
        try {
            shouldBeBroken = XMLPersistenceHelper.getBooleanAttribute(gemElement, GemPersistenceConstants.CODE_GEM_BROKEN_ATTR);
        } catch (BadXMLDocumentException e) {
            // Must be in the old save format.
            // TODOEL: remove when save formats have been updated.
            shouldBeBroken = false;
        }

       
        //
        // Begin code gem definition initialization.
        //
       
        // Analyze the code, and reevaluate arguments
        CodeAnalyser.AnalysisResults analysisResults = codeAnalyser.analyseCode(codeString, argNamesSet, qualificationMap);
       
        TypeExpr typeExpr = analysisResults.getTypeExpr();
        String[] argNamesFromCode = analysisResults.getAllArgumentNames();
       
        // Map arg names to types
        Map<String, TypeExpr> argNameToTypeMap = new HashMap<String, TypeExpr>();
        if (typeExpr != null) {
            TypeExpr[] typePieces = typeExpr.getTypePieces();
            for (int i = 0; i < argNamesFromCode.length; i++) {
                argNameToTypeMap.put(argNamesFromCode[i], typePieces[i]);
            }
        }

        // Get the arguments.
        Argument.NameTypePair[] codeGemArgs = new Argument.NameTypePair[argNames.length];
        for (int i = 0; i < argNames.length; i++) {
            String argName = argNames[i];
            TypeExpr argType = argNameToTypeMap.get(argName);
            if (argType == null) {
                // Argument is not used in code but part of the code gem
                argType = TypeExpr.makeParametricType();
                if (typeExpr != null) {
                    // This would not show as part of the type expression, so add it
                    typeExpr = TypeExpr.makeFunType(argType, typeExpr);
                }
            }
            codeGemArgs[i] = new Argument.NameTypePair(argName, argType);
        }
        setArguments(codeGemArgs);
       
        Map<String, PartInput> argNameToInputMap = getArgNameToInputMap();

        // Get the output type.
        TypeExpr codeResultType = typeExpr == null ? null : typeExpr.dropFirstNArgs(argNames.length);

        // Update the code gem
        definitionUpdate(analysisResults.getQualifiedCode(),
                         codeGemArgs,
                         codeResultType,
                         argNameToInputMap,
                         analysisResults.getQualificationMap(),
                         codeString);
       
        // Update with the real broken state.
        shouldBeBroken |= (typeExpr == null);       // old save format..
        setBroken(shouldBeBroken);
    }
   
    /**
     * Make a copy of this codeGem. Note that namespace validity is not ensured
     * @return CodeGem
     */
    public CodeGem makeCopy() {
       
        // Make a new clone
        CodeGem clone = new CodeGem();
        
        // get the code from the original
        String oldCode = qualifiedCode;
       
        // Get the old arguments
        Argument.NameTypePair[] oldArguments = arguments;
       
        // make a new copy
        Argument.NameTypePair[] newArguments = new Argument.NameTypePair[oldArguments.length];
       
        // build a blank mapping so everything is updated in definition update.
        Map<String, PartInput> argNameToArgMap = new HashMap<String, PartInput>();
     
        for (int k =0; k < oldArguments.length; k++) {
            newArguments[k] = new Argument.NameTypePair(oldArguments[k].getName(), oldArguments[k].getType());
            argNameToArgMap.put(newArguments[k].getName(), null);
        }
       
        CodeQualificationMap newQualificationMap = this.qualificationMap.makeCopy();
       
        // update everything!
        clone.setName(getUnqualifiedName());
        clone.definitionUpdate(oldCode, newArguments, getCodeResultType(), argNameToArgMap, newQualificationMap, visibleCode);
       
        clone.setBroken(broken);
               
        return clone;
    }
   
    /**
     * Sets the name of this code
     * @param name
     */
    public void setName(String name) {
        String oldName = this.unqualifiedName;
        this.unqualifiedName = name;
        if (nameChangeListener != null) {
            nameChangeListener.nameChanged(new NameChangeEvent(this, oldName));
        }
    }
   
    /**
     * Set the CAL source for this supercombinator.
     * @param qualifiedCode the code
     */
    private void setQualifiedCode(String qualifiedCode) {
        this.qualifiedCode = qualifiedCode;
    }
   
    /**
     * Get the code result type.
     * @return TypeExpr the code result type.
     */
    public TypeExpr getCodeResultType() {
        return codeResultType;
    }

    /**
     * Gets mapping from unqualified to qualified identifiers
     */
    public CodeQualificationMap getQualificationMap() {
        return qualificationMap;
    }
}

TOP

Related Classes of org.openquark.gems.client.CodeGem

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.