/* * 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. */ /* * ModuleName.java * Creation date: Dec 27, 2006. * By: Joseph Wong */ package org.openquark.cal.compiler; import java.util.Arrays; import java.util.Comparator; /** * This class abstracts a module name in CAL. A module name is composed of one or more components separated * by dots. For example, <code>Foo</code> and <code>Cal.Core.Prelude</code> are valid module names. Each component * must start with an uppercase letter. * * @author Joseph Wong */ public final class ModuleName implements Name, Comparable<ModuleName> { /** * The components of the module name. Cannot be null. */ private final String[] components; /** * The hash code of the module name. It is initially zero, and is lazily calculated on the first * call to {@link #hashCode()}. */ private int hash = 0; /** * A {@link Comparator} which compares {@link ModuleName}s by their fully-qualified form, comparing on * a component-by-component basis. * * @author Joseph Wong */ public static final class FullyQualifiedComparator implements Comparator<ModuleName> { /** The singleton instance of this class. */ public static final FullyQualifiedComparator INSTANCE = new FullyQualifiedComparator(); /** Private constructor. This class is meant to be used by its singleton instance. */ private FullyQualifiedComparator() {} /** {@inheritDoc} */ public int compare(final ModuleName a, final ModuleName b) { final int nComponentsInA = a.getNComponents(); final int nComponentsInB = b.getNComponents(); final int nComponentsInBoth = Math.min(nComponentsInA, nComponentsInB); for (int i = 0; i < nComponentsInBoth; i++) { final int componentResult = a.getNthComponent(i).compareTo(b.getNthComponent(i)); if (componentResult != 0) { return componentResult; } } // So far, the common prefix match completely... so the longer one sorts after the shorter one if (nComponentsInA > nComponentsInBoth) { return 1; } else if (nComponentsInB > nComponentsInBoth) { return -1; } else { // nComponentsInA == nComponentsInB == nComponentsInBoth - the two names are completely equal return 0; } } } /** * Private constructor. This class is meant to be instantiated by factory methods. * @param components the components of the module name. */ private ModuleName(final String[] components) { if (components == null) { throw new NullPointerException(); } if (components.length == 0) { throw new IllegalArgumentException("A ModuleName must have > 0 components"); } // no defensive cloning required because that's handled by the factory methods this.components = components; } /** * Returns the fully-qualified form of the given module name components. * @param components an array of module name components. * @return the corresponding fully-qualified form. */ private static String getFullyQualifiedString(final String[] components) { final StringBuilder buffer = new StringBuilder(); final int n = components.length; for (int i = 0; i < n; i++) { if (i > 0) { buffer.append('.'); } buffer.append(components[i]); } return buffer.toString(); } /** * Factory method for constructing an instance of this class. * @param moduleName a module name. Cannot be null. * @return an instance of this class. */ public static ModuleName make(final String moduleName) { if (moduleName == null) { throw new NullPointerException(); } if (moduleName.length() == 0) { throw new IllegalArgumentException("The module name cannot be empty."); } final String[] components = moduleName.split("\\."); final int nComponents = components.length; for (int i = 0; i < nComponents; i++) { if (!LanguageInfo.isValidModuleNameComponent(components[i])) { throw new IllegalArgumentException("'" + components[i] + "', occurring in the module name '" + moduleName + "' is not a valid module name component."); } } return new ModuleName(components); } /** * Factory method for constructing an instance of this class which accepts nulls and invalid module name strings. * @param maybeModuleName a string which may or may not represent a valid module name. <em>Can</em> be null. * @return an instance of this class, or null if the argument is null or does not represent a valid module name. */ public static ModuleName maybeMake(final String maybeModuleName) { if (maybeModuleName == null) { return null; } if (maybeModuleName.length() == 0) { return null; } final String[] components = maybeModuleName.split("\\."); final int nComponents = components.length; for (int i = 0; i < nComponents; i++) { if (!LanguageInfo.isValidModuleNameComponent(components[i])) { return null; } } return new ModuleName(components); } /** * Factory method for constructing an instance of this class. * @param components an array of the components for the module name. Cannot be null. * @return an instance of this class. */ public static ModuleName make(final String[] components) { final int nComponents = components.length; for (int i = 0; i < nComponents; i++) { if (!LanguageInfo.isValidModuleNameComponent(components[i])) { throw new IllegalArgumentException("'" + components[i] + "' is not a valid module name component."); } } // defensive cloning required return new ModuleName(components.clone()); } /** * {@inheritDoc} */ @Override public String toString() { return toSourceText(); } /** * {@inheritDoc} * Returns the module name as it would appear in source, fully-qualified. * @return the module name as it would appear in source, fully-qualified. */ public String toSourceText() { return getFullyQualifiedString(components); } /** * {@inheritDoc} */ @Override public boolean equals(final Object other) { if (other == null) { return false; } else if (other instanceof ModuleName){ // can use instanceof instead of getClass() here because the class is final return equals((ModuleName)other); } else { return false; } } /** * Compares whether the given module name is equal to this module name. * @param other another module name. * @return true if the given module name is non-null and equals this one, false otherwise. */ public boolean equals(final ModuleName other) { if (other == null) { return false; } else if (this == other) { return true; } else { return Arrays.equals(components, other.components); } } /** * {@inheritDoc} */ @Override public int hashCode() { // This method is thread safe in that there is no race condition - multiple threads // may be trying to calculate the hash in parallel, but ultimate the value they obtain // would be the same, and it may be set into the hash field multiple times without affecting // the semantics. This is patterned on how the hash code is cached in the String class. final int cachedHash = hash; if (cachedHash == 0) { int calculatedHash = 1; final String[] comps = components; final int nComps = comps.length; for (int i = 0; i < nComps; i++) { calculatedHash = 31 * calculatedHash + comps[i].hashCode(); } hash = calculatedHash; return calculatedHash; } else { return cachedHash; } } /** * {@inheritDoc} */ public int compareTo(final ModuleName other) { return FullyQualifiedComparator.INSTANCE.compare(this, other); } /** * Returns the number of components in this module name. * @return the number of components in this module name. */ public int getNComponents() { return components.length; } /** * Returns the component at the position specified by the given index. * @param n the index of the component to return. * @return the specified component. */ public String getNthComponent(final int n) { return components[n]; } /** * Returns the last component. * @return the last component. */ public String getLastComponent() { return components[components.length - 1]; } /** * @return an array of the components in this module name. * * @see #getNComponents * @see #getNthComponent * @see #getComponents(int, int) */ public String[] getComponents() { return components.clone(); } /** * Returns an array of some of the components in this module name, as specified by the given start and end indices. * @param start the start position, inclusive. * @param end the end position, exclusive. * @return an array of the components indexed from start to (end-1). */ public String[] getComponents(final int start, final int end) { if (start > end || start < 0 || end < 0 || start >= components.length || end > components.length) { throw new IllegalArgumentException(); } final int lengthToCopy = end - start; final String[] result = new String[lengthToCopy]; System.arraycopy(components, start, result, 0, lengthToCopy); return result; } /** * Returns whether this module name is a proper prefix of the given module name. * @param other another module name. * @return true if this module name is a proper prefix of the given module name, false otherwise. */ public boolean isProperPrefixOf(final ModuleName other) { final int n = components.length; if (n >= other.components.length) { return false; } for (int i = 0; i < n; i++) { if (!components[i].equals(other.components[i])) { return false; } } return true; } /** * Returns a module name that is a prefix of this name. * @param nComponents the number of components in the prefix. * @return a prefix of this module name, which may be this name if the specified number of components * is equal to the number of components in this name. */ public ModuleName getPrefix(int nComponents) { final int n = components.length; if (nComponents < 0 || nComponents > n) { throw new IllegalArgumentException(); } else { final String[] prefixComponents = getComponents(0, nComponents); return new ModuleName(prefixComponents); } } /** * Returns a module name that is the immediate prefix of this name, i.e. it is this name, but dropping the last component. * If this name only has one component, then null is returned. * @return the immediate prefix of this module name, or null if this name has only one component. */ public ModuleName getImmediatePrefix() { final int n = components.length; if (n > 1) { final String[] prefixComponents = getComponents(0, n - 1); return new ModuleName(prefixComponents); } else { return null; } } /** * Returns the common prefix of this module name and the given module name (which may be equal to either one if one * is an improper prefix of the other), or null if they do not share a common prefix. * @param other another module name. * @return the common prefix, or null. */ public ModuleName getCommonPrefix(final ModuleName other) { if (other == null) { return null; } final int minLength = Math.min(components.length, other.components.length); int i; for (i = 0; i < minLength; i++) { if (!components[i].equals(other.components[i])) { break; } } final int commonPrefixLength = i; // since i-1 is the last matching index as a post-condition of the loop if (commonPrefixLength == 0) { return null; } else { return getPrefix(commonPrefixLength); } } /** * {@inheritDoc} */ public ModuleName getModuleName() { return this; } }