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