/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.common.namedobject;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.teiid.core.designer.util.CoreStringUtil;
import org.teiid.designer.common.util.ErrorMessageKeys;
/**
* The BaseID class serves as the abstract base class for identifiers of objects. This class provides the method signatures common
* to all ID classes as well as the majority of the implementation, and defines an identification name as a list of one or more
* non-zero length atomic name components delimeted by a '.' (similar to JNDI names).
* <p>
* Because it is possible and likely that MetaMatrix client applications will construct new instances of identifier classes, these
* ID concepts in this framework are implemented as classes.
* <p>
* These classes are shipped between the client and Configuration Service, so the BaseID class is serializable. To speed
* serialization and decrease the overhead of shipping BaseID across the network using RMI, several instance variables that may
* not be required by all users are made transient and recomputed as needed.
* <p>
* Additionally, because IDs are designed to be used as primary keys, the <code>hashCode</code>, <code>equals</code> and
* <code>compareTo</code> methods are all consistent and optimized for fast performance. This is in part accomplished by caching
* the hash code value, which is tolerable since all BaseID subclasses are <i>immutable</i>: they cannot be modified after they
* have been created.
* <p>
* Finally, several key methods that are very commonly used and that will not be overridden in subclasses are marked as
* <code>final</code> as an inlining hint to the compiler.
*
* @since 8.0
*/
public abstract class BaseID implements Cloneable, Comparable, Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* The character that delimits the atomic components of the name
*/
public static final String DELIMITER = new String(new char[] {IDVerifier.DELIMITER_CHARACTER});
/**
* The wildcard character.
*/
public static final String WILDCARD = "*"; //$NON-NLS-1$
/**
* The fully-qualified name of the node. Will never be null.
*/
protected String fullName;
/**
* The hash code for the fully-qualified name of the node. It should be set when the name is set. Since this object is
* immutable, it should never have to be updated. It can be used for a fail-fast equals check (where unequal hash codes
* signify unequal objects, but not necessarily vice versa) as well.
*/
private int hashCode;
/**
* Ordered list of atomic name components. This is transient to reduce the overhead of passing superfluous information in an
* RMI request.
*/
protected transient List atomicNames;
/**
* Create an instance with the specified full name. The full name must be one or more atomic name components delimited by this
* class' delimeter character.
*
* @param fullName the string form of the full name from which this object is to be created; never null and never zero-length.
* @throws IllegalArgumentException if the full name is null
*/
public BaseID( String fullName ) {
this(fullName, IDVerifier.ALL);
}
protected BaseID( String fullName,
int checkLevel ) {
if (fullName == null) {
throw new IllegalArgumentException(ErrorMessageKeys.NAMEDOBJECT_ERR_0001);
}
if (fullName.trim().length() == 0) {
throw new IllegalArgumentException(ErrorMessageKeys.NAMEDOBJECT_ERR_0002);
}
this.fullName = fullName;
this.updateHashCode();
}
/**
* Obtain the full name for the object that this identifier represents.
*
* @return the full name for this identifier.
*/
public final String getFullName() {
return this.fullName;
}
/**
* Obtain the last name component this identifier. This last name component is the logical name for the object that this
* identifier represents.
*
* @return the name for this identifier.
*/
// Note - made non-final so HostID can override - to support fully qualified host names (supports .'s)
public String getName() {
int nameComponentCount = this.getNameComponents().size();
return (String)this.getNameComponents().get(nameComponentCount - 1);
}
/**
* Obtain the specified component of the name.
*
* @param index of the atomic name component to return; must be less than the result of the method <code>size</code> in
* order to be valid.
* @return the full name for this identifier.
* @throws IndexOutOfBoundsException if the index is not valid and is out of the bounds of this name.
*/
public final String getNameComponent( int index ) {
return (String)this.getNameComponents().get(index);
}
/**
* Obtain the list of atomic name components for this ID.
*
* @return the unmodifiable list of String objects.
* @throws IndexOutOfBoundsException if the index is not valid and is out of the bounds of this name.
*/
// Note - made non-final so HostID can override - to support fully qualified host names (supports .'s)
public List getNameComponents() {
if (this.atomicNames == null) {
if (this.fullName.indexOf(IDVerifier.DELIMITER_CHARACTER) != -1) {
this.atomicNames = CoreStringUtil.split(this.fullName, DELIMITER);
} else {
this.atomicNames = new ArrayList(1);
this.atomicNames.add(this.fullName);
}
this.atomicNames = Collections.unmodifiableList(this.atomicNames);
}
return this.atomicNames;
}
/**
* Return the number of atomic name components in this identifier.
*
* @return the size of this identifier.
*/
public final int size() {
return this.getNameComponents().size();
}
/**
* Returns true if the specified object is semantically equal to this instance. Note: this method is consistent with
* <code>compareTo()</code>.
* <p>
*
* @param obj the object that this instance is to be compared to.
* @return whether the object is equal to this object.
*/
@Override
public boolean equals( Object obj ) {
// Check if instances are identical ...
if (this == obj) {
return true;
}
// Check if object can be compared to this one
// (this includes checking for null ) ...
// if ( this.getClass().isInstance(obj) ) {
if (obj instanceof BaseID) {
// Do quick hash code check first
if (this.hashCode() != obj.hashCode()) {
return false;
}
// If the types aren't the same, then fail
BaseID that = (BaseID)obj;
if (this.getClass() != that.getClass()) {
return false;
}
return this.fullName.equalsIgnoreCase(that.fullName);
}
// Otherwise not comparable ...
return false;
}
/**
* Compares this object to another. If the specified object is an instance of the BaseID class, then this method compares the
* name; otherwise, it throws a ClassCastException (as instances are comparable only to instances of the same class). Note:
* this method <i>is</i> consistent with <code>equals()</code>, meaning that <code>(compare(x, y)==0) == (x.equals(y))</code>.
* <p>
* The algorithm that this method follows is based primarily upon the hash code. When two instances of BaseID, objects A and
* B, are being compared, this method first compares the (cached) hash code of the two objects. If the two hash codes are not
* equal, the method returns the difference in the hash codes (namely <code>A.hashCode() - B.hashCode()</code>). If, however,
* the two hash code values are equivalent, then the two BaseID instances are <i>potentially</i> equivalent, and the full
* names of the BaseIDs are compared (ignoring case) to determine <i>actual</i> result.
* <p>
*
* @param obj the object that this instance is to be compared to.
* @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the
* specified object, respectively.
* @throws IllegalArgumentException if the specified object reference is null
* @throws ClassCastException if the specified object's type prevents it from being compared to this instance.
*/
@Override
public int compareTo( Object obj ) {
BaseID that = (BaseID)obj; // May throw ClassCastException
if (obj == null) {
throw new IllegalArgumentException(ErrorMessageKeys.NAMEDOBJECT_ERR_0003);
}
int diff = this.hashCode() - that.hashCode();
if (diff != 0) {
return diff;
}
if (this.getClass() != that.getClass()) {
diff = this.getClass().hashCode() - that.getClass().hashCode();
return diff;
}
return this.fullName.compareToIgnoreCase(that.fullName);
}
/**
* Compares this object to another lexicographically. If the specified object is an instance of the same class, then this
* method compares the name; otherwise, it throws a ClassCastException (as instances are comparable only to instances of the
* same class). Note: this method is consistent with <code>equals()</code>.
* <p>
*
* @param obj the object that this instance is to be compared to.
* @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the
* specified object, respectively.
* @throws IllegalArgumentException if the specified object reference is null
* @throws ClassCastException if the specified object's type prevents it from being compared to this instance.
*/
public int compareToByName( Object obj ) {
BaseID that = (BaseID)obj; // May throw ClassCastException
if (obj == null) {
throw new IllegalArgumentException("Attempt to compare null"); //$NON-NLS-1$
}
return this.fullName.compareToIgnoreCase(that.fullName);
}
/**
* Returns the hash code value for this object.
*
* @return a hash code value for this object.
*/
@Override
public final int hashCode() {
return this.hashCode;
}
/**
* Returns a string representing the current state of the object.
*
* @return the string representation of this instance.
*/
@Override
public final String toString() {
return this.fullName;
}
/**
* Return a deep cloned instance of this object. Subclasses must override this method.
*
* @return the object that is the clone of this instance.
* @throws CloneNotSupportedException if this object cannot be cloned
*/
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* Return the full name of the parent. This is a convenience method to return the list of atomic name components that excludes
* this ID's last atomic name component.
*
* @return the full name of the parent, or null if this ID has no parent.
*/
// Note - made non-final so HostID can override - to support fully qualified host names (supports .'s)
public String getParentFullName() {
int lastDelim = this.fullName.lastIndexOf(IDVerifier.DELIMITER_CHARACTER);
if (lastDelim == -1) {
return null;
}
return this.fullName.substring(0, lastDelim);
}
// Note - made non-final so HostID can override - to support fully qualified host names (supports .'s)
public boolean hasParent() {
boolean result = false;
if (this.atomicNames != null) {
result = (this.atomicNames.size() > 1);
} else {
int lastDelim = this.fullName.indexOf(IDVerifier.DELIMITER_CHARACTER);
if (lastDelim != -1) {
result = true;
}
}
return result;
}
/**
* Update the currently cached hash code value with a newly computed one. This method should be called in any subclass method
* that updates any field. This includes constructors.
* <p>
* The implementation of this method invokes the <code>computeHashCode</code> method, which is likely overridden in
* subclasses.
*/
protected final void updateHashCode() {
this.hashCode = computeHashCode();
}
/**
* Return a new hash code value for this instance. If subclasses provide additional and non-transient fields, they should
* override this method using the following template:
*
* <pre>
* protected int computeHashCode() {
* int result = super.computeHashCode();
* result = HashCodeUtil.hashCode(result, ... );
* result = HashCodeUtil.hashCode(result, ... );
* return result;
* }
* </pre>
*
* Any specialized implementation must <i>not<i> rely upon the <code>hashCode</code> method.
* <p>
* Note that this method does not and cannot actually update the hash code value. Rather, this method is called by the
* <code>updateHashCode</code> method.
*
* @return the new hash code for this instance.
*/
protected int computeHashCode() {
return this.fullName.toLowerCase().hashCode();
}
/**
* Helper method that instantiates the atomic name list if null.
*/
// private final void buildAtomicNames() {
// if (this.atomicNames == null) {
// this.atomicNames = StringUtil.split(this.fullName, DELIMITER);
// this.atomicNames = Collections.unmodifiableList(this.atomicNames);
// }
// }
}