Package org.openquark.cal.compiler

Source Code of org.openquark.cal.compiler.Refactorer$ReplaceText

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

Related Classes of org.openquark.cal.compiler.Refactorer$ReplaceText

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