Package org.openquark.cal.compiler

Source Code of org.openquark.cal.compiler.ImportCleaner$ListUpdater

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


/*
* ImportCleaner.java
* Creation date: (Feb 14, 2006)
* By: James Wright
*/
package org.openquark.cal.compiler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.openquark.cal.compiler.SourceModel.Name;
import org.openquark.cal.compiler.SourceModel.Import.UsingItem;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.util.ArrayStack;



/**
* The clean-imports refactoring cleans up import-using clauses by:
* <ol>
*     <li> removing names that are not referenced from using clauses
*     <li> Sorting and nicely formatting using clauses
*     <li> removing extraneous import declarations
* </ol>
* <P> There are ordering issues with the removal of extraneous import declarations.
*  We never remove an import of a module M if it brings instances into scope that
*  are not brought into scope by the other imports.  But the order in which we do
*  the removals can affect the outcome.
<P>Consider the case where we import modules M, N, O, and P.  M and N both have
*  no instances of their own, but they both import Q (and only Q), which does have
*  some instances.  Assume that both M and N are extraneous in the sense that we
*  don't reference any of their symbols.
<P>If we check M first, then it will be removed, because it doesn't add any new
*  instances that aren't provided by the other imports (ie, {N, O, P}).  But then
*  when we check N we will /not/ remove it, because it provides instances that are
*  not provided by the other imports (ie, {O, P}).  So our final import set will be
*  {N, O, P}.
<P>On the other hand, if we check N first, then it will be removed, because it
*  doesn't add any new instances over {M, O, P}.  And M will not be removed,
*  because {O, P} doesn't provide Q's instances.  So the final import set will be
*  {M, O, P}.
<P>We check imports in source order; ie, if import M occurs before import Q in
*  the source text, then import M will be checked for redundancy first.
*
* @author James Wright
*/
final class ImportCleaner {

    private ImportCleaner() {
    }

    /**
     * This class gathers the information from a module's SourceModel that we need in
     * order to perform the refactoring and provides methods to query the resulting
     * summary data.
     *
     * @author James Wright
     */
    private static final class Summarizer extends BindingTrackingSourceModelTraverser<Void> {
       
        /**
         * Map (UsingItem. Category -> Set (String)) from category of name to
         * a set of names that are not bound (eg by let or case expressions)
         * that have been encountered
         */
        private final Map<String, Set<String>> unqualifiedUnboundNames = new HashMap<String, Set<String>>();
       
        /**
         * Set (ModuleName) of names of modules that have been (potentially) referenced
         * in the module being summarized.  The uncertainty arise from CALDoc links,
         * which can contain cons names which may or may not be module names.  We
         * conservatively treat such links as module references.
         */
        private final Set<ModuleName> potentiallyReferencedModules = new HashSet<ModuleName>();

        /**
         * List of import statements in the order that they were encountered
         * during a source traversal.
         */
        private final List<SourceModel.Import> importStatements = new ArrayList<SourceModel.Import>();
       
        /**
         * The module name resolver for the module to be processed.
         */
        private final ModuleNameResolver moduleNameResolver;

        /**
         * @param moduleNameResolver the module name resolver for the module to be processed.
         */
        Summarizer(ModuleNameResolver moduleNameResolver) {
           
            if (moduleNameResolver == null) {
                throw new NullPointerException();
            }
           
            this.moduleNameResolver = moduleNameResolver;
           
            unqualifiedUnboundNames.put(UsingItem.Function.CATEGORY_NAME, new HashSet<String>());
            unqualifiedUnboundNames.put(UsingItem.DataConstructor.CATEGORY_NAME, new HashSet<String>());
            unqualifiedUnboundNames.put(UsingItem.TypeConstructor.CATEGORY_NAME, new HashSet<String>());
            unqualifiedUnboundNames.put(UsingItem.TypeClass.CATEGORY_NAME, new HashSet<String>());
        }
       
        /**
         * Checks whether a name should be added to the list of unbound
         * unqualified names.
         * @param name Name to check
         */
        private void checkName(Name.Qualifiable name) {
           
            String unqualifiedName = name.getUnqualifiedName();
            ModuleName moduleName = SourceModel.Name.Module.maybeToModuleName(name.getModuleName()); // may be null
           
            if(moduleName == null && !isBound(unqualifiedName)) {
                Set<String> nameSet;
                if(name instanceof Name.Function) {
                    nameSet = unqualifiedUnboundNames.get(UsingItem.Function.CATEGORY_NAME);

                } else if(name instanceof Name.DataCons) {
                    nameSet = unqualifiedUnboundNames.get(UsingItem.DataConstructor.CATEGORY_NAME);
               
                } else if(name instanceof Name.TypeCons) {
                    nameSet = unqualifiedUnboundNames.get(UsingItem.TypeConstructor.CATEGORY_NAME);
               
                } else if(name instanceof Name.TypeClass) {
                    nameSet = unqualifiedUnboundNames.get(UsingItem.TypeClass.CATEGORY_NAME);
               
                } else if(name instanceof Name.WithoutContextCons) {
                    // Special case: This might be a type class, type cons, or data cons
                    // It will only be one of them (else it would be ambiguous and therefore uncompilable),
                    // so we can just add the name to all 3 consname sets without having to worry that it
                    // will cause us to retain superfluous names in a using item.

                    nameSet = unqualifiedUnboundNames.get(UsingItem.DataConstructor.CATEGORY_NAME);
                    nameSet.add(unqualifiedName);
                   
                    nameSet = unqualifiedUnboundNames.get(UsingItem.TypeConstructor.CATEGORY_NAME);
                    nameSet.add(unqualifiedName);

                    nameSet = unqualifiedUnboundNames.get(UsingItem.TypeClass.CATEGORY_NAME);
                   
                } else {
                    throw new IllegalStateException("unrecognized Name subclass");
                }
               
                nameSet.add(unqualifiedName);
            }

            if(name instanceof Name.WithoutContextCons) {
                ModuleName resolvedModuleName = moduleNameResolver.resolve(ModuleName.make(name.toSourceText())).getResolvedModuleName();
                potentiallyReferencedModules.add(resolvedModuleName);
            }

            if(moduleName != null) {
                ModuleName resolvedModuleName = moduleNameResolver.resolve(moduleName).getResolvedModuleName();
                potentiallyReferencedModules.add(resolvedModuleName);
            }
        }
       
        /** {@inheritDoc} */
        @Override
        public Void visit_Name_DataCons(Name.DataCons cons, Object arg) {
            checkName(cons);
            return super.visit_Name_DataCons(cons, arg);
        }

        /** {@inheritDoc} */
        @Override
        public Void visit_Name_Function(Name.Function function, Object arg) {
            checkName(function);
            return super.visit_Name_Function(function, arg);
        }

        /** {@inheritDoc} */
        @Override
        public Void visit_Name_TypeClass(Name.TypeClass typeClass, Object arg) {
            checkName(typeClass);
            return super.visit_Name_TypeClass(typeClass, arg);
        }

        /** {@inheritDoc} */
        @Override
        public Void visit_Name_TypeCons(Name.TypeCons cons, Object arg) {
            checkName(cons);
            return super.visit_Name_TypeCons(cons, arg);
        }

        /** {@inheritDoc} */
        @Override
        public Void visit_Name_WithoutContextCons(Name.WithoutContextCons cons, Object arg) {
            checkName(cons);
            return super.visit_Name_WithoutContextCons(cons, arg);
        }
       
        /** {@inheritDoc} */
        @Override
        public Void visit_Import(SourceModel.Import importStmt, Object arg) {

            importStatements.add(importStmt);
            return super.visit_Import(importStmt, arg);
        }

        /**
         * @return A List of Imports that were encountered while walking the model.
         */
        private List<SourceModel.Import> getImportStatements() {
            return Collections.unmodifiableList(importStatements);
        }
       
        /**
         * @param categoryName Name of the category of identifier that we are checking.
         *         This should be the CATEGORY_NAME field of one of the UsingItem subclasses.
         * @param name Name to check for unqualified unbound references
         * @return True if the specified name occurs unqualified and unbound in the module
         *          in the category with the name specified by categoryName.
         */
        private boolean isUnqualifiedUnboundName(String categoryName, String name) {
            Set<String> nameSet = unqualifiedUnboundNames.get(categoryName);
            return nameSet.contains(name);
        }

        /**
         * @return True if the specified module is not directly referenced in this module.
         *          A "direct" reference occurs when a module occurs in a fully-qualified name.
         *          Unqualified references to names imported in a using clause are explicitly
         *          not included.
         *          
         *          False negatives may occur (ie, we may return false for a module that is
         *          in fact unreferenced).  This is because CALDoc links can contain cons
         *          names that may or may not be modules; we conservatively treat all such
         *          names as module names.
         * @param moduleName Name of the module to check
         */
        private boolean isModuleUnreferenced(ModuleName moduleName) {
            return !potentiallyReferencedModules.contains(moduleName);
        }
    }

    /**
     * Helper class for finding instances that are brought into scope by a given set of imports.
     * This class caches results to allow us to check many combinations of imports without having
     * to fully walk the import graph each time.
     *
     * An instance is "brought into scope" by a module if
     *  (a) it is declared in the module, or
     *  (b) it is brought into scope by a module imported by the module
     *
     * @author James Wright
     */
    private static final class InstanceFinder {
       
        /**
         * Map (ModuleName -> Set (String)) from module name to Set of instances brought
         * into scope by importing the module.
         */
        private final Map<ModuleName, Set<String>> moduleInstances = new HashMap<ModuleName, Set<String>>();
       
        private final ModuleContainer moduleContainer;
       
        private InstanceFinder(ModuleContainer workspace) {
            if(workspace == null) {
                throw new NullPointerException();
            }
            this.moduleContainer = workspace;
        }
       
        /**
         * Find all the instances brought into scope by importing rootModule and
         * return a Set of their names.
         * @param moduleName Name of module
         * @return Set (String) containing the names of all the instances that will
         *          be brought into scope by importing rootModule
         */
        private Set<String> findInstancesInScope(ModuleName moduleName) {
           
            // Check the cache before walking the import graph
            if(moduleInstances.containsKey(moduleName)) {
                return moduleInstances.get(moduleName);
            }

            /** (String) */
            Set<String> instanceNames = new HashSet<String>();
           
            ModuleTypeInfo moduleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName);
           
            // Record the instances that this module brings into scope
            // We store Strings instead of the ClassIdentifiers directly so that we can
            // distinguish between instances of the same type and type class that were
            // declared in different modules.  A properly typechecked program will not
            // have two visible instances for the same type and type class, but it never
            // hurts to be certain.
            for(int i = 0, nClassInstances = moduleTypeInfo.getNClassInstances(); i < nClassInstances; i++) {
                ClassInstance classInstance = moduleTypeInfo.getNthClassInstance(i);
                instanceNames.add(moduleName + "|" + classInstance.getNameWithContext());
            }
           
            // Record the instances that this module's imports bring into scope
            for(int i = 0, nImportedModules = moduleTypeInfo.getNImportedModules(); i < nImportedModules; i++) {
                ModuleTypeInfo importedModule = moduleTypeInfo.getNthImportedModule(i);
                ModuleName importedModuleName = importedModule.getModuleName();
                instanceNames.addAll(findInstancesInScope(importedModuleName));
            }

            Set<String> retVal = Collections.unmodifiableSet(instanceNames);
            moduleInstances.put(moduleName, retVal);
            return retVal;
        }
       
        /**
         * Find all the instances that will be brought into scope by importing
         * all the modules in moduleNames.
         * @param moduleNames Set (ModuleName) of module names
         * @return Set (String) of all the instances that will be brought into scope by importing
         *          all the modules in moduleNames.
         */
        private Set<String> findInstancesInScope(Set<ModuleName> moduleNames) {
            Set<String> instanceNames = new HashSet<String>();
            for(final ModuleName moduleName : moduleNames) {             
                instanceNames.addAll(findInstancesInScope(moduleName));
            }
           
            return instanceNames;
        }
    }
   
    /**
     * Comparator that orders data constructor names by typecons name and
     * datacons ordinal.
     *
     * @author James Wright
     */
    private static final class DataConsNameComparator implements Comparator<String> {
        private final ModuleTypeInfo moduleTypeInfo;
        private final CompilerMessageLogger messageLogger;
       
        private DataConsNameComparator(ModuleTypeInfo moduleTypeInfo, CompilerMessageLogger messageLogger) {
            if(moduleTypeInfo == null || messageLogger == null) {
                throw new NullPointerException();
            }
            this.moduleTypeInfo = moduleTypeInfo;
            this.messageLogger = messageLogger;
        }
       
        /** {@inheritDoc} */
        public int compare(String leftName, String rightName) {
                              
            DataConstructor leftDataCons = moduleTypeInfo.getDataConstructor(leftName);
            if(leftDataCons == null) {
                MessageKind messageKind = new MessageKind.Error.DataConstructorDoesNotExist(QualifiedName.make(moduleTypeInfo.getModuleName(), leftName));
                messageLogger.logMessage(new CompilerMessage(messageKind));
                return leftName.compareTo(rightName);
            }

            DataConstructor rightDataCons = moduleTypeInfo.getDataConstructor(rightName);
            if(rightDataCons == null) {
                MessageKind messageKind = new MessageKind.Error.DataConstructorDoesNotExist(QualifiedName.make(moduleTypeInfo.getModuleName(), rightName));
                messageLogger.logMessage(new CompilerMessage(messageKind));
                return leftName.compareTo(rightName);
            }
           
            TypeConsApp leftTypeConsApp = leftDataCons.getTypeConsApp();
            TypeConsApp rightTypeConsApp = rightDataCons.getTypeConsApp();
           
            if(leftTypeConsApp.getName().equals(rightTypeConsApp.getName())) {
                return leftDataCons.getOrdinal() - rightDataCons.getOrdinal();
            } else {
                return leftTypeConsApp.getName().compareTo(rightTypeConsApp.getName());
            }
        }
    }

    /**
     * Used to generalize the getSourceModifier code so that is can be mostly shared for two different purposes.
     *
     * @author GMCCLEMENT
     */
    private interface ListUpdater{
        /**
         * @param moduleName Module name of the current import statement.
         * @return True if the given module imports should be skipped
         */
        public boolean skipModule(ModuleName moduleName);
       
        /**
         * @return the name of the module that the import statement is going to be updated on.
         */
        public ModuleName usingImportStatementFor();
       
        /**
         * Update the newUsingItemList as appropriate.
         */
        public void update(UsingItem[] oldUsingItems, List<UsingItem> newUsingItemsList, Summarizer summarizer, DataConsNameComparator dataconsComparator, ModuleTypeInfo importedModuleTypeInfo);
    }
   
    /**
     * Creates a SourceModifier containing SourceModifications that will perform the
     * clean-imports refactoring on a module.
     *
     * Messages will be logged to messageLogger on error.
     *
     * @param moduleContainer CALWorkspace containing the module to process
     * @param moduleName name of the module to process
     * @param sourceText source text of the module to process
     * @param preserveItems If true, items will not be combined or reordered.
     * @param messageLogger CompilerMessageLogger to log error messages to
     * @return A SourceModifier containing SourceModifications that will perform the
     *          ImportClean refactoring on a module.
     */
    static SourceModifier getSourceModifier_cleanImports(ModuleContainer moduleContainer, ModuleName moduleName, String sourceText, final boolean preserveItems, CompilerMessageLogger messageLogger) {
        ListUpdater updater = new ListUpdater(){
            public boolean skipModule(ModuleName moduleName){
                return false;
            }
           
            public void update(UsingItem[] oldUsingItems, List<UsingItem> newUsingItemsList, Summarizer summarizer, DataConsNameComparator dataconsComparator, ModuleTypeInfo importedModuleTypeInfo){
                if(preserveItems) {
                    for (final UsingItem oldUsingItem : oldUsingItems) {
                        Comparator<String> comparator = (oldUsingItem instanceof UsingItem.DataConstructor) ? dataconsComparator : null;
                        UsingItem newItem = calculateFactoredUsingItem(summarizer, oldUsingItem.getUsingItemCategoryName(), oldUsingItem.getUsingNames(), comparator);
                        if(newItem != null) {
                            newUsingItemsList.add(newItem);
                        }
                    }

                } else {
                    String[] emptyStingArray = new String[0];

                    List<String> functionNames = new ArrayList<String>();
                    List<String> dataConsNames = new ArrayList<String>();
                    List<String> typeConsNames = new ArrayList<String>();
                    List<String> typeClassNames = new ArrayList<String>();

                    Map<String, List<String>> groups = new HashMap<String, List<String>>();
                    groups.put(UsingItem.Function.CATEGORY_NAME, functionNames);
                    groups.put(UsingItem.DataConstructor.CATEGORY_NAME, dataConsNames);
                    groups.put(UsingItem.TypeConstructor.CATEGORY_NAME, typeConsNames);
                    groups.put(UsingItem.TypeClass.CATEGORY_NAME, typeClassNames);

                    for (final UsingItem oldUsingItem : oldUsingItems) {
                        List<String> groupNames = groups.get(oldUsingItem.getUsingItemCategoryName());
                        groupNames.addAll(Arrays.asList(oldUsingItem.getUsingNames()));
                    }

                    UsingItem typeClassUsingItem = calculateFactoredUsingItem(summarizer, UsingItem.TypeClass.CATEGORY_NAME, typeClassNames.toArray(emptyStingArray), null);
                    if(typeClassUsingItem != null) {
                        newUsingItemsList.add(typeClassUsingItem);
                    }

                    UsingItem typeConsUsingItem = calculateFactoredUsingItem(summarizer, UsingItem.TypeConstructor.CATEGORY_NAME, typeConsNames.toArray(emptyStingArray), null);
                    if(typeConsUsingItem != null) {
                        newUsingItemsList.add(typeConsUsingItem);
                    }

                    UsingItem dataConsUsingItem = calculateFactoredUsingItem(summarizer, UsingItem.DataConstructor.CATEGORY_NAME, dataConsNames.toArray(emptyStingArray), dataconsComparator);
                    if(dataConsUsingItem != null) {
                        newUsingItemsList.add(dataConsUsingItem);
                    }

                    UsingItem functionUsingItem = calculateFactoredUsingItem(summarizer, UsingItem.Function.CATEGORY_NAME, functionNames.toArray(emptyStingArray), null);
                    if(functionUsingItem != null) {
                        newUsingItemsList.add(functionUsingItem);
                    }
                }
            }

            public ModuleName usingImportStatementFor() {
                // not used
                return null;
            }
        };

        return getSourceModifier_common(updater, moduleContainer, moduleName, sourceText, false, messageLogger);
    }
   
    /**
     * Returns true if importing moduleName brings additional instances into scope compared with the
     * other imports of startingImports.  In other words, this function returns true if importing
     * startingImports - moduleName will bring a smaller number of instances into scope than importing
     * all of startingImports.
     * @param moduleName name of module to check
     * @param startingImports Set (ModuleName) of module names; it is assumed that this set contains moduleName.
     * @param instanceFinder An InstanceFinder instance to use for computation
     * @return boolean
     */   
    private static boolean bringsNewInstancesIntoScope(ModuleName moduleName, Set<ModuleName> startingImports, InstanceFinder instanceFinder) {
        Set<ModuleName> visitModules = new HashSet<ModuleName>();
       
        visitModules.addAll(startingImports);
        visitModules.remove(moduleName);
        Set<String> importsWithoutTarget = instanceFinder.findInstancesInScope(visitModules);
        Set<String> importsViaTarget = instanceFinder.findInstancesInScope(moduleName);
       
        for (final String instanceName : importsViaTarget) {
           
            if(!importsWithoutTarget.contains(instanceName)) {
                return true;
            }
        }
       
        return false;
    }
   
    /**
     * Calculates a new UsingItem that contains all of the referenced names in oldNames.
     * Returns null if none of the names in oldNames have unqualified references.
     * @param visitor A Summarizer object containing information about references in the current module.
     * @param oldUsingCategoryName Category of the names in oldNames; Should be the CATEGORY_NAME field of
     *         one of the UsingItem subclasses.
     * @param oldNames String array of names to consider including in the new UsingItem
     * @param comparator If non-null, the names added to the new UsingItem will be ordered according
     *                    to this comparator.  If null, the names added to the new UsingItem will be
     *                    ordered alphabetically.
     * @return a UsingItem with category oldUsingCategory that contains all of the names in
     *          oldNames that have at least one unqualified reference in the current module.
     *          Returns null if none of the names in oldNames have an unqualified reference.
     */
    private static UsingItem calculateFactoredUsingItem(Summarizer visitor, String oldUsingCategoryName, String[] oldNames, Comparator<String> comparator) {
        List<String> newNamesList = new ArrayList<String>();
       
        for (final String oldName : oldNames) {
            if(visitor.isUnqualifiedUnboundName(oldUsingCategoryName, oldName)) {
                newNamesList.add(oldName);
            }
        }
       
        if(newNamesList.size() == 0) {
            return null;
        }
       
        String[] newNames = newNamesList.toArray(new String[0]);
        if(comparator != null) {
            Arrays.sort(newNames, comparator);
        } else {
            Arrays.sort(newNames);
        }
       
        if(oldUsingCategoryName.equals(UsingItem.Function.CATEGORY_NAME)) {
            return UsingItem.Function.make(newNames);

        } else if(oldUsingCategoryName.equals(UsingItem.DataConstructor.CATEGORY_NAME)) {
            return UsingItem.DataConstructor.make(newNames);
   
        } else if(oldUsingCategoryName.equals(UsingItem.TypeConstructor.CATEGORY_NAME)) {
            return UsingItem.TypeConstructor.make(newNames);
   
        } else if(oldUsingCategoryName.equals(UsingItem.TypeClass.CATEGORY_NAME)) {
            return UsingItem.TypeClass.make(newNames);
       
        } else {
            throw new IllegalArgumentException("invalid using category name");
        }
    }
   

    /**
     * @param ui The using item to add the name to. This maybe null.
     * @param newName
     */
    private static String[] updateNames(UsingItem ui, String newName){
        if (ui == null){
            String[] newNames = new String[1];
            newNames[0] = newName;
            return newNames;
        }
        else{
            String[] names = ui.getUsingNames();
            // if the name is already in the list then don't add it. This is not 
            // done in the following loop combined because the list might
            // be unalphabetical and that loop bails early.
            for(int i = 0; i < names.length; ++i){
                if (names[i].equals(newName)){
                    // already has the name so why bother
                    return names;
                }
            }
            String[] newNames = new String[names.length+1];
            // insert the name as alphabetically as possible.
            int iNextName = 0;
            // copy all the names that are alphabetically before the new one over to the new array.
            while(iNextName < names.length){
                if (names[iNextName].compareTo(newName) <= 0){
                    newNames[iNextName] = names[iNextName];
                    iNextName++;
                }
                else{
                    break;
                }
            }
            // copy over the new name
            newNames[iNextName] = newName;
            // copy all the names that are alphabetically after the new one over to the new array
            System.arraycopy(names, iNextName, newNames, iNextName+1, names.length - iNextName);
            return newNames;
        }
    }

    /**
     * @param usingItem The using item to add the name to. This maybe be null
     * @param unqualifiedName
     * @param importedModuleTypeInfo
     */
    private static UsingItem maybeGetNewUsingItem(UsingItem usingItem, String unqualifiedName, final SourceIdentifier.Category category, ModuleTypeInfo importedModuleTypeInfo){
        if (
                (category == null || category == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) &&
                importedModuleTypeInfo.getFunction(unqualifiedName) != null ||
                importedModuleTypeInfo.getClassMethod(unqualifiedName) != null
           ){
            if (usingItem == null || usingItem instanceof UsingItem.Function){
                return UsingItem.Function.make(updateNames(usingItem, unqualifiedName));
            }
        }
        else if (
                (category == null || category == SourceIdentifier.Category.DATA_CONSTRUCTOR) &&
                importedModuleTypeInfo.getDataConstructor(unqualifiedName) != null
                ){               
            if (usingItem == null || usingItem instanceof UsingItem.DataConstructor){
                return UsingItem.DataConstructor.make(updateNames(usingItem, unqualifiedName));
            }
        }
        else if (
                (category == null || category == SourceIdentifier.Category.TYPE_CONSTRUCTOR) &&
                importedModuleTypeInfo.getTypeConstructor(unqualifiedName) != null){               
            if (usingItem == null || usingItem instanceof UsingItem.TypeConstructor){
                return UsingItem.TypeConstructor.make(updateNames(usingItem, unqualifiedName));
            }
        }
        else if (
                (category == null || category == SourceIdentifier.Category.TYPE_CLASS) &&
                importedModuleTypeInfo.getTypeClass(unqualifiedName) != null){               
            if (usingItem == null || usingItem instanceof UsingItem.TypeClass){
                return UsingItem.TypeClass.make(updateNames(usingItem, unqualifiedName));
            }
        }
       
        return null; // not applicable
    }           

    /**
     * Insert the given symbol as an import in the current file.
     */
    static SourceModifier getSourceModifier_insertImport(ModuleContainer moduleContainer, ModuleName moduleName, String sourceText, final QualifiedName insertImport, final SourceIdentifier.Category category, final boolean ignoreErrors, CompilerMessageLogger messageLogger) {
        ListUpdater updater = new ListUpdater(){
            public boolean skipModule(ModuleName importedModuleName){
                return !importedModuleName.equals(insertImport.getModuleName());
            }

            public ModuleName usingImportStatementFor() {
                return insertImport.getModuleName();
            }
           
            public void update(UsingItem[] oldUsingItems, List<UsingItem> newUsingItemsList, Summarizer summarizer, DataConsNameComparator dataconsComparator, ModuleTypeInfo importedModuleTypeInfo){
                // Update the newUsingItemsList
                {
                    boolean wasAdded = false;
                    for (final UsingItem oldItem : oldUsingItems) {
                        UsingItem newItem = maybeGetNewUsingItem(oldItem, insertImport.getUnqualifiedName(), category, importedModuleTypeInfo);
                        // if wasAdded then the symbol has already been added to an import statement.
                        // The user has used multiple import statements for the same type so use the old definition.
                        if (wasAdded || newItem == null){
                            newUsingItemsList.add(oldItem);
                        }
                        else{
                            newUsingItemsList.add(newItem);
                            wasAdded = true;
                            // don't add a break here because the remaining unchanged
                            // oldItems must be added to the list
                        }
                    }
                    if (!wasAdded){
                        newUsingItemsList.add(maybeGetNewUsingItem(null, insertImport.getUnqualifiedName(), category, importedModuleTypeInfo));
                    }
                }
            }
        };           
   
        return getSourceModifier_common(updater, moduleContainer, moduleName, sourceText, ignoreErrors, messageLogger);
    }

    private static SourceModifier getSourceModifier_common(ListUpdater updater, ModuleContainer moduleContainer, ModuleName moduleName, String sourceText, final boolean ignoreErrors, CompilerMessageLogger messageLogger) {
        SourceModifier sourceModifier = new SourceModifier();
       
        // if the module is a sourceless module, then there is not much we can do with it.
        if (sourceText.length() == 0) {
            return sourceModifier;
        }
       
        int nErrorsBefore = messageLogger.getNErrors();
        SourceModel.ModuleDefn moduleDefn = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(sourceText, ignoreErrors, messageLogger);
        if (!ignoreErrors && messageLogger.getNErrors() > nErrorsBefore || moduleDefn == null) {
            // We can't proceed if the module is unparseable
            return sourceModifier;
        }
       
        if(!moduleName.equals(SourceModel.Name.Module.toModuleName(moduleDefn.getModuleName()))) {
            throw new IllegalArgumentException("moduleName must correspond to the module name in sourceText");
        }
       
        ModuleTypeInfo moduleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName);
        if(moduleTypeInfo == null) {
            // We can't attempt to process a module that isn't in the workspace
            MessageKind messageKind = new MessageKind.Fatal.ModuleNotInWorkspace(moduleName);
            messageLogger.logMessage(new CompilerMessage(messageKind));
            return sourceModifier;
        }
       
        Summarizer summarizer = new Summarizer(moduleTypeInfo.getModuleNameResolver());
        summarizer.visit_ModuleDefn(moduleDefn, ArrayStack.make());
        List<SourceModel.Import> importList = new ArrayList<SourceModel.Import>(summarizer.getImportStatements());
       
        if(importList.size() == 0) {
            return sourceModifier;
        }
       
        // Set (ModuleName) of imported modules post-refactoring
        Set/*ModuleName*/<ModuleName> unremovedImports = new HashSet<ModuleName>();
        for(int i = 0; i < moduleTypeInfo.getNImportedModules(); i++) {
            unremovedImports.add(moduleTypeInfo.getNthImportedModule(i).getModuleName());
        }
       
        InstanceFinder instanceFinder = new InstanceFinder(moduleContainer);
        SourcePosition previousPosition = new SourcePosition(1, 1);
        int previousIndex = 0;

        // The import statement for the module that the
        // symbols are imported from might not be present
        // so we have to add it. This code figure out if one
        // is missing and adds it.
        SourceModel.Import newImportStatement = null;
        {
            boolean foundImportStatement = false;
            SourcePosition positionOfLastImportStatement = null;
            // there has to be at least one import statement since the module must import Prelude.
            for (final SourceModel.Import importStmt : importList) {
               
                final SourcePosition importStmtSourcePosition = importStmt.getSourceRange().getEndSourcePosition().offsetPositionByText("\n\n");
                if (positionOfLastImportStatement == null){
                    positionOfLastImportStatement = importStmtSourcePosition;
                }
                if (SourcePosition.compareByPosition.compare(importStmtSourcePosition, positionOfLastImportStatement) > 0){
                    positionOfLastImportStatement = importStmtSourcePosition;
                }
                ModuleName importedModuleName = SourceModel.Name.Module.toModuleName(importStmt.getImportedModuleName());

                if (updater.skipModule(importedModuleName)){
                    continue;
                }
                foundImportStatement = true;
                break;
            }

            if (!foundImportStatement){
                newImportStatement = SourceModel.Import.makeAnnotated(Name.Module.make(updater.usingImportStatementFor()), new UsingItem[0], new SourceRange(positionOfLastImportStatement, positionOfLastImportStatement));
                importList.add(newImportStatement);
            }
        }

        for (final SourceModel.Import importStmt : importList) {
           
            ModuleName importedModuleName = SourceModel.Name.Module.toModuleName(importStmt.getImportedModuleName());

            if (updater.skipModule(importedModuleName)){
                continue;
            }

            ModuleTypeInfo importedModuleTypeInfo = moduleContainer.getModuleTypeInfo(importedModuleName);
            if(importedModuleTypeInfo == null) {
                // We can't attempt to process a module that isn't in the workspace
                MessageKind messageKind = new MessageKind.Fatal.ModuleNotInWorkspace(importedModuleName);
                messageLogger.logMessage(new CompilerMessage(messageKind));
                sourceModifier.clearAllModifications();
                return sourceModifier;
            }
            DataConsNameComparator dataconsComparator = new DataConsNameComparator(importedModuleTypeInfo, messageLogger);
           
            // Calculate a new import statement
            UsingItem[] oldUsingItems = importStmt.getUsingItems();
            List<UsingItem> newUsingItemsList = new ArrayList<UsingItem>();

            updater.update(oldUsingItems, newUsingItemsList, summarizer, dataconsComparator, importedModuleTypeInfo);
           
            // If the calculated import statement doesn't have any using clauses, and
            // we don't reference any symbols from it, and it doesn't bring any new
            // instance declarations into scope, then remove the import entirely.
            if(newUsingItemsList.size() == 0 &&
               summarizer.isModuleUnreferenced(importedModuleName) &&
               !importedModuleName.equals(CAL_Prelude.MODULE_NAME) &&
               !bringsNewInstancesIntoScope(importedModuleName, unremovedImports, instanceFinder)) {
               
                unremovedImports.remove(importedModuleName);
               
                SourcePosition startPosition = importStmt.getSourceRange().getStartSourcePosition();
                int startIndex = startPosition.getPosition(sourceText, previousPosition, previousIndex);
                SourcePosition endPosition = importStmt.getSourceRange().getEndSourcePosition();
                int endIndex = endPosition.getPosition(sourceText, startPosition, startIndex);
               
                sourceModifier.addSourceModification(new SourceModification.RemoveText(sourceText.substring(startIndex, endIndex), startPosition));
                previousPosition = endPosition;
                previousIndex = endIndex;

            // If the original import statement had no using clauses, then don't bother
            // regenerating it (since there are no using clauses to tidy up).
            } else if(oldUsingItems.length == 0 && newUsingItemsList.size() == 0) {
                continue;
           
            // Otherwise record a change that emits the new import statement
            } else {           
                UsingItem[] newUsingItems = newUsingItemsList.toArray(new UsingItem[0]);

                if (importStmt == newImportStatement){
                    final SourceModel.Import newImportStmt = SourceModel.Import.make(importedModuleName, newUsingItems);
                    SourcePosition startPosition = importStmt.getSourcePosition();
                    sourceModifier.addSourceModification(
                            new SourceModification.ReplaceText("", newImportStmt.toSourceText() + "\n\n", startPosition));
                }
                else{
                    SourcePosition startPosition = importStmt.getSourceRange().getStartSourcePosition();
                    int startIndex = startPosition.getPosition(sourceText, previousPosition, previousIndex);
                    SourcePosition endPosition = importStmt.getSourceRange().getEndSourcePosition();
                    int endIndex = endPosition.getPosition(sourceText, startPosition, startIndex);

                    SourceModel.Import newImportStmt = SourceModel.Import.make(importedModuleName, newUsingItems);
                    sourceModifier.addSourceModification(new SourceModification.ReplaceText(sourceText.substring(startIndex, endIndex), newImportStmt.toSourceText(), startPosition));

                    previousPosition = endPosition;
                    previousIndex = endIndex;
                }
            }
        }
   
        return sourceModifier;
    }

    /**
     * This function creates a source modifier that will insert an import of the given module name.
     */
    public static SourceModifier getSourceModifier_insertImportOnly(ModuleContainer moduleContainer, ModuleName moduleName, ModuleName moduleToImport, String sourceText, final boolean ignoreErrors, CompilerMessageLogger messageLogger) {
        SourceModifier sourceModifier = new SourceModifier();
       
        // if the module is a sourceless module, then there is not much we can do with it.
        if (sourceText.length() == 0) {
            return sourceModifier;
        }
       
        int nErrorsBefore = messageLogger.getNErrors();
        SourceModel.ModuleDefn moduleDefn = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(sourceText, ignoreErrors, messageLogger);
        if (!ignoreErrors && messageLogger.getNErrors() > nErrorsBefore || moduleDefn == null) {
            // We can't proceed if the module is unparseable
            return sourceModifier;
        }
       
        if(!moduleName.equals(SourceModel.Name.Module.toModuleName(moduleDefn.getModuleName()))) {
            throw new IllegalArgumentException("moduleName must correspond to the module name in sourceText");
        }
       
        ModuleTypeInfo moduleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName);
        if(moduleTypeInfo == null) {
            // We can't attempt to process a module that isn't in the workspace
            MessageKind messageKind = new MessageKind.Fatal.ModuleNotInWorkspace(moduleName);
            messageLogger.logMessage(new CompilerMessage(messageKind));
            return sourceModifier;
        }
       
        Summarizer summarizer = new Summarizer(moduleTypeInfo.getModuleNameResolver());
        summarizer.visit_ModuleDefn(moduleDefn, ArrayStack.make());
        List<SourceModel.Import> importList = new ArrayList<SourceModel.Import>(summarizer.getImportStatements());
       
        if(importList.size() == 0) {
            return sourceModifier;
        }
       
        // Set (ModuleName) of imported modules post-refactoring
        Set/*ModuleName*/<ModuleName> unremovedImports = new HashSet<ModuleName>();
        for(int i = 0; i < moduleTypeInfo.getNImportedModules(); i++) {
            unremovedImports.add(moduleTypeInfo.getNthImportedModule(i).getModuleName());
        }
       
        // The import statement for the module that the
        // symbols are imported from might not be present
        // so we have to add it. This code figure out if one
        // is missing and adds it.
        SourceModel.Import newImportStatement = null;
        {
            SourcePosition positionOfLastImportStatement = null;
            // If there already is an import statement then
            for (final SourceModel.Import importStmt : importList) {               
                final SourcePosition importStmtSourcePosition = importStmt.getSourceRange().getEndSourcePosition().offsetPositionByText("\n\n");
                if (positionOfLastImportStatement == null){
                    positionOfLastImportStatement = importStmtSourcePosition;
                }
                if (SourcePosition.compareByPosition.compare(importStmtSourcePosition, positionOfLastImportStatement) > 0){
                    positionOfLastImportStatement = importStmtSourcePosition;
                }
                ModuleName currentModuleName = SourceModel.Name.Module.toModuleName(importStmt.getImportedModuleName());
                if (moduleToImport.equals(currentModuleName)){
                    return sourceModifier;
                }
            }

            newImportStatement = SourceModel.Import.makeAnnotated(Name.Module.make(moduleToImport), new UsingItem[0], new SourceRange(positionOfLastImportStatement, positionOfLastImportStatement));
            importList.add(newImportStatement);
        }

        {
            ModuleTypeInfo importedModuleTypeInfo = moduleContainer.getModuleTypeInfo(moduleToImport);
            if(importedModuleTypeInfo == null) {
                // We can't attempt to process a module that isn't in the workspace
                MessageKind messageKind = new MessageKind.Fatal.ModuleNotInWorkspace(moduleToImport);
                messageLogger.logMessage(new CompilerMessage(messageKind));
                sourceModifier.clearAllModifications();
                return sourceModifier;
            }
            // Calculate a new import statement
            List<UsingItem> newUsingItemsList = new ArrayList<UsingItem>();

            // Otherwise record a change that emits the new import statement
            {           
                UsingItem[] newUsingItems = newUsingItemsList.toArray(new UsingItem[0]);

                final SourceModel.Import newImportStmt = SourceModel.Import.make(moduleToImport, newUsingItems);
                SourcePosition startPosition = newImportStatement.getSourcePosition();
                sourceModifier.addSourceModification(
                        new SourceModification.ReplaceText("", newImportStmt.toSourceText() + "\n\n", startPosition));
            }
        }
   
        return sourceModifier;
    }
   
}
TOP

Related Classes of org.openquark.cal.compiler.ImportCleaner$ListUpdater

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.
v>