/*
* 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.
*/
/*
* ModuleNameUtilities.java
* Creation date: Nov 3, 2006.
* By: Joseph Wong
*/
package org.openquark.cal.compiler;
/**
* 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 compiler-specific logic for working with module names, especially in the form of
* parse tree nodes. See also the {@link ModuleNameResolver} for the logic for resolving module names.
*
* @author Joseph Wong
*/
final class ModuleNameUtilities {
/** This class is not meant to be instantiated. */
private ModuleNameUtilities() {}
/**
* Constructs a parse tree for a module name.
* @param moduleName a module name.
* @return a parse tree for the given module name.
*/
static ParseTreeNode makeParseTreeForModuleName(ModuleName moduleName) {
return makeParseTreeForMaybeModuleName(moduleName.toSourceText(), null);
}
/**
* Constructs a parse tree for a module name.
* @param moduleName a module name.
* @return a parse tree for the given module name.
*/
static ParseTreeNode makeParseTreeForMaybeModuleName(String moduleName) {
return makeParseTreeForMaybeModuleName(moduleName, null);
}
/**
* Constructs a parse tree for a module name with the given source position to be used as the start position
* of all components in the module name.
*
* @param moduleName a module name.
* @param sourcePos the source position that is to be used as the start position of all the components.
* @return a parse tree for the given module name using the given source position.
*/
static ParseTreeNode makeParseTreeForMaybeModuleName(String moduleName, SourcePosition sourcePos) {
if (moduleName.length() == 0) {
return new ParseTreeNode(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER, "HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER", sourcePos);
} else {
String[] components = LanguageInfo.getVerifiedModuleNameComponents(moduleName);
return makeParseTreeForModuleNameComponents(components, components.length, sourcePos);
}
}
/**
* Constructs a parse tree from an array of module name components with the given source position to be used as
* the start position of all components. The number of components actually used in the conversion to parse tree
* is specified by the <code>upToNotIncludingIndex</code> parameter.
*
* @param components the components of a module name.
* @param upToNotIncludingIndex an index <em>n</em> such that the parse tree will be generated for components[0..n-1]
* @param sourcePos the source position that is to be used as the start position of all the components.
* @return a parse tree for the given module name using the given source position.
*/
private static ParseTreeNode makeParseTreeForModuleNameComponents(String[] components, final int upToNotIncludingIndex, SourcePosition sourcePos) {
ParseTreeNode hierarchicalModuleNameNode = new ParseTreeNode(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER, "HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER", sourcePos);
for (int i = 0; i < upToNotIncludingIndex; i++) {
ParseTreeNode qualifierNode = hierarchicalModuleNameNode;
hierarchicalModuleNameNode = new ParseTreeNode(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, "HIERARCHICAL_MODULE_NAME", sourcePos);
ParseTreeNode unqualifiedModuleNameNode = new ParseTreeNode(CALTreeParserTokenTypes.CONS_ID, components[i], sourcePos);
hierarchicalModuleNameNode.setFirstChild(qualifierNode);
qualifierNode.setNextSibling(unqualifiedModuleNameNode);
}
return hierarchicalModuleNameNode;
}
/**
* Sets the given module name into the given parse tree, adding new nodes and modifying existing nodes as necessary.
* <p>
* If the specified module name is the same as the name represented by the parse tree rooted at <code>moduleNameNode</code>,
* then no modifications will be made.
*
* @param moduleNameNode the root of the parse tree to be modified.
* @param moduleName the module name to be set into the parse tree.
*/
static void setModuleNameIntoParseTree(final ParseTreeNode moduleNameNode, ModuleName moduleName) {
String moduleNameString = moduleName.toSourceText();
String moduleNameFromParseTree = getMaybeModuleNameStringFromParseTree(moduleNameNode);
// no need to proceed further if the name to be set is the same as the one represented by the parse tree
if (moduleNameFromParseTree.equals(moduleNameString)) {
return;
}
// obtain the original module name as appearing in source
String moduleNameInSourceFromAttribute = moduleNameNode.getModuleNameInSource();
String moduleNameInSource;
if (moduleNameInSourceFromAttribute != null) {
moduleNameInSource = moduleNameInSourceFromAttribute;
} else {
moduleNameInSource = moduleNameFromParseTree;
}
moduleNameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
String[] components = LanguageInfo.getVerifiedModuleNameComponents(moduleNameString);
final int nComponents = components.length;
SourcePosition leftmostPos = moduleNameNode.getAssemblySourcePosition();
// We loop through the components of the module name starting from the end, because the parse tree for
// a module name A.B.C has the structure: (((<empty> A) B) C)
// The following loop goes through to make sure that components[0..componentIndex] corresponds to the subtree
// rooted at currentModuleNameNode.
int componentIndex = nComponents - 1;
ParseTreeNode currentModuleNameNode = moduleNameNode;
boolean done = false;
while (componentIndex >= 0) {
currentModuleNameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
// If we hit the empty qualifier, we will need to add new nodes for all the remaining components not in tree.
if (currentModuleNameNode.getType() == CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER) {
// The parse tree <empty> should become (<parse tree for components[0..componentIndex-1]> components[componentIndex])
ParseTreeNode leftChild = makeParseTreeForModuleNameComponents(components, componentIndex, leftmostPos);
ParseTreeNode rightChild = new ParseTreeNode(CALTreeParserTokenTypes.CONS_ID, components[componentIndex], leftmostPos);
// We modify the type of the parse tree node and set in the children.
currentModuleNameNode.setType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME);
currentModuleNameNode.setText("HIERARCHICAL_MODULE_NAME");
currentModuleNameNode.setFirstChild(leftChild);
leftChild.setNextSibling(rightChild);
// And we are completely done with modifying the parse tree.
done = true;
break;
}
// The parse tree rooted at currentModuleNameNode is of the form (<parent qualifier> <trailing component>)
// We need to check the <trailing component> against components[componentIndex] now,
// and <parent qualifier> against components[0..componentIndex-1] in the next iteration of the loop.
ParseTreeNode parentQualifierNode = currentModuleNameNode.firstChild();
parentQualifierNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
ParseTreeNode trailingComponentNode = parentQualifierNode.nextSibling();
trailingComponentNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String trailingComponent = trailingComponentNode.getText();
String specifiedComponent = components[componentIndex];
if (!specifiedComponent.equals(trailingComponent)) {
// The trailing component differs, so we need to drop both this component and the parent qualifier from the tree
// and construct new ones (since all the source positions in the parent qualifier in the tree would be wrong)
ParseTreeNode leftChild = makeParseTreeForModuleNameComponents(components, componentIndex, leftmostPos);
ParseTreeNode rightChild = new ParseTreeNode(CALTreeParserTokenTypes.CONS_ID, components[componentIndex], leftmostPos);
// We do not need to modify the type of the parse tree node (since we know it is a HIERARCHICAL_MODULE_NAME).
// We just need to change both its children.
currentModuleNameNode.setFirstChild(leftChild);
leftChild.setNextSibling(rightChild);
// And we are completely done with modifying the parse tree.
done = true;
break;
}
// The trailing component matches, so process the parent qualifier in the next iteration.
currentModuleNameNode = parentQualifierNode;
componentIndex--;
}
// If we are still not done, that means the original parse tree contains at least as many nodes as
// are needed with the new name.
// So we truncate the remaining module name if needed.
// (e.g. the parse tree represents A.B.C and we are trying to set just B.C into the tree)..
if (!done) {
if (currentModuleNameNode.getType() != CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER) {
currentModuleNameNode.setType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
currentModuleNameNode.setText("HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER");
currentModuleNameNode.setFirstChild(null);
}
}
// Finally, mark the moduleNameNode as being synthetically assigned by setting the original module name
// as appearing in source into the root of the parse tree as a custom attribute.
moduleNameNode.setModuleNameInSource(moduleNameInSource);
}
/**
* Returns the module name as it original appears in the source, unmodified by resolution steps.
* If the original name was previously stored in the root of the given parse tree as a custom attribute, that
* stored name is returned. Otherwise the value obtained via {@link #getMaybeModuleNameStringFromParseTree} is returned.
*
* @param moduleNameNode the root of the parse tree representing a module name.
* @return the module name as it original appears in the source, unmodified by resolution steps.
*/
static String getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource(final ParseTreeNode moduleNameNode) {
String moduleNameInSourceFromAttribute = moduleNameNode.getModuleNameInSource();
if (moduleNameInSourceFromAttribute != null) {
return moduleNameInSourceFromAttribute;
} else {
return getMaybeModuleNameStringFromParseTree(moduleNameNode);
}
}
/**
* Returns the module name as represented by the given parse tree, which may be the empty string "".
* Unlike {@link #getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource}, no attention is paid to the custom attributes
* stored in the parse tree nodes.
*
* @param moduleNameNode the root of the parse tree representing a module name.
* @return the module name represented by the given parse tree, which may be the empty string "".
*/
static String getMaybeModuleNameStringFromParseTree(ParseTreeNode moduleNameNode) {
// we return an empty string for a null parse tree node or a HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER
if (moduleNameNode == null ||
moduleNameNode.getType() == CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER) {
return "";
}
moduleNameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME);
String moduleName = "";
ParseTreeNode currentNode = moduleNameNode;
int nComponents = 0;
// loop through the parse tree, extracting components in reverse order
// e.g. for a name A.B.C, the loop will go through the components in the order: C, B, A
while (currentNode.getType() == CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME) {
ParseTreeNode parentQualifierNode = currentNode.firstChild();
parentQualifierNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
ParseTreeNode trailingComponentNode = parentQualifierNode.nextSibling();
trailingComponentNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String trailingComponent = trailingComponentNode.getText();
// add the trailing component to the front (ahead of the components already added)
if (nComponents == 0) {
moduleName = trailingComponent;
} else {
moduleName = trailingComponent + '.' + moduleName;
}
nComponents++;
currentNode = parentQualifierNode;
}
// we should've reached the end of the chain - i.e. a HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER
currentNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
return moduleName;
}
/**
* Returns the module name as represented by the given parse tree, which may be null if the module name represented is the empty string "".
* Unlike {@link #getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource}, no attention is paid to the custom attributes
* stored in the parse tree nodes.
*
* @param moduleNameNode the root of the parse tree representing a module name.
* @return the module name represented by the given parse tree, or <code>null</code> if the given parse tree represents the empty string "".
*/
static ModuleName getModuleNameOrNullFromParseTree(ParseTreeNode moduleNameNode) {
String maybeModuleName = getMaybeModuleNameStringFromParseTree(moduleNameNode);
if (maybeModuleName.length() == 0) {
return null;
} else {
return ModuleName.make(maybeModuleName);
}
}
/**
* Returns the module name as represented by the given parse tree, which may <strong>not</strong> be the empty string "".
* Unlike {@link #getMaybeModuleNameStringUnmodifiedAccordingToOriginalSource}, no attention is paid to the custom attributes
* stored in the parse tree nodes.
*
* @param moduleNameNode the root of the parse tree representing a module name.
* @return the module name represented by the given parse tree, which may <strong>not</strong> be the empty string "".
*/
static ModuleName getModuleNameFromParseTree(ParseTreeNode moduleNameNode) {
String maybeModuleName = getMaybeModuleNameStringFromParseTree(moduleNameNode);
if (maybeModuleName.length() == 0) {
throw new IllegalArgumentException("A non-empty module name is not found in the given node");
}
return ModuleName.make(maybeModuleName);
}
/**
* Resolves the module name represented by the given parse tree, and modifies it to represent the resolved name
* (if it differs from the original name).
*
* @param moduleNameNode the root of the parse tree representing a module name.
* @param currentModuleTypeInfo the type info of the current module under whose context module name resolution is to be performed.
* @param compiler the compiler instance (used for error logging).
* @param suppressErrorMessageLogging whether to suppress the logging of error messages.
* @return the resolved name for the given module name.
*/
static String resolveMaybeModuleNameInParseTree(ParseTreeNode moduleNameNode, ModuleTypeInfo currentModuleTypeInfo, CALCompiler compiler, boolean suppressErrorMessageLogging) {
String maybeModuleName = getMaybeModuleNameStringFromParseTree(moduleNameNode);
if (maybeModuleName.length() > 0) {
ModuleName moduleName = ModuleName.make(maybeModuleName);
ModuleNameResolver.ResolutionResult resolution = currentModuleTypeInfo.getModuleNameResolver().resolve(moduleName);
if (!resolution.isResolvedModuleNameEqualToOriginalModuleName()) {
setModuleNameIntoParseTree(moduleNameNode, resolution.getResolvedModuleName());
}
if (resolution.isAmbiguous()) {
if (!suppressErrorMessageLogging) {
compiler.logMessage(new CompilerMessage(moduleNameNode, new MessageKind.Error.AmbiguousPartiallyQualifiedFormModuleName(moduleName, resolution.getPotentialMatches())));
}
}
return resolution.getResolvedModuleName().toSourceText();
} else {
// if the module name node represents the empty string "", then an empty string is returned.
return "";
}
}
}