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