/*
* 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.
*/
/*
* ModuleNameResolver.java
* Creation date: Nov 6, 2006.
* By: Joseph Wong
*/
package org.openquark.cal.compiler;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* This immutable class encapsulates logic related to resolving module names that appear in CAL source.
* CAL supports the use of hierarchical module names (i.e. names containing one of more dots),
* and allows the use of partially qualified module names where they are unambiguous.
* A partially qualified module name is a proper suffix of a fully qualified module name.
* For example, B.C and C are partially qualified versions of the module name A.B.C .
*
* <p>
* Suppose we have a module:
* <pre>
* module W.X.Y.Z;
*
* import Y.Z;
* import Z;
* import A.B.C.D.E;
* import P.C.D.E;
* import D.E;
* </pre>
*
* <p>
* Here are the set of name resolutions:
* <table>
* <tr><td> <b>Module Name</b> <td> <b>Resolves to...</b>
* <tr><td> Z <td> Z
* <tr><td> Y.Z <td> Y.Z
* <tr><td> X.Y.Z <td> W.X.Y.Z (the current module)
* <tr><td> W.X.Y.Z <td> W.X.Y.Z
* <tr><td> E <td> Ambiguous (A.B.C.D.E, P.C.D.E, D.E)
* <tr><td> D.E <td> D.E
* <tr><td> C.D.E <td> Ambiguous (A.B.C.D.E, P.C.D.E)
* <tr><td> B.C.D.E <td> A.B.C.D.E
* <tr><td> P.C.D.E <td> P.C.D.E
* <tr><td> A.B.C.D.E <td> A.B.C.D.E
* </table>
*
* <p>
* The salient points from the example above are:
* <ul>
* <li> The fully qualified name of a module always resolves to that module.
* <li> In the case of C.D.E, no preference is given to either A.B.C.D.E or P.C.D.E -- it is considered ambiguous.
* <li> Neither Z nor Y.Z resolves to the current module.
* <li> Adding a "qualifier" to the front of a resolvable name may make it ambiguous (e.g. D.E -> C.D.E)
* </ul>
*
* <p>
* Each resolvable module name also has a <em>minimally qualified</em> form, i.e. the shortest module name
* that also resolves to the module unambiguously. For example:
* <table>
* <tr><td> <b>Fully Qualified Module Name</b> <td> <b>Minimally Qualified Form</b>
* <tr><td> Z <td> Z
* <tr><td> Y.Z <td> Y.Z
* <tr><td> W.X.Y.Z <td> X.Y.Z
* <tr><td> D.E <td> D.E
* <tr><td> P.C.D.E <td> P.C.D.E
* <tr><td> A.B.C.D.E <td> B.C.D.E
* </table>
*
* @author Joseph Wong
*/
public final class ModuleNameResolver {
/**
* A map mapping each known module name (partially or fully qualified) to its resolution (which
* may be ambiguous or unambiguous).
*/
private final Map<ModuleName, ResolutionResult> resolutions;
/**
* A map mapping each known fully qualified module name to its minimally qualified form.
* This map does not contain mappings for resolvable partially qualified module names.
*/
private final Map<ModuleName, ModuleName> minimallyQualifiedModuleNames;
/**
* This immutable class encapsulates the result of a module name resolution.
* A module name may be:
* <ul>
* <li><em>unambiguous</em> - it resolves to exactly one known module
* <li><em>ambiguous</em> - it is a proper suffix to two or more module names
* <li><em>unknown</em> - it is not resolvable to any of the known modules
* </ul>
*
* Instances of this class class are meant to be constructed only by the {@link ModuleNameResolver}.
*
* @author Joseph Wong
*/
public static final class ResolutionResult {
/**
* The original module name to be resolved.
*/
private final ModuleName originalModuleName;
/**
* The name of the module the original name resolves to. If the original name is not unambiguously
* resolvable, then this holds the original name.
*/
private final ModuleName resolvedModuleName;
/**
* The status of the resolution: unambiguous, ambiguous or unknown.
*/
private final Status status;
/**
* The fully qualified names of modules that could potentially be referred to by the original name.
* If the status is unambiguous, this array should contain just one name. If the status is
* ambiguous, this array should contain more than one name. If the status is unknown, this array
* should be empty.
*/
private final ModuleName[] potentialMatches;
/**
* An enumerated type for the possible status of a resolution.
*
* @author Joseph Wong
*/
private static final class Status {
/** Represents an unknown module name. */
private static final Status UNKNOWN = new Status("unknown");
/** Represents an ambiguous module name. */
private static final Status AMBIGUOUS = new Status("ambiguous");
/** Represents an unambiguous module name. */
private static final Status UNAMBIGUOUS = new Status("unambiguous");
/** A description of the status. */
private final String description;
/**
* Private constructor for the enumerated type.
* @param description a description of the status.
*/
private Status(final String description) {
if (description == null) {
throw new NullPointerException();
}
this.description = description;
}
/** {@inheritDoc} */
@Override
public String toString() {
return description;
}
}
/**
* Private constructor for use by the {@link ModuleNameResolver}.
*
* @param originalModuleName
* the original module name to be resolved. Can be null.
* @param resolvedModuleName
* the name of the module the original name resolves to. If the original name
* is not unambiguously resolvable, then this holds the original name. Can be null if originalModuleName is null.
* @param status
* the status of the resolution: unambiguous, ambiguous or unknown.
* @param potentialMatches
* the fully qualified names of modules that could potentially be referred to
* by the original name. If the status is unambiguous, this array should
* contain just one name. If the status is ambiguous, this array should
* contain more than one name. If the status is unknown, this array should
* be empty.
*/
private ResolutionResult(final ModuleName originalModuleName, final ModuleName resolvedModuleName, final Status status, final Set<ModuleName> potentialMatches) {
// resolvedModuleName can only be null if originalModuleName is also null
if (originalModuleName != null && resolvedModuleName == null) {
throw new NullPointerException();
}
if (status == null || potentialMatches == null) {
throw new NullPointerException();
}
if (status != Status.UNAMBIGUOUS) {
if (!areMaybeModuleNamesEqual(originalModuleName, resolvedModuleName)) {
throw new IllegalArgumentException("if the original name is ambiguous or unknown, then the resolved name should be the original name");
}
}
this.originalModuleName = originalModuleName;
this.resolvedModuleName = resolvedModuleName;
this.status = status;
this.potentialMatches = potentialMatches.toArray(new ModuleName[potentialMatches.size()]);
}
/**
* @return the original module name to be resolved. Can be null.
*/
public ModuleName getOriginalModuleName() {
return originalModuleName;
}
/**
* @return the name of the module the original name resolves to. If the original name
* is not unambiguously resolvable, then the original name is returned.
* Can be null if {@link #getOriginalModuleName()} returns null.
*/
public ModuleName getResolvedModuleName() {
return resolvedModuleName;
}
/**
* @return true if the original module name is not resolvable to any of the known modules; false otherwise.
*/
public boolean isUnknown() {
return status == Status.UNKNOWN;
}
/**
* @return true if the original module name is a proper suffix to two or more module names; false otherwise.
*/
public boolean isAmbiguous() {
return status == Status.AMBIGUOUS;
}
/**
* @return true if the original module name resolves to exactly one known module; false otherwise.
*/
public boolean isKnownUnambiguous() {
return status == Status.UNAMBIGUOUS;
}
/**
* @return the fully qualified names of modules that could potentially be referred to
* by the original name. If the status is unambiguous, this array should
* contain just one name. If the status is ambiguous, this array should
* contain more than one name. If the status is unknown, this array should be empty.
*/
public ModuleName[] getPotentialMatches() {
return potentialMatches.clone();
}
/**
* @return true if <code>getOriginalModuleName().equals(getResolvedModuleName())</code>; false otherwise.
* In particular, the returned value does not take into account whether the original name is unambiguously
* resolvable or not.
*/
public boolean isResolvedModuleNameEqualToOriginalModuleName() {
return areMaybeModuleNamesEqual(originalModuleName, resolvedModuleName);
}
/**
* Checks the equality of the 2 arguments, which may be null.
* @param maybeModuleName1 a module name, or null.
* @param maybeModuleName2 a module name, or null.
* @return true if the names are equal or if they are both null, false otherwise.
*/
private static boolean areMaybeModuleNamesEqual(final ModuleName maybeModuleName1, final ModuleName maybeModuleName2) {
if (maybeModuleName1 == null) {
return maybeModuleName2 == null;
} else {
return maybeModuleName1.equals(maybeModuleName2);
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return
"[Original: " + getOriginalModuleName() +
", Resolved: " + getResolvedModuleName() +
", Status: " + status +
", Potential matches: " + Arrays.asList(potentialMatches) + "]";
}
}
/**
* Warning- this class should only be used by the CAL compiler implementation. It is not part of the
* external API of the CAL platform.
* <P>
* This class encapsulates a mapping which maps module names that are affected by a module renaming to
* their corresponding disambiguated names.
*
* @author Joseph Wong
*/
static final class RenameMapping {
/**
* A map which maps module names that are affected by a module renaming to
* their corresponding disambiguated names.
*/
private final Map<ModuleName, ModuleName> oldToNewMapping;
/**
* Private constructor for use by the {@link ModuleNameResolver}.
*
* @param oldToNewMapping
* a map which maps module names that are affected by a module renaming to their corresponding
* disambiguated names.
*/
private RenameMapping(final Map<ModuleName, ModuleName> oldToNewMapping) {
if (oldToNewMapping == null) {
throw new NullPointerException();
}
this.oldToNewMapping = oldToNewMapping;
}
/**
* Returns the new name for the given module. Maps null to null.
* @param oldName a module name.
* @return the new name for the module. Maps null to null.
*/
ModuleName getNewNameForModule(final ModuleName oldName) {
if (oldName == null) {
return null;
}
final ModuleName newName = oldToNewMapping.get(oldName);
if (newName == null) {
return oldName;
} else {
return newName;
}
}
/**
* Returns whether the given module is mapped to a new name under this mapping.
* @param oldName a module name.
* @return true if the given module is mapped to a new name under this mapping; false otherwise.
*/
boolean hasNewName(final ModuleName oldName) {
if (oldName == null) {
return false;
}
return oldToNewMapping.containsKey(oldName);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "[RenameMapping (old module name -> new module name): " + oldToNewMapping.toString() + "]";
}
}
/**
* Private constructor. This class should be instantiated via the factory methods.
*
* @param resolutions
* a map mapping each known module name (partially or fully qualified) to its resolution
* (which may be ambiguous or unambiguous).
* @param minimallyQualifiedModuleNames
* a map mapping each known fully qualified module name to its minimally qualified form.
* This map does not contain mappings for resolvable partially qualified module names.
*/
private ModuleNameResolver(final Map<ModuleName, ResolutionResult> resolutions, final Map<ModuleName, ModuleName> minimallyQualifiedModuleNames) {
if (resolutions == null || minimallyQualifiedModuleNames == null) {
throw new NullPointerException();
}
this.resolutions = resolutions;
this.minimallyQualifiedModuleNames = minimallyQualifiedModuleNames;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return
"[ModuleNameResolver:\n resolutions: " + resolutions.toString() +
"\n minimallyQualifiedModuleNames: " + minimallyQualifiedModuleNames.toString() + "\n]";
}
/**
* Creates a module name resolver for a given module and its imports.
*
* @param currentModuleName the name of the module for which the resolver is to be created.
* @param importedModuleNames the set of names of another modules imported by the module.
* @return a new instance of this class.
*/
public static ModuleNameResolver make(final ModuleName currentModuleName, final Set<ModuleName> importedModuleNames) {
final Set<ModuleName> visibleModuleNames = new HashSet<ModuleName>(importedModuleNames);
visibleModuleNames.add(currentModuleName);
return make(visibleModuleNames);
}
/**
* Creates a module name resolver from a given set of fully qualified module names.
* @param visibleModuleNames the set of fully qualified module names.
* @return a new instance of this class.
*/
public static ModuleNameResolver make(final Set<ModuleName> visibleModuleNames) {
////
/// First we obtain, for each partial module name constructible from the set of visible module names,
/// a set of potential matches for that partial module name.
//
// For example, given the module declaration:
//
// module W.X.Y.Z;
// import Y.Z;
// import Z;
// import A.B.C.D.E;
// import P.C.D.E;
// import D.E;
//
// we build up the following map:
//
// Z -> {Z}
// Y.Z -> {Y.Z}
// X.Y.Z -> {W.X.Y.Z}
// W.X.Y.Z -> {W.X.Y.Z}
// E -> {A.B.C.D.E, P.C.D.E, D.E}
// D.E -> {D.E}
// C.D.E -> {A.B.C.D.E, P.C.D.E}
// B.C.D.E -> {A.B.C.D.E}
// P.C.D.E -> {P.C.D.E}
// A.B.C.D.E -> {A.B.C.D.E}
final Map<ModuleName, Set<ModuleName>> partialModuleNameToPotentialMatches = new HashMap<ModuleName, Set<ModuleName>>();
for (final ModuleName moduleName : visibleModuleNames) {
String partialModuleNameString = "";
final int lastComponentIndex = moduleName.getNComponents() - 1;
for (int i = lastComponentIndex; i >= 0; i--) {
if (i == lastComponentIndex) {
partialModuleNameString = moduleName.getNthComponent(i);
} else {
partialModuleNameString = moduleName.getNthComponent(i) + '.' + partialModuleNameString;
}
final ModuleName partialModuleName = ModuleName.make(partialModuleNameString);
Set<ModuleName> potentialMatches = partialModuleNameToPotentialMatches.get(partialModuleName);
if (potentialMatches == null) {
potentialMatches = new HashSet<ModuleName>();
partialModuleNameToPotentialMatches.put(partialModuleName, potentialMatches);
}
potentialMatches.add(moduleName);
}
}
////
/// We now turn the mapping into ResolutionResults (which may be ambiguous or not, depending on whether there
/// is just one match or more than one.)
//
final Map<ModuleName, ResolutionResult> resolutions = new HashMap<ModuleName, ResolutionResult>();
for (final Map.Entry<ModuleName, Set<ModuleName>> entry : partialModuleNameToPotentialMatches.entrySet()) {
final ModuleName partialModuleName = entry.getKey();
final Set<ModuleName> potentialMatches = entry.getValue();
final int nPotentialMatches = potentialMatches.size();
ResolutionResult resolution;
if (nPotentialMatches > 1) {
if (potentialMatches.contains(partialModuleName)) {
// the name could resolve to itself, therefore the name is a fully-qualified module name, and is thus not ambiguous
resolution = new ResolutionResult(partialModuleName, partialModuleName, ResolutionResult.Status.UNAMBIGUOUS, potentialMatches);
} else {
// the name is ambiguous
resolution = new ResolutionResult(partialModuleName, partialModuleName, ResolutionResult.Status.AMBIGUOUS, potentialMatches);
}
} else if (nPotentialMatches == 1) {
final ModuleName match = potentialMatches.iterator().next();
resolution = new ResolutionResult(partialModuleName, match, ResolutionResult.Status.UNAMBIGUOUS, potentialMatches);
} else {
throw new IllegalStateException();
}
resolutions.put(partialModuleName, resolution);
}
////
/// Finally, from the ResolutionResults we build a map from fully-qualified module names to their minimally-qualified forms
//
final Map<ModuleName, ModuleName> minimallyQualifiedModuleNames = new HashMap<ModuleName, ModuleName>();
for (final ResolutionResult resolution : resolutions.values()) {
if (resolution.isKnownUnambiguous()) {
final ModuleName potentialMinimalName = resolution.getOriginalModuleName();
final ModuleName fullyQualifiedName = resolution.getResolvedModuleName();
final ModuleName minimalName = minimallyQualifiedModuleNames.get(fullyQualifiedName);
if (minimalName == null || potentialMinimalName.getNComponents() < minimalName.getNComponents()) {
minimallyQualifiedModuleNames.put(fullyQualifiedName, potentialMinimalName);
}
}
}
return new ModuleNameResolver(resolutions, minimallyQualifiedModuleNames);
}
/**
* Resolves the given module name and returns a ResolutionResult.
* @param moduleName the module name to be resolved. Can be null.
* @return a ResolutionResult representing the result of the resolution.
*/
public ResolutionResult resolve(final ModuleName moduleName) {
final ResolutionResult result = resolutions.get(moduleName);
if (result == null) {
return new ResolutionResult(moduleName, moduleName, ResolutionResult.Status.UNKNOWN, Collections.<ModuleName>emptySet());
} else {
return result;
}
}
/**
* Returns the minimally qualified form of the given module name.
* @param moduleName the module name, which may be fully qualified or partially qualified. Can be null.
* @return the minimally qualified form of the given module name. If the given name is not resolvable, then it is value returned.
* If the given module name is null, then null is returned.
*/
public ModuleName getMinimallyQualifiedModuleName(final ModuleName moduleName) {
final ResolutionResult resolution = resolve(moduleName);
final ModuleName minimalName = minimallyQualifiedModuleNames.get(resolution.getResolvedModuleName());
if (minimalName == null) {
return moduleName;
} else {
return minimalName;
}
}
/**
* Returns whether a naming conflict will be introduced by adding an additional import statement.
* @param additionalImportedModuleName the module name to be checked.
* @return true if a naming conflict will be introduced by adding an additional import statement for the given module name; false otherwise.
*/
boolean willAdditionalModuleImportProduceConflict(final ModuleName additionalImportedModuleName) {
////
/// For each partial module name constructible from the additional visible module name,
/// we determine whether it used to be uniquely resolvable but is now in conflict.
//
String partialModuleName = "";
final int lastComponentIndex = additionalImportedModuleName.getNComponents() - 1;
for (int i = lastComponentIndex; i >= 0; i--) {
if (i == lastComponentIndex) {
partialModuleName = additionalImportedModuleName.getNthComponent(i);
} else {
partialModuleName = additionalImportedModuleName.getNthComponent(i) + '.' + partialModuleName;
}
final ResolutionResult existingResolution = resolve(ModuleName.make(partialModuleName));
if (existingResolution.isKnownUnambiguous()) {
// if the existing resolution does not resolve the name to itself (i.e. the name was a fully-qualified module name)
// then the name becomes ambiguous with the addition of these newly visible module name
if (!existingResolution.isResolvedModuleNameEqualToOriginalModuleName()) {
return true;
}
}
}
return false;
}
/**
* Returns a new module name resolver for use after a module renaming.
* @param oldModuleName the original name of the module to be renamed.
* @param newModuleName the new name of the module to be renamed.
* @return a new module name resolver for use after a module renaming.
*/
ModuleNameResolver getResolverAfterRenaming(final ModuleName oldModuleName, final ModuleName newModuleName) {
final Set<ModuleName> visibleModuleNames = new HashSet<ModuleName>(minimallyQualifiedModuleNames.keySet());
visibleModuleNames.remove(oldModuleName);
visibleModuleNames.add(newModuleName);
return make(visibleModuleNames);
}
/**
* @return an empty RenameMapping.
*/
RenameMapping makeEmptyRenameMapping() {
return new RenameMapping(Collections.<ModuleName, ModuleName>emptyMap());
}
/**
* Constructs a RenameMapping for handling the given module renaming in the scope of the module for which this
* module name resolver is responsible.
* @param oldModuleName the original name of the module to be renamed.
* @param newModuleName the new name of the module to be renamed.
* @return a RenameMapping that can be used for updating the module for which this module name resolver is responsible.
*/
RenameMapping makeRenameMapping(final ModuleName oldModuleName, final ModuleName newModuleName) {
// If A.B is renamed to C.D.E.F, and the original module X imports A.B, X.D.E.F and E.F, we build the mapping:
//
// E.F -> E.F
// D.E.F -> X.D.E.F
//
// The handling of A.B -> C.D.E.F and B -> C.D.E.F is to be taken care of by the renamer itself
// (because that should simply be part of its resolution code)
final Map<ModuleName, ModuleName> oldToNewMapping = new HashMap<ModuleName, ModuleName>();
String partialNameFromNewModuleName = "";
final int lastComponentIndex = newModuleName.getNComponents() - 1;
for (int i = lastComponentIndex; i >= 0; i--) {
if (i == lastComponentIndex) {
partialNameFromNewModuleName = newModuleName.getNthComponent(i);
} else {
partialNameFromNewModuleName = newModuleName.getNthComponent(i) + '.' + partialNameFromNewModuleName;
}
final ModuleName partialModuleName = ModuleName.make(partialNameFromNewModuleName);
final ResolutionResult existingResolution = resolve(partialModuleName);
if (existingResolution.isKnownUnambiguous()) {
final ModuleName resolvedModuleName = existingResolution.getResolvedModuleName();
if (resolvedModuleName.equals(oldModuleName)) {
oldToNewMapping.put(partialModuleName, newModuleName);
} else {
oldToNewMapping.put(partialModuleName, resolvedModuleName);
}
}
}
return new RenameMapping(oldToNewMapping);
}
}