/*
* 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.
*/
/*
* DesignRenameUpdater.java
* Created: Mar 7, 2005
* By: Peter Cardwell
*/
package org.openquark.gems.client;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openquark.cal.compiler.CodeQualificationMap;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleNameResolver;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.SourceIdentifier;
import org.openquark.cal.compiler.TypeChecker;
import org.openquark.cal.compiler.SourceIdentifier.Category;
import org.openquark.cal.services.CALFeatureName;
import org.openquark.cal.services.CALPersistenceHelper;
import org.openquark.cal.services.CALWorkspace;
import org.openquark.cal.services.GemDesign;
import org.openquark.cal.services.GemDesignManager;
import org.openquark.cal.services.GemDesignStore;
import org.openquark.cal.services.ResourceName;
import org.openquark.cal.services.Status;
import org.openquark.cal.services.WorkspaceManager;
import org.openquark.cal.services.WorkspaceResource;
import org.openquark.cal.valuenode.Target;
import org.openquark.cal.valuenode.TargetRunner;
import org.openquark.cal.valuenode.ValueNode;
import org.openquark.gems.client.Argument.NameTypePair;
import org.openquark.gems.client.Gem.PartInput;
import org.openquark.util.Pair;
import org.openquark.util.xml.BadXMLDocumentException;
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;
/**
* Provides functionality to update GemCutter entities to reflect a renaming.
*
* This class can be used to update either a design document or the gems on a tabletop whenever a gem, type class,
* or type constructor is renamed.
*
* @author Peter Cardwell
*/
public class GemCutterRenameUpdater {
/**
* Used for errors that occur during the updateDesigns operation.
* @author Peter Cardwell
*/
private static class RenamingException extends Exception {
private static final long serialVersionUID = -7382673467540795708L;
private final String errorMessage;
RenamingException (String errorMessage) {
super(errorMessage);
this.errorMessage = errorMessage;
}
String getErrorMessage() {
return errorMessage;
}
}
private final Status status;
private final TypeChecker typeChecker;
private final QualifiedName newName;
private final QualifiedName oldName;
private final SourceIdentifier.Category category;
/**
* Constructs a new DesignRenameUpdater
* @param status A status object to keep track of any error messages.
* @param typeChecker The type checker to use for the renaming of code expressions (ie. in code gems).
* @param newName The new name of the identifier to rename.
* @param oldName The old name of the identifier.
* @param category The category of the identifier.
*/
GemCutterRenameUpdater(Status status, TypeChecker typeChecker, QualifiedName newName, QualifiedName oldName, SourceIdentifier.Category category) {
this.status = status;
this.typeChecker = typeChecker;
this.newName = newName;
this.oldName = oldName;
this.category = category;
}
/**
* Replace the children nodes of the destination document with copies of the nodes of the source document.
* @param destinationDocument The document for which to replace the nodes
* @param sourceDocument The document to copy the nodes from
*/
private void replaceDocumentChildren(Document destinationDocument, Document sourceDocument) {
// First remove all children of the destination document
while(destinationDocument.hasChildNodes()) {
destinationDocument.removeChild(destinationDocument.getFirstChild());
}
// Now make a copy of each child node from the source document and append it to the destination document.
for (Node childNode = sourceDocument.getFirstChild();
childNode.getNextSibling() != null;
childNode = childNode.getNextSibling()){
destinationDocument.appendChild(childNode.cloneNode(true));
}
}
/**
* Updates all the designs stored in the given workspace to reflect the renaming.
* @param workspaceManager The workspace manager containing all the design the user wishes to update
* @return True if any designs were updated
*/
public boolean updateDesigns(WorkspaceManager workspaceManager) {
CALWorkspace workspace = workspaceManager.getWorkspace();
List<Pair<GemDesign, Document>> undoList = new ArrayList<Pair<GemDesign, Document>>(); // A list of designs that have been updated and should be undone if an error is encountered.
try {
ModuleName[] workspaceModuleNames = workspaceManager.getModuleNamesInProgram();
// loop through each module in the workspace
for (final ModuleName moduleName : workspaceModuleNames) {
if (workspace.getMetaModule(moduleName) == null) {
// the program contains the module but the workspace doesn't, so we cannot process it
continue;
}
ModuleNameResolver moduleNameResolver = workspaceManager.getModuleTypeInfo(moduleName).getModuleNameResolver();
GemDesignManager designManager = (GemDesignManager)workspace.getResourceManager(moduleName, WorkspaceResource.GEM_DESIGN_RESOURCE_TYPE);
// loop through the resources for this module
for (Iterator<WorkspaceResource> it = ((GemDesignStore)designManager.getResourceStore()).getResourceIterator(moduleName); it.hasNext(); ) {
WorkspaceResource designResource = it.next();
GemDesign gemDesign = GemDesign.loadGemDesign(designResource, new Status("Load Design Status"));
Document designDoc = gemDesign.getDesignDocument();
Document oldDesignDoc = (Document)designDoc.cloneNode(true);
boolean changesMade = updateDesignDocument(designDoc, gemDesign.getDesignName(), moduleNameResolver);
if (changesMade) {
if (!designManager.getResourceStore().isWriteable(new ResourceName(CALFeatureName.getFunctionFeatureName(gemDesign.getDesignName())))) {
throw new RenamingException("Can not update the design for " + gemDesign.getDesignName().getQualifiedName() + " because it is not writeable.");
}
Status saveGemStatus = new Status("Save gem design status");
workspace.saveDesign(gemDesign, saveGemStatus);
if (saveGemStatus.getSeverity().equals(Status.Severity.ERROR)) {
throw new RenamingException("Error saving the updated design for " + gemDesign.getDesignName().getQualifiedName() + ".");
}
undoList.add(new Pair<GemDesign, Document>(gemDesign, oldDesignDoc));
}
}
}
// Return true if the undoList has contents (ie. changes have been made)
return !undoList.isEmpty();
} catch (RenamingException e) {
status.add(new Status(Status.Severity.ERROR, e.getErrorMessage()));
for (int i = undoList.size() - 1; i >= 0; i--) {
Pair<GemDesign, Document> designDocPair = undoList.get(i);
GemDesign gemDesign = designDocPair.fst();
Document oldDesignDoc = designDocPair.snd();
replaceDocumentChildren(gemDesign.getDesignDocument(), oldDesignDoc);
Status undoStatus = new Status("Undo design update status");
workspace.saveDesign(gemDesign, undoStatus);
if (undoStatus.getSeverity().equals(Status.Severity.ERROR)) {
String msg = "FATAL: Error undoing the design update for " + gemDesign.getDesignName().getQualifiedName();
status.add(new Status(Status.Severity.ERROR, msg));
}
}
return false;
}
}
/**
* Updates all references to the renamed entity in the given design document.
* @param document The document to update.
* @param designName The qualifiedName of the gem that this design represents.
* @param moduleNameResolver the module name resolver for the module containing the document.
* @return True if changes were made to the document.
*/
private boolean updateDesignDocument (Document document, QualifiedName designName, ModuleNameResolver moduleNameResolver) throws RenamingException {
boolean changesMade = false;
try {
// Get the document element
Element documentElement = document.getDocumentElement();
XMLPersistenceHelper.checkTag(documentElement, GemPersistenceConstants.GEMCUTTER_TAG);
// Get the table top element
Node tableTopNode = documentElement.getFirstChild();
XMLPersistenceHelper.checkIsElement(tableTopNode);
Element tableTopElement = (Element)tableTopNode;
XMLPersistenceHelper.checkTag(tableTopElement, GemPersistenceConstants.TABLETOP_TAG);
// Loop over all the children of the tabletop node
Node tableToChildNode;
for (tableToChildNode = tableTopElement.getFirstChild();
GemCutterPersistenceHelper.isDisplayedGemElement(tableToChildNode) ||
GemCutterPersistenceHelper.isDisplayedGemElement(tableToChildNode.getFirstChild()); // old save format
tableToChildNode = tableToChildNode.getNextSibling()) {
Element displayedGemElement = (Element) tableToChildNode;
// Do a check for the old save format
Node firstChild = displayedGemElement.getFirstChild();
if (firstChild instanceof Element && firstChild.getLocalName().equals(GemPersistenceConstants.DISPLAYED_GEM_TAG)) {
displayedGemElement = (Element)firstChild;
}
// Get the gem node and dispatch to the appropriate helper method based on the tag name.
Node gemNode = displayedGemElement.getFirstChild();
XMLPersistenceHelper.checkIsElement(gemNode);
String tagName = gemNode.getLocalName();
if (tagName.equals(GemPersistenceConstants.COLLECTOR_GEM_TAG)) {
changesMade |= updateCollectorGemElement(document, designName.getModuleName(), gemNode);
} else if (tagName.equals(GemPersistenceConstants.FUNCTIONAL_AGENT_GEM_TAG)) {
changesMade |= updateFunctionalAgentElement(document, moduleNameResolver, gemNode);
} else if (tagName.equals(GemPersistenceConstants.VALUE_GEM_TAG)) {
changesMade |= updateValueGemElement(document, designName.getModuleName(), moduleNameResolver, gemNode);
} else if (tagName.equals(GemPersistenceConstants.CODE_GEM_TAG)) {
changesMade |= updateCodeGemElement(document, designName.getModuleName(), moduleNameResolver, gemNode);
}
}
return changesMade;
} catch (BadXMLDocumentException ex) {
throw new RenamingException("The design document for " + designName.getQualifiedName() + " is formatted incorrectly.");
}
}
/**
* Updates this collector gem design element if it represents the name of the gem being renamed.
* @param document The document that this node is contained in.
* @param moduleName The module that this gem design is contained in.
* @param gemNode The node representing the collector gem to update.
* @return True if changes were made to the element.
*/
private boolean updateCollectorGemElement(Document document, ModuleName moduleName, Node gemNode) throws BadXMLDocumentException {
boolean changesMade = false;
List<Element> childElems = XMLPersistenceHelper.getChildElements(gemNode);
Element nameElem = (childElems.size() < 2) ? null : (Element) childElems.get(1);
XMLPersistenceHelper.checkIsElement(nameElem);
String gemName = CALPersistenceHelper.elementToUnqualifiedName(nameElem);
if ((category == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) &&
gemName.equals(oldName.getUnqualifiedName()) && moduleName.equals(oldName.getModuleName())) {
gemNode.replaceChild(CALPersistenceHelper.unqualifiedNameToElement(newName.getUnqualifiedName(), document), nameElem);
changesMade = true;
}
return changesMade;
}
/**
* Updates the contents of this value gem design element if it references the entity being renamed at all.
* @param document The document that this node is contained in.
* @param moduleName The module that this gem design is contained in.
* @param moduleNameResolver the module name resolver for the module containing the document.
* @param gemNode The node representing the value gem to update.
* @return True if changes were made to the element
*/
private boolean updateValueGemElement(Document document, ModuleName moduleName, ModuleNameResolver moduleNameResolver, Node gemNode) throws BadXMLDocumentException {
boolean changesMade = false;
List<Element> childElems = XMLPersistenceHelper.getChildElements(gemNode);
Element valueChildElem = (childElems.size() < 2) ? null : (Element) childElems.get(1);
XMLPersistenceHelper.checkIsElement(valueChildElem);
XMLPersistenceHelper.checkTag(valueChildElem, GemPersistenceConstants.VALUE_GEM_VALUE_TAG);
// Get the value text
StringBuilder valueText = new StringBuilder();
Node valueChild = valueChildElem.getFirstChild();
XMLPersistenceHelper.getAdjacentCharacterData(valueChild, valueText);
String valueString = new String(valueText);
String updatedValueString = typeChecker.calculateUpdatedCodeExpression(valueString, moduleName, moduleNameResolver, null, oldName, newName, category, null);
if (!updatedValueString.equals(valueString)) {
CDATASection newValueChild = XMLPersistenceHelper.createCDATASection(document, updatedValueString);
valueChildElem.replaceChild(newValueChild, valueChild);
changesMade = true;
}
return changesMade;
}
/**
* Updates the functional agent gem if it represents the gem being renamed.
* @param document The document that this node is contained in.
* @param moduleNameResolver the module name resolver for the module containing the document.
* @param gemNode The node representing the functional agent gem to update.
* @return True if changes were made to the element
*/
private boolean updateFunctionalAgentElement(Document document, ModuleNameResolver moduleNameResolver, Node gemNode) throws BadXMLDocumentException {
boolean changesMade = false;
List<Element> childElems = XMLPersistenceHelper.getChildElements(gemNode);
Element nameElem = (childElems.size() < 2) ? null : (Element) childElems.get(1);
XMLPersistenceHelper.checkIsElement(nameElem);
QualifiedName gemName = CALPersistenceHelper.elementToQualifiedName(nameElem);
if ((category == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD ||
category == SourceIdentifier.Category.DATA_CONSTRUCTOR) &&
gemName.equals(oldName)) {
gemNode.replaceChild(CALPersistenceHelper.qualifiedNameToElement(newName, document), nameElem);
changesMade = true;
} else if ( (category == SourceIdentifier.Category.MODULE_NAME) &&
moduleNameResolver.resolve(gemName.getModuleName()).getResolvedModuleName().equals(oldName.getModuleName()) ){
QualifiedName newGemName = QualifiedName.make(newName.getModuleName(), gemName.getUnqualifiedName());
gemNode.replaceChild(CALPersistenceHelper.qualifiedNameToElement(newGemName, document), nameElem);
changesMade = true;
}
return changesMade;
}
/**
* Updates the functional agent gem if it represents the gem being renamed.
* This method will try to resolve any conflicts between the new name and any arguments to the code gem.
* It will do this by modifying the name of any argument that matches the new name of renaming.
* @param document The document that this node is contained in.
* @param moduleNameResolver the module name resolver for the module containing the document.
* @param gemNode The node representing the functional agent gem to update.
* @return True if changes were made to the element.
*/
private boolean updateCodeGemElement(Document document, ModuleName moduleName, ModuleNameResolver moduleNameResolver, Node gemNode) throws BadXMLDocumentException {
boolean changesMade = false;
List<Element> childElems = XMLPersistenceHelper.getChildElements(gemNode);
Element codeElement = (childElems.size() < 3) ? null : (Element) childElems.get(2);
XMLPersistenceHelper.checkIsElement(codeElement);
StringBuilder codeText = new StringBuilder();
Node codeChild = codeElement.getFirstChild();
XMLPersistenceHelper.getAdjacentCharacterData(codeChild, codeText);
String codeString = new String(codeText);
String updatedCodeString = codeString;
// Load the qualification map
Element qualificationMapElement = XMLPersistenceHelper.getChildElement(codeElement, CodeQualificationMap.QUALIFICATION_MAP_TAG);
CodeQualificationMap qualificationMap = new CodeQualificationMap();
if (qualificationMapElement != null) {
qualificationMap.loadFromXML(codeElement);
}
if (category != SourceIdentifier.Category.MODULE_NAME) {
QualifiedName qualificationMapEntry = qualificationMap.getQualifiedName(oldName.getUnqualifiedName(), category);
if (oldName.equals(qualificationMapEntry)) {
// The old name is in the qualification map. This means there might be an unqualified reference to the symbol that when
// renamed might clash with one of the arguments. Let's check for that now.
// First, load the arguments
Element argsElement = (childElems.size() < 4) ? null : (Element) childElems.get(3);
XMLPersistenceHelper.checkIsElement(argsElement);
XMLPersistenceHelper.checkTag(argsElement, GemPersistenceConstants.CODE_GEM_ARGUMENTS_TAG);
// We'll create both a list (for easy iteration) and a set (for easy membership checks).
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);
argNames[i] = argNode.getAttribute(GemPersistenceConstants.CODE_GEM_ARGUMENT_NAME_ATTR);
}
List<String> argNamesList = Arrays.asList(argNames);
Set<String> argNamesSet = new HashSet<String>(argNamesList);
// Next, loop over the arguments and look for the conflict..
for (int i = 0, n = argNamesList.size(); i < n; i++) {
String argName = argNamesList.get(i);
if (argName.equals(newName.getUnqualifiedName())) {
// Here we are. The new name is going to collide with this argument's name.
// We are going to have to rename this argument.
QualifiedName oldArgName = QualifiedName.make(moduleName, argName);
// Figure out a suitable new name for the argument that does not conflict with anything else.
int suffix = 1;
QualifiedName newArgName;
while (true) {
String potentialName = argName + suffix;
// Make sure that the new potential name is not the name of another argument and is not in the qualification map
if (!argNamesSet.contains(potentialName) &&
(qualificationMap.getQualifiedName(
potentialName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) == null)) {
newArgName = QualifiedName.make(moduleName, potentialName);
break;
}
suffix++;
}
// Update the reference to the argument in the code expression
updatedCodeString = typeChecker.calculateUpdatedCodeExpression(codeString, moduleName, moduleNameResolver, qualificationMap, oldArgName, newArgName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD, null);
// Create a new argument node and replace the old one with it.
Element oldArgNode = argNodes.get(i);
Element newArgNode = document.createElement(GemPersistenceConstants.CODE_GEM_ARGUMENT_TAG);
newArgNode.setAttribute(GemPersistenceConstants.CODE_GEM_ARGUMENT_NAME_ATTR, newArgName.getUnqualifiedName());
argsElement.replaceChild(newArgNode, oldArgNode);
changesMade = true;
break;
}
}
}
}
// Update the code expression
updatedCodeString = typeChecker.calculateUpdatedCodeExpression(updatedCodeString, moduleName, moduleNameResolver, qualificationMap, oldName, newName, category, null);
if (!updatedCodeString.equals(codeString)) {
CDATASection newCodeChild = XMLPersistenceHelper.createCDATASection(document, updatedCodeString);
codeElement.replaceChild(newCodeChild, codeChild);
changesMade = true;
}
// Update the qualification map
if (qualificationMap != null) {
if (updateQualificationMap(qualificationMap)) {
codeElement.removeChild(qualificationMapElement);
qualificationMap.saveToXML(codeElement);
changesMade = true;
}
}
return changesMade;
}
/**
* Updates all references to the renamed entity in the given CodeQualificationMap. If the entity type is a module,
* this can include any identifiers that use that module name. Otherwise, the qualification for the entity itself will be
* updated if it is in the map.
* @param qualificationMap The map to update
* @return True if the qualification map is changed.
*/
private boolean updateQualificationMap(CodeQualificationMap qualificationMap) {
boolean changesMade = false;
SourceIdentifier.Category[] categories = new SourceIdentifier.Category[] {
SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD,
SourceIdentifier.Category.DATA_CONSTRUCTOR,
SourceIdentifier.Category.TYPE_CONSTRUCTOR,
SourceIdentifier.Category.TYPE_CLASS
};
for (final Category sectionCategory : categories) {
Set<String> unqualifiedNames = qualificationMap.getUnqualifiedNames(sectionCategory);
for (final String string : unqualifiedNames) {
String unqualifiedName = string;
QualifiedName qualifiedName = qualificationMap.getQualifiedName(unqualifiedName, sectionCategory);
if (category == sectionCategory && oldName.equals(qualifiedName)) {
qualificationMap.removeQualification(unqualifiedName, sectionCategory);
qualificationMap.putQualification(newName.getUnqualifiedName(), newName.getModuleName(), sectionCategory);
changesMade = true;
} else if (category == SourceIdentifier.Category.MODULE_NAME && oldName.getModuleName().equals(qualifiedName.getModuleName())) {
qualificationMap.removeQualification(unqualifiedName, sectionCategory);
qualificationMap.putQualification(unqualifiedName, newName.getModuleName(), sectionCategory);
changesMade = true;
}
}
}
return changesMade;
}
/**
* Updates all references to the renamed entity on the current TableTop (Ie. in the gem graph and any on screen code editors).
* This includes functional agent references, collector gems, code gems, and value nodes.
* @param gemCutter
* @throws GemEntityNotPresentException
*/
public void updateTableTop(GemCutter gemCutter) throws GemEntityNotPresentException {
ModuleName workingModuleName = gemCutter.getPerspective().getWorkingModuleName();
ModuleNameResolver workingModuleNameResolver = gemCutter.getPerspective().getWorkingModuleTypeInfo().getModuleNameResolver();
for (final Gem gem : gemCutter.getTableTop().getGemGraph().getGems()) {
if (gem instanceof FunctionalAgentGem) {
FunctionalAgentGem fGem = (FunctionalAgentGem)gem;
if (fGem.getName().equals(oldName)) {
fGem.updateGemEntity(gemCutter.getWorkspace(), newName);
} else {
fGem.updateGemEntity(gemCutter.getWorkspace());
}
} else if (gem instanceof CollectorGem) {
CollectorGem cGem = (CollectorGem)gem;
if (cGem.getUnqualifiedName().equals(oldName.getUnqualifiedName())) {
cGem.setName(newName.getUnqualifiedName());
}
} else if (gem instanceof CodeGem) {
CodeGem cGem = (CodeGem)gem;
CodeGemEditor codeGemEditor = gemCutter.getTableTop().getCodeGemEditor(cGem);
codeGemEditor.setDelayUpdatingForTextChanges(false);
codeGemEditor.setCodeAnalyser(gemCutter.getCodeGemAnalyser());
String visibleCode = codeGemEditor.getGemCodePanel().getCALEditorPane().getText();
String updatedVisibleCode = visibleCode;
if (category != SourceIdentifier.Category.MODULE_NAME) {
QualifiedName qualificationMapEntry = cGem.getQualificationMap().getQualifiedName(oldName.getUnqualifiedName(), category);
if (oldName.equals(qualificationMapEntry)) {
// The old name is in the qualification map. This means there might be an unqualified reference to the symbol that when
// renamed might clash with one of the arguments. Let's check for that now.
NameTypePair[] arguments = cGem.getArguments();
for (int i = 0, n = arguments.length; i < n; i++) {
if (arguments[i].getName().equals(newName.getUnqualifiedName())) {
// Here we are. The new name is going to collide with this argument's name.
// We are going to have to rename this argument..
QualifiedName oldArgName = QualifiedName.make(workingModuleName, arguments[i].getName());
// Append successive integers onto the end of the argument name until we no longer have a conflict.
Set<String> argNameSet = cGem.getArgNameToInputMap().keySet();
int suffix = 1;
QualifiedName newArgName;
while (true) {
String potentialName = arguments[i].getName() + suffix;
// Make sure that the new potential name is not the name of another argument and is not in the qualification map
if (!argNameSet.contains(potentialName) &&
(cGem.getQualificationMap().getQualifiedName(
potentialName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) == null)) {
newArgName = QualifiedName.make(workingModuleName, potentialName);
break;
}
suffix++;
}
// Update references to the argument in the code itself
updatedVisibleCode = typeChecker.calculateUpdatedCodeExpression(visibleCode, workingModuleName, workingModuleNameResolver, cGem.getQualificationMap(), oldArgName, newArgName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD, null);
// Update the argument to input map and the argument names in the code gem
Map<String, PartInput> newArgNameToInputMap = cGem.getArgNameToInputMap();
newArgNameToInputMap.put(newArgName.getUnqualifiedName(), newArgNameToInputMap.get(oldArgName.getUnqualifiedName()));
newArgNameToInputMap.remove(oldArgName.getUnqualifiedName());
arguments[i] = new NameTypePair(newArgName.getUnqualifiedName(), arguments[i].getType());
cGem.definitionUpdate(cGem.getCode(), arguments, cGem.getCodeResultType(), newArgNameToInputMap, cGem.getQualificationMap(), cGem.getVisibleCode());
// Replace the code in the editor and make sure the old argument name is no longer in the editor's list of arguments.
codeGemEditor.getGemCodePanel().getCALEditorPane().setText(updatedVisibleCode);
codeGemEditor.changeArgumentToQualification(oldArgName.getUnqualifiedName(), newName.getModuleName());
break;
}
}
}
}
// Update any references to the entity in the code
updatedVisibleCode = typeChecker.calculateUpdatedCodeExpression(updatedVisibleCode, workingModuleName, workingModuleNameResolver, cGem.getQualificationMap(), oldName, newName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD, null);
if (category == SourceIdentifier.Category.MODULE_NAME) {
codeGemEditor.updateQualificationsForModuleRename(oldName.getModuleName(), newName.getModuleName());
} else {
codeGemEditor.updateQualificationsForEntityRename(oldName, newName, category);
}
codeGemEditor.getGemCodePanel().getCALEditorPane().setText(updatedVisibleCode);
codeGemEditor.setDelayUpdatingForTextChanges(true);
} else if (gem instanceof ValueGem) {
ValueGem vGem = (ValueGem) gem;
ValueNode vNode = vGem.getValueNode();
String valueString = vNode.getCALValue();
String updatedValueString = typeChecker.calculateUpdatedCodeExpression(valueString, workingModuleName, workingModuleNameResolver, null, oldName, newName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD, null);
if (!valueString.equals(updatedValueString)) {
// Create a target to be run by a value runner, and return the result
Target valueTarget = new Target.SimpleTarget(updatedValueString);
ValueNode targetValue = null;
try {
targetValue = gemCutter.getValueRunner().getValue(valueTarget, workingModuleName);
} catch (TargetRunner.ProgramCompileException pce) {
status.add(new Status(Status.Severity.ERROR, "Error updating value gem. Can't compile the following text: " + updatedValueString));
}
if (targetValue == null) {
status.add(new Status(Status.Severity.ERROR, "Error updating value gem. Can't convert the following text to a value node: " + updatedValueString));
}
vGem.changeValue(targetValue);
}
}
}
}
}