/*
* 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.
*/
/*
* Refactorer.java
* Creation date: (Feb 27, 2006)
* By: James Wright
*/
package org.openquark.cal.compiler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.openquark.cal.compiler.ModuleContainer.ISourceManager2;
import org.openquark.cal.compiler.SourceModel.ModuleDefn;
import org.openquark.cal.metadata.MetadataRenameUpdater;
import org.openquark.cal.services.CALFeatureName;
import org.openquark.cal.services.Status;
import org.openquark.cal.services.WorkspaceResource;
import org.openquark.util.General;
import org.openquark.util.Pair;
/**
*
* A Refactorer represents a specific refactoring operation on a specific
* module or set of modules.
* <p>
* The usage pattern is:
* <ol>
* <li> Construct one of Refactorer's subclasses
* <li> Call the calculateModifications method to determine the modifications
* the refactoring requires
* <li> Call the apply method to apply the refactoring to the module(s)
* <li> (optional) Call undo
* <li> (optional) Call apply
* </ol>
* We do some consistency checking (ie, calling apply before calculateModifications
* or undo before apply will cause an exception), but we don't check every case.
* eg, calling apply multiple times can cause data corruption.
* <p>
* This class contains a lot of code that was factored out of the RenameRefactoring class
* (which no longer exists). Some of RenameRefactoring's functionality was factored out
* into the base-class, and the renaming-specific portions were moved to the Refactorer.Rename
* inner class.
*
* @author James Wright
* @author Iulian Radu
*/
public abstract class Refactorer {
/** Workspace to apply changes to */
private final ModuleContainer moduleContainer;
/** Map from module name to a SourceModifier for that module */
private final Map<ModuleName, SourceModifier> moduleSourceModificationsModifiers = new HashMap<ModuleName, SourceModifier>();
/** Set to true when the changes required to perform this refactoring have been calculated */
private boolean changesCalculated;
/** Listener for updates */
private StatusListener statusListener;
/**
* Interface for listeners wishing to receive status updates during refactoring
* @author Iulian Radu
*/
public static interface StatusListener {
/**
* Refactorer is about to use the specified resource.
* @param resource module to be accessed
*/
public void willUseResource(ModuleSourceDefinition resource);
/**
* Refactorer has finished using the specified resource.
* @param resource module which has been accessed
*/
public void doneUsingResource(ModuleSourceDefinition resource);
}
/**
* Exception which will be logged within a compiler message when the
* refactorer reports an error/warning.
*
* This object identifies the problematic resource, along with further
* information detailing the cause.
*
* @author Iulian Radu
*/
public static final class CALRefactoringException extends Exception {
private static final long serialVersionUID = 3908428565998271064L;
/** Resource scanned when logging exception */
private final ModuleSourceDefinition resource;
/**
* Constructor
* @param resource module causing the problem
* @param description description of error/warning
*/
CALRefactoringException (ModuleSourceDefinition resource, String description) {
super(description, (Throwable)null);
this.resource = resource;
}
/**
* Constructor
* @param resource module causing the problem
* @param description description of error/warning
* @param exception exception detailing the problem
*/
CALRefactoringException (ModuleSourceDefinition resource, String description, Exception exception) {
super(description, exception);
this.resource = resource;
}
@Override
public String toString() {
return getDescription();
}
public String getDescription() {
return getMessage();
}
public ModuleSourceDefinition getResource() {
return resource;
}
}
/**
* Represents the Clean Imports refactoring.
*
* @author James Wright
*/
public static final class CleanImports extends Refactorer {
/** Name of the module to perform the Clean Imports refactoring on */
private final ModuleName moduleName;
/** When this is true, items will not be grouped together or reordered */
private final boolean preserveItems;
/**
* @param moduleContainer Workspace of module to perform the refactoring on
* @param moduleName Name of the module to perform the refactoring on
* @param preserveItems When this is true, items will not be grouped together or reordered
* (although the names within the items will still be reordered).
*/
public CleanImports(ModuleContainer moduleContainer, ModuleName moduleName, boolean preserveItems) {
super(moduleContainer);
if(moduleName == null) {
throw new NullPointerException();
}
this.moduleName = moduleName;
this.preserveItems = preserveItems;
}
/** {@inheritDoc} */
@Override
SourceModifier sourceModificationsForModule(ModuleName moduleName, CompilerMessageLogger messageLogger) {
if(!moduleName.equals(this.moduleName)) {
return null;
}
int nOldErrors = messageLogger.getNErrors();
SourceModifier sourceModifier = ImportCleaner.getSourceModifier_cleanImports(super.moduleContainer, moduleName, super.readModuleText(moduleName, messageLogger), preserveItems, messageLogger);
if (messageLogger.getNErrors() > nOldErrors) {
return null;
}
return sourceModifier;
}
/** {@inheritDoc} */
@Override
public List<ModuleSourceDefinition> calculateModifications(CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("Argument messageLogger cannot be null.");
}
if(!super.moduleContainer.containsModule(moduleName)) {
MessageKind messageKind = new MessageKind.Fatal.ModuleNotInWorkspace(moduleName);
messageLogger.logMessage(new CompilerMessage(messageKind));
return Collections.emptyList();
}
return super.calculateModifications(messageLogger);
}
}
/**
* Represents the Pretty Print refactoring.
*
* @author Magnus Byne
*/
public static final class PrettyPrint extends Refactorer {
/** Name of the module to perform the refactoring on */
private final ModuleName moduleName;
/** The range of code to update the type info in. This may be null */
private final SourceRange rangeToUpdate;
/** a set of functions that should be refactored - may be the mpty set*/
private final Set<String> functionsToUpdate;
/**
* Create a pretty refactorer for a complete module.
*/
public PrettyPrint(ModuleContainer moduleContainer, ModuleName moduleName) {
this(moduleContainer, moduleName, -1, -1,-1,-1, Collections.<String>emptySet());
}
/**
* Create the pretty print refactorer for part of a module, defined by
* a line range or a set of functions.
* If the startLine is <= 0,
* and the function names set is empty, the whole module is refactored.
* Otherwise only lines between startLine and endLine, or functions
* that are in the functionNames set are refactored.
*
* @param moduleContainer
* Workspace of module to perform the refactoring on
* @param moduleName
* Name of the module to perform the refactoring on
* @param startLine
* the first line of range for pretty printing, if this is <
* 0 and the the whole file is included
* @param startColumn
* @param endLine
* @param endColumn
* @param functionNames - set of function names to reformat
*/
public PrettyPrint(ModuleContainer moduleContainer, ModuleName moduleName, final int startLine, final int startColumn, final int endLine, final int endColumn, final Set<String> functionNames) {
super(moduleContainer);
if(moduleName == null) {
throw new NullPointerException();
}
this.moduleName = moduleName;
functionsToUpdate = new HashSet<String>(functionNames);
if (startLine > 0){
final SourcePosition startPosition = new SourcePosition(startLine, startColumn);
final SourcePosition endPosition = new SourcePosition(endLine, endColumn);
rangeToUpdate = new SourceRange(startPosition, endPosition);
}
else{
rangeToUpdate = null;
}
}
/**
* @param moduleContainer Workspace of module to perform the refactoring on
* @param moduleName Name of the module to perform the refactoring on
*/
public PrettyPrint(ModuleContainer moduleContainer, ModuleName moduleName, final String function) {
super(moduleContainer);
if(moduleName == null) {
throw new NullPointerException();
}
this.moduleName = moduleName;
functionsToUpdate = Collections.singleton(function);
rangeToUpdate = null;
}
/**
* Checks to make sure a module is formatted correctly
* @return true iff the formatted string represents the moduleDefn
*/
private boolean isFormattingValid(ModuleDefn moduleDefn, String formatted) {
CompilerMessageLogger mlogger= new MessageLogger();
ArrayList<SourceEmbellishment> embellishments = new ArrayList<SourceEmbellishment>();
//verify the formatted source can parsed
ModuleDefn moduleDefnTest = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(formatted, false, mlogger, embellishments);
if (moduleDefnTest == null) {
return false;
}
if (!General.toPlatformLineSeparators(moduleDefn.toString()).equals(General.toPlatformLineSeparators(moduleDefnTest.toString()))) {
//the original and formatted code are not equal
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
SourceModifier sourceModificationsForModule(ModuleName moduleName, CompilerMessageLogger messageLogger) {
if(!moduleName.equals(this.moduleName)) {
return null;
}
String oldSource = super.readModuleText(moduleName, messageLogger); //super.moduleContainer.getModuleSource(moduleName);
CompilerMessageLogger mlogger= new MessageLogger();
ArrayList<SourceEmbellishment> embellishments = new ArrayList<SourceEmbellishment>();
ModuleDefn moduleDefn = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(oldSource, false, mlogger, embellishments);
if (moduleDefn == null) {
MessageKind messageKind = new MessageKind.Fatal.DebugMessage("PrettyPrint failed: Unable to parse source module.");
messageLogger.logMessage(new CompilerMessage(messageKind));
return null;
}
final PrettyPrintRefactorer ref= new PrettyPrintRefactorer(oldSource, rangeToUpdate, functionsToUpdate);
final SourceModifier modifier = ref.getModifier();
if (modifier == null) {
MessageKind messageKind = new MessageKind.Fatal.DebugMessage("PrettyPrint failed: Internal error.");
messageLogger.logMessage(new CompilerMessage(messageKind));
return null;
}
//verify the formating has not affect the module
if (!isFormattingValid(moduleDefn, modifier.apply(oldSource))) {
MessageKind messageKind = new MessageKind.Fatal.DebugMessage("PrettyPrint failed: Unable to format module. Internal error.");
messageLogger.logMessage(new CompilerMessage(messageKind));
return null;
}
return modifier;
}
}
/**
* Represents the Insert Import refactoring.
*
* @author Greg McClement
*/
public static abstract class InsertImport extends Refactorer {
/** Name of the module to perform the Clean Imports refactoring on */
protected final ModuleName moduleName;
protected final SourcePosition startPosition;
protected SourceModification sourceModification;
/**
* @param moduleContainer Workspace of module to perform the refactoring on
* @param moduleName Name of the module to perform the refactoring on
*/
public InsertImport(ModuleContainer moduleContainer, ModuleName moduleName, int startLine, int startColumn) {
super(moduleContainer);
if(moduleName == null) {
throw new NullPointerException();
}
this.moduleName = moduleName;
this.startPosition = new SourcePosition(startLine, startColumn);
}
/**
* @return the source position that corresponds to the input source position after the file has been modified.
* This is typically used to update the cursor position.
*/
public SourcePosition getNewSourcePosition(){
return sourceModification.getNewSourcePosition();
}
/** {@inheritDoc} */
@Override
public List<ModuleSourceDefinition> calculateModifications(CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("Argument messageLogger cannot be null.");
}
if(!super.moduleContainer.containsModule(moduleName)) {
MessageKind messageKind = new MessageKind.Fatal.ModuleNotInWorkspace(moduleName);
messageLogger.logMessage(new CompilerMessage(messageKind));
return Collections.emptyList();
}
return super.calculateModifications(messageLogger);
}
protected ModuleContainer getModuleContainer(){
return super.moduleContainer;
}
protected String readModuleText(ModuleName moduleName, CompilerMessageLogger messageLogger) {
return super.readModuleText(moduleName, messageLogger);
}
}
/**
* Represents the Insert Import refactoring that inserts an import statement for a given name.
*
* @author Greg McClement
*/
public static final class InsertImport_Symbol extends InsertImport {
/** The name of the symbol to be imported. */
private final QualifiedName importSymbol;
/** The category of the import symbol. This may be null */
private final SourceIdentifier.Category category;
/**
* @param moduleContainer Workspace of module to perform the refactoring on
* @param moduleName Name of the module to perform the refactoring on
*/
public InsertImport_Symbol(ModuleContainer moduleContainer, ModuleName moduleName, QualifiedName importSymbol, final SourceIdentifier.Category category, int startLine, int startColumn) {
super(moduleContainer, moduleName, startLine, startColumn);
this.importSymbol = importSymbol;
this.category = category;
}
/** {@inheritDoc} */
@Override
SourceModifier sourceModificationsForModule(ModuleName moduleName, CompilerMessageLogger messageLogger) {
if(!moduleName.equals(this.moduleName)) {
return null;
}
int nOldErrors = messageLogger.getNErrors();
SourceModifier sourceModifier = ImportCleaner.getSourceModifier_insertImport(
getModuleContainer(),
moduleName,
readModuleText(moduleName, messageLogger),
importSymbol,
category,
false,
messageLogger);
sourceModification = new SourceModification.ReplaceText("", "", startPosition);
sourceModifier.addSourceModification(sourceModification);
if (messageLogger.getNErrors() > nOldErrors) {
return null;
}
return sourceModifier;
}
}
/**
* Represents the Insert Import refactoring that inserts only an import statement with no names.
*
* @author Greg McClement
*/
public static final class InsertImport_Only extends InsertImport {
/** The name of the symbol to be imported. */
private final ModuleName importedModuleName;
/**
* @param moduleContainer Workspace of module to perform the refactoring on
* @param moduleName Name of the module to perform the refactoring on
*/
public InsertImport_Only(ModuleContainer moduleContainer, ModuleName moduleName, ModuleName importedModuleName, int startLine, int startColumn) {
super(moduleContainer, moduleName, startLine, startColumn);
this.importedModuleName = importedModuleName;
}
/** {@inheritDoc} */
@Override
SourceModifier sourceModificationsForModule(ModuleName moduleName, CompilerMessageLogger messageLogger) {
if(!moduleName.equals(this.moduleName)) {
return null;
}
int nOldErrors = messageLogger.getNErrors();
SourceModifier sourceModifier = ImportCleaner.getSourceModifier_insertImportOnly(
getModuleContainer(),
moduleName,
importedModuleName,
readModuleText(moduleName, messageLogger),
false,
messageLogger);
sourceModification = new SourceModification.ReplaceText("", "", startPosition);
sourceModifier.addSourceModification(sourceModification);
if (messageLogger.getNErrors() > nOldErrors) {
return null;
}
return sourceModifier;
}
}
/**
* Represents the Auto-complete refactoring with an insert import for the auto-completed symbol.
*
* @author Greg McClement
*/
public static final class AutoCompleteWithInsertImport extends Refactorer {
/** Name of the module to perform the Clean Imports refactoring on */
private final ModuleName moduleName;
/** When this is true, items will not be grouped together or reordered */
private final QualifiedName importSymbol;
/** The category of the import symbol. This may be null */
private final SourceIdentifier.Category category;
private final SourcePosition startPosition;
private SourceModification sourceModification;
/**
* @param moduleContainer Workspace of module to perform the refactoring on
* @param moduleName Name of the module to perform the refactoring on
*/
public AutoCompleteWithInsertImport(ModuleContainer moduleContainer, ModuleName moduleName, QualifiedName importSymbol, final SourceIdentifier.Category category, String oldText, String newText, int startLine, int startColumn) {
super(moduleContainer);
if(moduleName == null) {
throw new NullPointerException();
}
this.moduleName = moduleName;
this.importSymbol = importSymbol;
this.startPosition = new SourcePosition(startLine, startColumn);
this.category = category;
}
/** {@inheritDoc} */
@Override
SourceModifier sourceModificationsForModule(ModuleName moduleName, CompilerMessageLogger messageLogger) {
if(!moduleName.equals(this.moduleName)) {
return null;
}
boolean ignoreErrors = true;
int nOldErrors = messageLogger.getNErrors();
SourceModifier sourceModifier = ImportCleaner.getSourceModifier_insertImport(
super.moduleContainer,
moduleName,
super.readModuleText(moduleName, messageLogger),
importSymbol,
category,
ignoreErrors,
messageLogger);
sourceModification = new SourceModification.ReplaceText("", "", startPosition);
sourceModifier.addSourceModification(sourceModification);
if (!ignoreErrors && messageLogger.getNErrors() > nOldErrors) {
return null;
}
return sourceModifier;
}
/**
* @return the source position that corresponds to the input source position after the file has been modified.
* This is typically used to update the cursor position.
*/
public SourcePosition getNewSourcePosition(){
return sourceModification.getNewSourcePosition();
}
/** {@inheritDoc} */
@Override
public List<ModuleSourceDefinition> calculateModifications(CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("Argument messageLogger cannot be null.");
}
if(!super.moduleContainer.containsModule(moduleName)) {
MessageKind messageKind = new MessageKind.Fatal.ModuleNotInWorkspace(moduleName);
messageLogger.logMessage(new CompilerMessage(messageKind));
return Collections.emptyList();
}
return super.calculateModifications(messageLogger);
}
}
/**
* Replaces old text with new text
*
* @author Greg McClement
*/
public static final class ReplaceText extends Refactorer {
/** Name of the module to perform the ReplaceText refactoring on */
private final ModuleName moduleName;
private final SourcePosition startPosition;
private final String oldText;
private final String newText;
private SourceModification sourceModification;
/**
* This is used to keep track of the new cursor position after an update.
*/
private SourceModification.CursorPosition cursorPosition = null;
/**
* @param moduleContainer Workspace of module to perform the refactoring on
* @param moduleName Name of the module to perform the refactoring on
*/
public ReplaceText(ModuleContainer moduleContainer, ModuleName moduleName, String oldText, String newText, int startLine, int startColumn) {
super(moduleContainer);
if(moduleName == null) {
throw new NullPointerException();
}
this.moduleName = moduleName;
this.oldText = oldText;
this.newText = newText;
this.startPosition = new SourcePosition(startLine, startColumn);
}
/** {@inheritDoc} */
@Override
SourceModifier sourceModificationsForModule(ModuleName moduleName, CompilerMessageLogger messageLogger) {
if(!moduleName.equals(this.moduleName)) {
return null;
}
boolean ignoreErrors = true;
int nOldErrors = messageLogger.getNErrors();
SourceModifier sourceModifier = new SourceModifier();
sourceModification = new SourceModification.ReplaceText(oldText, newText, startPosition);
sourceModifier.addSourceModification(sourceModification);
if (cursorPosition != null){
sourceModifier.addSourceModification(cursorPosition);
}
if (!ignoreErrors && messageLogger.getNErrors() > nOldErrors) {
return null;
}
return sourceModifier;
}
/**
* If this is set then getNewCursorPosition can be called to get the updated
* position of the cursor after the refactoring.
*/
public void setCursorPosition(int line, int column){
cursorPosition = new SourceModification.CursorPosition(new SourcePosition(line, column));
}
/**
* @return the update position of the cursor as provided by a call to setCursorPosition.
* Calling this function without calling setCursorPosition first is invalid.
*/
public SourcePosition getNewCursorPosition(){
if (cursorPosition == null){
throw new IllegalStateException();
}
return cursorPosition.getNewSourcePosition();
}
/**
* @return the source position that corresponds to the input source position after the file has been modified.
* This is typically used to update the cursor position.
*/
public SourcePosition getNewSourcePosition(){
return sourceModification.getNewSourcePosition();
}
/** {@inheritDoc} */
@Override
public List<ModuleSourceDefinition> calculateModifications(CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("Argument messageLogger cannot be null.");
}
if(!super.moduleContainer.containsModule(moduleName)) {
MessageKind messageKind = new MessageKind.Fatal.ModuleNotInWorkspace(moduleName);
messageLogger.logMessage(new CompilerMessage(messageKind));
return Collections.emptyList();
}
return super.calculateModifications(messageLogger);
}
}
/**
* Represents the Insert Type Declarations refactoring. Type declarations will be inserted
* in front of every top-level function and local definition that doesn't already have one
* (where possible; issues with non-generic type variables can make it impossible to add
* explicit type declarations for certain local definitions).
*
* @author James Wright
*/
public static final class InsertTypeDeclarations extends Refactorer {
/** Name of the module to insert type declarations into */
private final ModuleName moduleName;
/** The range of code to update the type info in. This may be null */
private final SourceRange rangeToUpdate;
/** Statistics about the latest refactoring operation */
private TypeDeclarationInserter.RefactoringStatistics statistics;
/**
* @param moduleContainer Workspace containing module to perform the refactoring on
* @param moduleName String name of the module to perform the refactoring on
* @param startLine The start line of the range to perform the type declaration on. If this is -1 then the entire module will be considered
* @param startColumn The start column of the range to perform the type declaration on. Only used when startLine != -1.
* @param endLine The end line of the range to perform the type declaration on. Only used when startLine != -1.
* @param endColumn The end column of the range to perform the type declaration on. Only used when startLine != -1.
*/
public InsertTypeDeclarations(ModuleContainer moduleContainer, ModuleName moduleName, final int startLine, final int startColumn, final int endLine, final int endColumn) {
super(moduleContainer);
if(moduleName == null) {
throw new NullPointerException();
}
this.moduleName = moduleName;
if (startLine > 0){
final SourcePosition startPosition = new SourcePosition(startLine, startColumn);
final SourcePosition endPosition = new SourcePosition(endLine, endColumn);
rangeToUpdate = new SourceRange(startPosition, endPosition);
}
else{
rangeToUpdate = null;
}
statistics = new TypeDeclarationInserter.RefactoringStatistics();
}
/** {@inheritDoc} */
@Override
SourceModifier sourceModificationsForModule(ModuleName moduleName, CompilerMessageLogger messageLogger) {
if(!moduleName.equals(this.moduleName)) {
return null;
}
statistics = new TypeDeclarationInserter.RefactoringStatistics();
int nOldErrors = messageLogger.getNErrors();
SourceModifier sourceModifier = TypeDeclarationInserter.getSourceModifier(super.moduleContainer, moduleName, rangeToUpdate, super.readModuleText(moduleName, messageLogger), messageLogger, statistics);
if (messageLogger.getNErrors() > nOldErrors) {
return null;
}
return sourceModifier;
}
/** {@inheritDoc} */
@Override
public List<ModuleSourceDefinition> calculateModifications(CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("Argument messageLogger cannot be null.");
}
if(!super.moduleContainer.containsModule(moduleName)) {
MessageKind messageKind = new MessageKind.Fatal.ModuleNotInWorkspace(moduleName);
messageLogger.logMessage(new CompilerMessage(messageKind));
return Collections.emptyList();
}
return super.calculateModifications(messageLogger);
}
/**
* @return Number of type declarations that included at least one type class constraint
* that were added by the refactoring to top-level functions.
*/
public int getTopLevelTypeDeclarationsAddedWithClassConstraints() {
return statistics.getTopLevelTypeDeclarationsAddedWithClassConstraints();
}
/**
* @return Number of type declarations that included no type class constraints
* that were added by the refactoring to top-level functions.
*/
public int getTopLevelTypeDeclarationsAddedWithoutClassConstraints() {
return statistics.getTopLevelTypeDeclarationsAddedWithoutClassConstraints();
}
/**
* @return Number of type declarations that included at least one type class constraint
* that were added by the refactoring to local functions.
*/
public int getLocalTypeDeclarationsAddedWithClassConstraints() {
return statistics.getLocalTypeDeclarationsAddedWithClassConstraints();
}
/**
* @return Number of type declarations that included no type class constraints
* that were added by the refactoring to local functions.
*/
public int getLocalTypeDeclarationsAddedWithoutClassConstraints() {
return statistics.getLocalTypeDeclarationsAddedWithoutClassConstraints();
}
/**
* @return Number of local functions that did not have type declarations where
* type declarations were not added because their types had no syntactic representation
* (eg, types that include non-generic type variables).
*/
public int getLocalTypeDeclarationsNotAdded() {
return statistics.getLocalTypeDeclarationsNotAdded();
}
/**
* @return Number of import declarations that were added by the refactoring.
*/
public int getImportsAdded() {
return statistics.getImportsAdded();
}
/**
* @return Number of type declarations that are not added due to the fact that the module names appearing
* therein would require new import statements that may potential introduce module name resolution conflicts.
*/
public int getTypeDeclarationsNotAddedDueToPotentialImportConflict() {
return statistics.getTypeDeclarationsNotAddedDueToPotentialImportConflict();
}
/**
* @return The set of module names that are not added as imports due to the fact that they may introduce module name
* resolution conflicts.
*/
public SortedSet<ModuleName> getImportsNotAddedDueToPotentialConfict() {
return statistics.getImportsNotAddedDueToPotentialConfict();
}
}
/**
* Represents the Rename refactoring. This functionality used to reside in RenameRefactorer.
* <p>
* Any references found in the refactored resource will be modified to reflect the new name.
* <p>
* References searched include:
* <ul>
* <li>"import using" statements
* <li>function definition name within the container module
* <li>references within class instance declarations
* <li>references within function definitions
* </ul>
* This operation attempts to rename the function entity as specified, and automatically
* resolves conflicts between the entity new name and local variable bindings.
* <p>
* Example:
* <pre>
* Renaming scTest to r in "public scTest x = let r = 1.0; in scTest x"
* produces "public r x = let r2 = 1.0; in r x"
* </pre>
*
* @author James Wright
* @author Iulian Radu
*/
public static final class Rename extends Refactorer {
/**
* The placeholder unqualified name to use for constructing a qualified name when the module renaming
* operation requires a qualified name to stand for the entity (the module) being renamed.
*/
public static final String UNQUALIFIED_NAME_FOR_MODULE_RENAMING = "__moduleRenaming";
/** Typechecker to use for the renaming operations */
private final TypeChecker typeChecker;
/** Name to rename */
private final QualifiedName oldName;
/** Name to replace oldName with */
private final QualifiedName newName;
/** Category of the identifier being renamed */
private final SourceIdentifier.Category category;
/**
* Map (QualifiedName -> CALFeatureName). The main point of caching this information
* is the ability to roll back operations that have caused compilation errors; in
* such a case, we may no longer have access to ModuleTypeInfo that we need.
*/
private final HashMap<QualifiedName, CALFeatureName> featureNameCache = new HashMap<QualifiedName, CALFeatureName>();
/**
* @param moduleContainer CALWorkspace to perform renaming in
* @param typeChecker TypeChecker to use during the renaming
* @param oldName QualifiedName of the identifier to rename
* @param newName QualifiedName of the new identifier name
* @param category Category of oldName and newName
*/
public Rename(ModuleContainer moduleContainer, TypeChecker typeChecker, QualifiedName oldName, QualifiedName newName, SourceIdentifier.Category category) {
super(moduleContainer);
if(typeChecker == null || oldName == null || newName == null || category == null) {
throw new NullPointerException();
}
this.typeChecker = typeChecker;
this.oldName = oldName;
this.newName = newName;
this.category = category;
}
/** {@inheritDoc} */
@Override
public void apply(CompilerMessageLogger logger) {
if (logger == null) {
throw new NullPointerException("Argument logger cannot be null.");
}
int nOldErrors = logger.getNErrors();
updateSources(logger);
if(logger.getNErrors() > nOldErrors) {
return;
}
updateMetadata(logger, oldName, newName);
if(logger.getNErrors() > nOldErrors) {
undoSources(logger);
return;
}
updateResourceNames(logger, oldName, newName);
if(logger.getNErrors() > nOldErrors) {
updateMetadata(logger, newName, oldName);
undoSources(logger);
return;
}
}
/** {@inheritDoc} */
@Override
public void undo(CompilerMessageLogger logger) {
if (logger == null) {
throw new NullPointerException("Argument logger cannot be null.");
}
updateResourceNames(logger, newName, oldName);
updateMetadata(logger, newName, oldName);
undoSources(logger);
}
/**
* Perform the part of the refactoring that involves modifying source files.
* The Refactorer parent class is already set up for source modifications, so
* delegate to its apply method.
* @param logger
*/
private void updateSources(CompilerMessageLogger logger) {
super.apply(logger);
}
/**
* Undo the part of the refactoring that involves modifying source files.
* The Refactorer parent class is already set up for source modifications, so
* delegate to its undo method.
* @param logger
*/
private void undoSources(CompilerMessageLogger logger) {
super.undo(logger);
}
/**
* Perform the part of the refactoring that involves modifying metadata files.
* There is no corresponding undo method; to undo the effects of this call, call it
* again with qualifiedFromName and qualifiedToName reversed. fromName and toName are both
* assumed to be in the current category.
* @param logger
* @param fromName QualifiedName to change from
* @param toName QualifiedName to change to
*/
private void updateMetadata(CompilerMessageLogger logger, QualifiedName fromName, QualifiedName toName) {
Status updateMetadataStatus = new Status("Update metadata status");
MetadataRenameUpdater metadataRenamer = new MetadataRenameUpdater(updateMetadataStatus, typeChecker, toName, fromName, category);
MetadataRenameUpdater.StatusListener refactoringStatusListener = new MetadataRenameUpdater.StatusListener() {
public void willUseResource(WorkspaceResource resource) {}
public void doneUsingResource(WorkspaceResource resource) {
fireDoneUsingResource(((CALFeatureName)resource.getIdentifier().getFeatureName()).toModuleName());
}
};
metadataRenamer.setStatusListener(refactoringStatusListener);
metadataRenamer.updateMetadata(super.moduleContainer);
metadataRenamer.setStatusListener(null);
addStatusMessagesToCompilerMessageLogger(updateMetadataStatus, Status.Severity.ERROR, logger);
}
/**
* Perform the part of the refactoring that involves renaming resources in the workspace.
* There is no corresponding undo method; to undo the effects of this call, call it
* again with qualifiedFromName and qualifiedToName reversed. fromName and toName are both
* assumed to be in the current category.
*
* @param logger
* @param fromName QualifiedName to change from
* @param toName QualifiedName to change to
*/
private void updateResourceNames(CompilerMessageLogger logger, QualifiedName fromName, QualifiedName toName) {
CALFeatureName fromFeatureName = toCALFeatureName(fromName, logger);
CALFeatureName toFeatureName = toCALFeatureName(toName, logger);
if(fromFeatureName == null || toFeatureName == null) {
// toCALFeatureName will have already logged an error for us
return;
}
Status renameResourceStatus = new Status("Rename resource status");
super.moduleContainer.renameFeature(fromFeatureName, toFeatureName, renameResourceStatus);
addStatusMessagesToCompilerMessageLogger(renameResourceStatus, Status.Severity.ERROR, logger);
}
/**
* Adds each message in status of severity at least minimumSeverity to logger.
* @param status Status object to pull messages from
* @param minimumSeverity Minimum severity of messages to copy
* @param logger CompilerMessageLogger to copy messages to
*/
private void addStatusMessagesToCompilerMessageLogger(Status status, Status.Severity minimumSeverity, CompilerMessageLogger logger) {
Status[] msgs = status.getChildren();
for (int i = 0, nMsgs = msgs.length; i < nMsgs; ++i) {
Status.Severity msgSeverity = msgs[i].getSeverity();
if(msgSeverity.compareTo(minimumSeverity) < 0) {
continue;
}
if(msgSeverity == Status.Severity.ERROR) {
logger.logMessage(new CompilerMessage(new MessageKind.Error.DebugMessage(msgs[i].getMessage())));
} else if (msgSeverity == Status.Severity.WARNING) {
logger.logMessage(new CompilerMessage(new MessageKind.Warning.DebugMessage(msgs[i].getMessage())));
} else if (msgSeverity == Status.Severity.INFO) {
logger.logMessage(new CompilerMessage(new MessageKind.Info.DebugMessage(msgs[i].getMessage())));
}
}
}
/** {@inheritDoc} */
@Override
SourceModifier sourceModificationsForModule(ModuleName moduleName, CompilerMessageLogger messageLogger) {
fireWillUseResource(moduleName);
ModuleTypeInfo moduleTypeInfo = super.moduleContainer.getModuleTypeInfo(moduleName);
// No modifications required in modules that can't see the old name (since they
// won't be able to see the new name either)
if(!isOldNameVisible(moduleTypeInfo)) {
return new SourceModifier();
}
String sourceText = super.readModuleText(moduleName, messageLogger);
SourceModifier sourceModifier = IdentifierRenamer.getSourceModifier(moduleTypeInfo, sourceText, oldName, newName, category, messageLogger);
fireDoneUsingResource(moduleName);
if(sourceModifier == null) {
// Module \"{resource.getModuleName()}\" could not be parsed.
ModuleSourceDefinition moduleSourceDefinition = super.moduleContainer.getSourceDefinition(moduleName);
MessageKind message = new MessageKind.Error.ModuleCouldNotBeParsed(moduleName);
messageLogger.logMessage(new CompilerMessage(message, new CALRefactoringException(moduleSourceDefinition, message.getMessage())));
return null;
}
return sourceModifier;
}
/**
* @param moduleTypeInfo
* @return True if the entity identified by oldName and category is visible
* from moduleTypeInfo's module, or false otherwise.
*/
private boolean isOldNameVisible(ModuleTypeInfo moduleTypeInfo) {
if(category == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) {
return moduleTypeInfo.getVisibleFunction(oldName) != null ||
moduleTypeInfo.getVisibleClassMethod(oldName) != null;
} else if(category == SourceIdentifier.Category.DATA_CONSTRUCTOR) {
return moduleTypeInfo.getVisibleDataConstructor(oldName) != null;
} else if(category == SourceIdentifier.Category.TYPE_CONSTRUCTOR) {
return moduleTypeInfo.getVisibleTypeConstructor(oldName) != null;
} else if(category == SourceIdentifier.Category.TYPE_CLASS) {
return moduleTypeInfo.getVisibleTypeClass(oldName) != null;
} else if(category == SourceIdentifier.Category.MODULE_NAME) {
return isOldModuleVisible(moduleTypeInfo);
} else if(category == SourceIdentifier.Category.LOCAL_VARIABLE) {
throw new UnsupportedOperationException();
} else if(category == SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION) {
throw new UnsupportedOperationException();
} else {
throw new IllegalArgumentException("Unrecognized SourceIdentifier category");
}
}
/**
* @param moduleTypeInfo
* @return True if oldName's module is visible from moduleTypeInfo's module, or
* false otherwise.
*/
private boolean isOldModuleVisible(ModuleTypeInfo moduleTypeInfo) {
ModuleName oldModuleName = oldName.getModuleName();
if(moduleTypeInfo.getModuleName().equals(oldModuleName)) {
return true;
}
for(int i = 0, nImports = moduleTypeInfo.getNImportedModules(); i < nImports; i++) {
if(moduleTypeInfo.getNthImportedModule(i).getModuleName().equals(oldModuleName)) {
return true;
}
}
return false;
}
/**
* Converts the given qualified name to a CALFeatureName using the current category.
* @param qualifiedName name to convert
* @param logger CompilerMessageLogger for logging any failures
* @return CALFeatureName of the qualifiedName (in the current category)
*/
private CALFeatureName toCALFeatureName(QualifiedName qualifiedName, CompilerMessageLogger logger) {
CALFeatureName cachedFeatureName = featureNameCache.get(qualifiedName);
if(cachedFeatureName != null) {
return cachedFeatureName;
}
if (category == SourceIdentifier.Category.MODULE_NAME) {
cachedFeatureName = CALFeatureName.getModuleFeatureName(qualifiedName.getModuleName());
} else if (category == SourceIdentifier.Category.DATA_CONSTRUCTOR) {
cachedFeatureName = CALFeatureName.getDataConstructorFeatureName(qualifiedName);
} else if (category == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) {
// We need to determine exactly what kind of gem this is..
// Note that we are looking up the oldName specifically and not the parameter that
// was passed in. That's because the oldName will definitely exist the first time
// we call this function, whereas the newName will not. We're relying here on the
// fact that both the old and new names should have the same feature category.
ModuleName oldModuleName = oldName.getModuleName();
String oldUnqualifiedName = oldName.getUnqualifiedName();
ModuleTypeInfo typeInfo = super.moduleContainer.getModuleTypeInfo(oldModuleName);
if(typeInfo == null) {
logger.logMessage(new CompilerMessage(new MessageKind.Error.ModuleCouldNotBeRead(oldModuleName)));
return null;
}
if (typeInfo.getClassMethod(oldUnqualifiedName) != null) {
// This is a class method
cachedFeatureName = CALFeatureName.getClassMethodFeatureName(qualifiedName);
} else {
// This is a function
cachedFeatureName = CALFeatureName.getFunctionFeatureName(qualifiedName);
}
} else if (category == SourceIdentifier.Category.TYPE_CLASS) {
cachedFeatureName = CALFeatureName.getTypeClassFeatureName(qualifiedName);
} else if (category == SourceIdentifier.Category.TYPE_CONSTRUCTOR) {
cachedFeatureName = CALFeatureName.getTypeConstructorFeatureName(qualifiedName);
} else {
throw new IllegalStateException("Invalid category");
}
featureNameCache.put(qualifiedName, cachedFeatureName);
return cachedFeatureName;
}
}
/**
* Represents the refactoring of renaming a locally-bound name (let-bound functions and local
* pattern match variables, case-bound patterns, lambda-bound parameters, and parameters of
* top-level and local functions).
*
* @see LocalNameRenamer
*
* @author Joseph Wong
*/
public static final class RenameLocalName extends Refactorer {
/**
* The module of the target to be renamed.
*/
private final ModuleName targetModule;
/**
* The target to be renamed.
*/
private final IdentifierInfo.Local oldName;
/**
* The new name for the target.
*/
private final String newName;
/**
* Constructs an instance of this refactorer.
* @param moduleContainer the module container.
* @param targetModule the module of the target to be renamed.
* @param oldName the target to be renamed.
* @param newName the new name for the target.
*/
public RenameLocalName(final ModuleContainer moduleContainer, final ModuleName targetModule, final IdentifierInfo.Local oldName, final String newName) {
super(moduleContainer);
if (targetModule == null || oldName == null || newName == null) {
throw new NullPointerException();
}
this.targetModule = targetModule;
this.oldName = oldName;
this.newName = newName;
}
/**
* {@inheritDoc}
*/
@Override
SourceModifier sourceModificationsForModule(final ModuleName moduleName, final CompilerMessageLogger messageLogger) {
if (moduleName.equals(targetModule)) {
final ModuleTypeInfo moduleTypeInfo = super.moduleContainer.getModuleTypeInfo(moduleName);
final String sourceText = super.readModuleText(moduleName, messageLogger);
if (moduleTypeInfo != null && sourceText != null) {
return LocalNameRenamer.getSourceModifier(moduleTypeInfo, sourceText, oldName, newName, messageLogger);
}
}
return null;
}
}
/**
* Represents the refactoring of renaming a type variable.
*
* @see TypeVariableRenamer
*
* @author Joseph Wong
*/
public static final class RenameTypeVariable extends Refactorer {
/**
* The module of the target to be renamed.
*/
private final ModuleName targetModule;
/**
* The target to be renamed.
*/
private final IdentifierInfo.TypeVariable oldName;
/**
* The new name for the target.
*/
private final String newName;
/**
* Constructs an instance of this refactorer.
* @param moduleContainer the module container.
* @param targetModule the module of the target to be renamed.
* @param oldName the target to be renamed.
* @param newName the new name for the target.
*/
public RenameTypeVariable(final ModuleContainer moduleContainer, final ModuleName targetModule, final IdentifierInfo.TypeVariable oldName, final String newName) {
super(moduleContainer);
if (targetModule == null || oldName == null || newName == null) {
throw new NullPointerException();
}
this.targetModule = targetModule;
this.oldName = oldName;
this.newName = newName;
}
/**
* {@inheritDoc}
*/
@Override
SourceModifier sourceModificationsForModule(final ModuleName moduleName, final CompilerMessageLogger messageLogger) {
if (moduleName.equals(targetModule)) {
final ModuleTypeInfo moduleTypeInfo = super.moduleContainer.getModuleTypeInfo(moduleName);
final String sourceText = super.readModuleText(moduleName, messageLogger);
if (moduleTypeInfo != null && sourceText != null) {
return TypeVariableRenamer.getSourceModifier(moduleTypeInfo, sourceText, oldName, newName, messageLogger);
}
}
return null;
}
}
/**
* Represents the refactoring of renaming a data constructor field.
*
* @see DataConsFieldNameRenamer
*
* @author Joseph Wong
*/
public static final class RenameDataConsFieldName extends Refactorer {
/**
* The target to be renamed.
*/
private final IdentifierInfo.DataConsFieldName oldName;
/**
* The new name for the target.
*/
private final FieldName newName;
/**
* A set of all affected data constructors related to the target via multi-data-constructor
* case alternatives. The value will be set in {@link #calculateModifications}.
*/
private Set<IdentifierInfo.TopLevel.DataCons> affectedDataConsSet;
/**
* A set of the modules in which the data constructor field is visible.
* The value will be set in {@link #calculateModifications}.
*/
private Set<ModuleName> relevantModules;
/**
* The callback to confirm the user's choice.
*/
private final UserChoiceConfirmer confirmer;
/**
* A callback interface for confirming the user's choice during the renaming process.
*
* @author Joseph Wong
*/
public static interface UserChoiceConfirmer {
/**
* Determines whether to proceed with renaming the named field in multiple data constructors
* related to the target via multi-data-constructor case alternatives.
* @param oldName the old field name.
* @param dataConsSet the affected data constructors.
* @return the user's choice: true to proceed; false to cancel.
*/
public boolean shouldRenameMultipleDataConsField(FieldName oldName, SortedSet<QualifiedName> dataConsSet);
/**
* Notifies the user that there is a name collision (the new name already occurs in one of
* the affected data constructors).
* @param newName the colliding name.
*/
public void notifyCollisionOfNewName(FieldName newName);
}
/**
* Constructs an instance of this refactorer.
* @param moduleContainer the module container.
* @param oldName the target to be renamed.
* @param newName the new name for the target.
* @param confirmer the callback to confirm the user's choice.
*/
public RenameDataConsFieldName(final ModuleContainer moduleContainer, final IdentifierInfo.DataConsFieldName oldName, final FieldName newName, final UserChoiceConfirmer confirmer) {
super(moduleContainer);
if (oldName == null || newName == null || confirmer == null) {
throw new NullPointerException();
}
this.oldName = oldName;
this.newName = newName;
this.confirmer = confirmer;
}
/**
* Determines the relevant modules and affected data constructors.
* @param messageLogger the message logger.
* @return the pair of results.
*/
private Pair<Set<ModuleName>, Set<IdentifierInfo.TopLevel.DataCons>> getRelevantModulesAndAffectedDataConsSet(final CompilerMessageLogger messageLogger) {
final Set<ModuleName> relevantModules = new HashSet<ModuleName>();
final Set<IdentifierInfo.TopLevel.DataCons> affectedDataConsSet = new HashSet<IdentifierInfo.TopLevel.DataCons>();
final ModuleName definingModule = oldName.getFirstAssociatedDataConstructor().getResolvedName().getModuleName();
////
/// Optimization: if the type cons defining the associated data constructor(s) has only private data constructors
/// then we only need to process the defining module (because there could not be any references to any of the
/// associated data constructor(s) outside the module).
//
boolean definingTypeConsHasNonPrivateDataCons = false;
final ModuleTypeInfo definingModuleTypeInfo = super.moduleContainer.getModuleTypeInfo(definingModule);
if (definingModuleTypeInfo != null) {
final DataConstructor firstDataCons =
definingModuleTypeInfo.getDataConstructor(oldName.getFirstAssociatedDataConstructor().getResolvedName().getUnqualifiedName());
if (firstDataCons != null) {
final TypeConstructor typeCons = firstDataCons.getTypeConstructor();
final int nDataCons = typeCons.getNDataConstructors();
for (int i = 0; i < nDataCons; i++) {
if (!typeCons.getNthDataConstructor(i).getScope().isPrivate()) {
definingTypeConsHasNonPrivateDataCons = true;
break;
}
}
}
}
final ModuleName[] modulesToCheck;
if (definingTypeConsHasNonPrivateDataCons) {
modulesToCheck = super.moduleContainer.getModuleNames();
} else {
modulesToCheck = new ModuleName[] {definingModule};
}
for (final ModuleName moduleName : modulesToCheck) {
final ModuleTypeInfo moduleTypeInfo = super.moduleContainer.getModuleTypeInfo(moduleName);
final String sourceText = super.readModuleText(moduleName, messageLogger);
if (moduleTypeInfo != null && sourceText != null) {
if (moduleName.equals(definingModule) || moduleTypeInfo.getImportedModule(definingModule) != null) {
// this is either the defining module, or imports it so we check it
// if the module is a sourceless module, then there is not much we can do with it.
if (sourceText.length() == 0) {
continue;
}
final SourceModel.ModuleDefn moduleDefn = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(sourceText, messageLogger);
if (moduleDefn == null) {
continue;
}
relevantModules.add(moduleName);
affectedDataConsSet.addAll(DataConsFieldNameRenamer.getAffectedDataConsSetFromModule(oldName, moduleTypeInfo, moduleDefn));
}
}
}
return Pair.make(relevantModules, affectedDataConsSet);
}
/**
* {@inheritDoc}
*/
@Override
public List<ModuleSourceDefinition> calculateModifications(final CompilerMessageLogger messageLogger) {
Pair<Set<ModuleName>, Set<IdentifierInfo.TopLevel.DataCons>> relevantModulesAndAffectedDataConsSet =
getRelevantModulesAndAffectedDataConsSet(messageLogger);
relevantModules = relevantModulesAndAffectedDataConsSet.fst();
affectedDataConsSet = relevantModulesAndAffectedDataConsSet.snd();
final TreeSet<QualifiedName> dataConsNames = new TreeSet<QualifiedName>();
final Set<FieldName> allSiblingFields = new HashSet<FieldName>();
for (final IdentifierInfo.TopLevel.DataCons dataCons : affectedDataConsSet) {
dataConsNames.add(dataCons.getResolvedName());
final ModuleTypeInfo moduleTypeInfo =
super.moduleContainer.getModuleTypeInfo(dataCons.getResolvedName().getModuleName());
if (moduleTypeInfo != null) {
final DataConstructor dataConsEntity =
moduleTypeInfo.getDataConstructor(dataCons.getResolvedName().getUnqualifiedName());
for (int i = 0, n = dataConsEntity.getArity(); i < n; i++) {
allSiblingFields.add(dataConsEntity.getNthFieldName(i));
}
}
}
final boolean confirmedAffectedDataConsSet;
if (affectedDataConsSet.size() > 1) {
confirmedAffectedDataConsSet = confirmer.shouldRenameMultipleDataConsField(oldName.getFieldName(), dataConsNames);
} else {
confirmedAffectedDataConsSet = true;
}
final boolean proceed;
if (confirmedAffectedDataConsSet) {
if (allSiblingFields.contains(newName)) {
confirmer.notifyCollisionOfNewName(newName);
proceed = false;
} else {
proceed = true;
}
} else {
proceed = false;
}
if (!proceed) {
relevantModules = Collections.emptySet();
}
return super.calculateModifications(messageLogger);
}
/**
* {@inheritDoc}
*/
@Override
SourceModifier sourceModificationsForModule(final ModuleName moduleName, CompilerMessageLogger messageLogger) {
if (relevantModules.contains(moduleName)) {
final ModuleTypeInfo moduleTypeInfo = super.moduleContainer.getModuleTypeInfo(moduleName);
final String sourceText = super.readModuleText(moduleName, messageLogger);
if (moduleTypeInfo != null && sourceText != null) {
return DataConsFieldNameRenamer.getSourceModifier(moduleTypeInfo, sourceText, affectedDataConsSet, oldName.getFieldName(), newName, messageLogger);
}
}
return null;
}
}
private Refactorer(ModuleContainer moduleContainer) {
if(moduleContainer == null) {
throw new NullPointerException();
}
this.moduleContainer = moduleContainer;
changesCalculated = false;
}
/**
* Calculates the modifications that will be required to perform this refactoring.
* Problems encountered during calculation will be logged.
* @param messageLogger CompilerMessageLogger to log failures to. Cannot be null.
* @return List of WorkspaceResources that will be modified by this refactoring
*/
public List<ModuleSourceDefinition> calculateModifications(CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("Argument messageLogger cannot be null.");
}
changesCalculated = true;
List<ModuleSourceDefinition> affectedResources = new ArrayList<ModuleSourceDefinition>();
for(int i = 0, nMetaModules = moduleContainer.getNModules(); i < nMetaModules; i++) {
ModuleName moduleName = moduleContainer.getNthModuleTypeInfo(i).getModuleName();
SourceModifier sourceModifier = sourceModificationsForModule(moduleName, messageLogger);
// If this module requires source modifications, then it is affected
if(sourceModifier != null && sourceModifier.getNSourceModifications() > 0) {
moduleSourceModificationsModifiers.put(moduleName, sourceModifier);
affectedResources.add(moduleContainer.getSourceDefinition(moduleName));
// Check that the source resource is writeable
if(!moduleContainer.getSourceManager(moduleName).isWriteable(moduleName)) {
MessageKind message = new MessageKind.Error.ModuleNotWriteable(moduleName);
messageLogger.logMessage(new CompilerMessage(message));
}
}
}
return affectedResources;
}
/**
* @param moduleName Name of module to check for modifications
* @param messageLogger CompilerMessageLogger to log failures to
* @return A SourceModifier containing modifications for the module, or null if there are no modifications for this module.
*/
abstract SourceModifier sourceModificationsForModule(ModuleName moduleName, CompilerMessageLogger messageLogger);
/**
* Apply the refactoring.
* calculateModifications must be called before this method. An IllegalStateException will be signalled
* if this method is called before calculateModifications.
* @param messageLogger CompilerMessageLogger to log failures to. Cannot be null.
*/
public void apply(CompilerMessageLogger messageLogger) {
if (!changesCalculated) {
throw new IllegalStateException("calculateModifications must be called before apply");
}
if (messageLogger == null) {
throw new NullPointerException("Argument messageLogger cannot be null.");
}
for (final Map.Entry<ModuleName, SourceModifier> entry : moduleSourceModificationsModifiers.entrySet()) {
ModuleName moduleName = entry.getKey();
SourceModifier sourceModifier = entry.getValue();
fireWillUseResource(moduleName);
/**
* If the module can be updated incrementally then do the incremental update.
*/
final ModuleContainer.ISourceManager sourceManager = moduleContainer.getSourceManager(moduleName);
if (sourceManager instanceof ModuleContainer.ISourceManager2
&&
((ISourceManager2) sourceManager).canUpdateIncrementally(moduleName)
){
ModuleContainer.ISourceManager2 sm2 = (ISourceManager2) sourceManager;
sm2.startUpdate(moduleName);
try{
sourceModifier.apply(moduleName, sm2);
}
finally{
sm2.endUpdate(moduleName);
}
}
else{
final String oldText = readModuleText(moduleName, messageLogger);
if(oldText == null) {
continue;
}
String newText = sourceModifier.apply(oldText);
saveModuleText(moduleName, newText, messageLogger);
}
fireDoneUsingResource(moduleName);
}
}
/**
* Undo the previously-applied refactoring.
* calculateModifications must be called before this method. An IllegalStateException will be signalled
* if this method is called before calculateModifications.
* @param messageLogger CompilerMessageLogger to log failures to. Cannot be null.
*/
public void undo(CompilerMessageLogger messageLogger) {
if (!changesCalculated) {
throw new IllegalStateException("calculateModifications (and then apply) must be called before undo");
}
if (messageLogger == null) {
throw new NullPointerException("Argument messageLogger cannot be null.");
}
for (final Map.Entry<ModuleName, SourceModifier> entry : moduleSourceModificationsModifiers.entrySet()) {
ModuleName moduleName = entry.getKey();
SourceModifier sourceModifier = entry.getValue();
fireWillUseResource(moduleName);
String oldText = readModuleText(moduleName, messageLogger);
if(oldText == null) {
continue;
}
String newText = sourceModifier.undo(oldText);
saveModuleText(moduleName, newText, messageLogger);
fireDoneUsingResource(moduleName);
}
}
/**
* Set status listener to use
* @param statusListener
*/
public void setStatusListener(StatusListener statusListener) {
this.statusListener = statusListener;
}
/**
* Call willUseResource on any status listeners with the ModuleSourceDefinition for the
* module named moduleName.
*/
void fireWillUseResource(ModuleName moduleName) {
if(statusListener == null) {
return;
}
ModuleSourceDefinition moduleSourceDefinition = moduleContainer.getSourceDefinition(moduleName);
statusListener.willUseResource(moduleSourceDefinition);
}
/**
* Call doneUsingResource on any status listeners with the ModuleSourceDefinition for the
* module named moduleName.
*/
void fireDoneUsingResource(ModuleName moduleName) {
if(statusListener == null) {
return;
}
ModuleSourceDefinition moduleSourceDefinition = moduleContainer.getSourceDefinition(moduleName);
statusListener.doneUsingResource(moduleSourceDefinition);
}
/**
* Read the text of the specified module.
* @param moduleName Name of the module to read from the workspace
* @param messageLogger CompilerMessageLogger to log failures to
* @return containing the source text of the module, or null on error
*/
private String readModuleText(ModuleName moduleName, CompilerMessageLogger messageLogger) {
final ModuleContainer.ISourceManager sourceManager = moduleContainer.getSourceManager(moduleName);
final String sourceText = sourceManager.getSource(moduleName, messageLogger);
if(sourceText == null) {
return moduleContainer.getModuleSource(moduleName);
}
return sourceText;
}
/**
* Save moduleText to the workspace to be the new source text for the specified module
* @param moduleName Name of the module to save
* @param moduleText String containing the new source text for the module
* @param messageLogger CompilerMessageLogger to log failures to
*/
private void saveModuleText(ModuleName moduleName, String moduleText, CompilerMessageLogger messageLogger) {
ModuleContainer.ISourceManager sourceManager = moduleContainer.getSourceManager(moduleName);
Status saveStatus = new Status("Saving refactored module text");
sourceManager.saveSource(moduleName, moduleText, saveStatus);
if(!saveStatus.isOK()) {
MessageKind messageKind = new MessageKind.Error.ModuleNotWriteable(moduleName);
messageLogger.logMessage(new CompilerMessage(messageKind));
}
}
}