blob: 4810cf9d2208e4020937ef61f9f2d7b0084a6370 [file] [log] [blame]
package org.eclipse.jdt.internal.core.builder;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import java.io.*;
/**
* <code>DeltaKey</code> objects are used to index into hierarchical structures.
* They consist of a sequence of local names, which can be
* arbitrary objects. A node in the hierarchy is obtained by
* traversing down from the root of the hierarchy, using the
* localNames to indicate the choice of child at each step along the way.
*
* <p><code>DeltaKey</code> objects are immutable, as they must consistently describe
* a particular branch of a source tree. If branches are addded or deleted
* from a <code>DeltaKey</code> object, it is done by creating a copy and modifying
* the copy.
*/
public class DeltaKey implements IDeltaKey, Serializable {
/**
* Names of the branches of the key
*/
protected Object[] fLocalNames;
/**
* Singleton root node instance.
*/
protected static DeltaKey NodeRoot = new DeltaKey(0);
/** Version UID for serialization purposes (as of LF 143)
*/
protected static final long serialVersionUID = 1379863541613390307L;
/**
* Sets the names of the key
*/
public DeltaKey(Object[] names) {
fLocalNames = names;
}
/**
* Creates a new key of the given length.
*/
protected DeltaKey (int length) {
fLocalNames = new Object[length];
}
/**
* Returns a new key describing the key's child with the given local name.
* This replaces the "/" syntax in the Smalltalk implementation
*/
public DeltaKey add(Object localName) {
/* Calculate length of new key */
int length = this.size();
DeltaKey newKey = new DeltaKey(length + 1);
/* Copy names from receiver to new key */
newKey.replaceFromToWithStartingAt(0, length - 1, this, 0);
/* Add new local name at end of new key */
newKey.fLocalNames[length] = localName;
return newKey;
}
/**
* Returns the local name at the given index in the receiver
*/
public Object at(int index) {
return fLocalNames[index];
}
/**
* Returns true if the receiver has the exact same local names as anObject.
* Returns false otherwise.
* Returns false if anObject is not an instance of NodeKey
*/
public boolean equals (Object anObject) {
DeltaKey aKey;
int result;
/* return true if the same object */
if (this == anObject) {
return true;
}
/* return false if different class of object */
if (anObject instanceof DeltaKey) {
aKey = (DeltaKey) anObject;
} else {
return false;
}
/* return false if not the same number of names */
if (this.size() != aKey.size()) {
return false;
}
/* not equal if any local names are not equal */
for (int i = this.size() - 1; i >= 0; i--) {
if (!this.at(i).equals(aKey.at(i))) {
return false;
}
}
return true;
}
/**
* Returns the receiver's local name (the name of the last child). If the
* receiver is a root key, answer null
*/
public Object getLocalName() {
if (this.size() == 0) {
return null;
} else {
return fLocalNames[fLocalNames.length - 1];
}
}
/**
* Returns the local names of the key
*/
protected Object[] getLocalNames() {
return fLocalNames;
}
/**
* Returns the singleton root node instance.
*/
public static DeltaKey getRoot() {
return NodeRoot;
}
/**
* Returns the hash value of the receiver. Objects which are equal have
* the same hash value.
*/
public int hashCode() {
int hash, selfSize;
selfSize = this.size();
if (selfSize == 0) {
return 0;
}
/* Hash the first element */
hash = this.at(0).hashCode();
if (selfSize == 1) {
return hash;
}
/* Hash the last element */
hash ^= this.at(selfSize - 1).hashCode();
if (selfSize == 2) {
return hash & 32767;
}
/* Hash the middle element for good luck */
hash ^= this.at(selfSize / 2).hashCode();
return hash & 32767;
}
/**
* Returns true if the receiver is a prefix of the given key, false otherwise.
* Keys which are equal are considered to be prefixes of each other (so
* true is answered in this case).
*/
public boolean isPrefixOf (DeltaKey key) {
int size;
/**
* Capitalize on the property that compound keys are usually paths,
* where a common prefix is more likely than a common suffix.
* Compare last elements before comparing the rest.
*/
size = this.size();
if (size > key.size()) {
return false;
}
if (size == 0) {
return true;
}
/* Check the last element first. */
size--;
if (this.fLocalNames[size] != key.fLocalNames[size]) {
return false;
}
if (--size <= 0) {
return true;
}
/* Check the first element next */
if (this.fLocalNames[0] != key.fLocalNames[0]) {
return false;
}
if (size == 1) {
return true;
}
/* Unroll the loop for increased performance */
if (this.fLocalNames[size] != key.fLocalNames[size]) {return false;}
if (--size == 1) {return true;}
if (this.fLocalNames[size] != key.fLocalNames[size]) {return false;}
if (--size == 1) {return true;}
if (this.fLocalNames[size] != key.fLocalNames[size]) {return false;}
if (--size == 1) {return true;}
if (this.fLocalNames[size] != key.fLocalNames[size]) {return false;}
if (--size == 1) {return true;}
if (this.fLocalNames[size] != key.fLocalNames[size]) {return false;}
if (--size == 1) {return true;}
if (this.fLocalNames[size] != key.fLocalNames[size]) {return false;}
if (--size == 1) {return true;}
if (this.fLocalNames[size] != key.fLocalNames[size]) {return false;}
if (--size == 1) {return true;}
if (this.fLocalNames[size] != key.fLocalNames[size]) {return false;}
if (--size == 1) {return true;}
if (this.fLocalNames[size] != key.fLocalNames[size]) {return false;}
if (--size == 1) {return true;}
while (size > 1) {
if (this.fLocalNames[size] != key.fLocalNames[size]) {
return false;
}
}
return true;
}
/**
* Returns true if the receiver is the root key, false otherwise
*/
public boolean isRoot() {
return (this.size() == 0);
}
/**
* Returns the key describing the receiver's parent, or null if the receiver
* is the root key.
*/
public DeltaKey parent() {
int size = this.size() - 1;
DeltaKey newKey;
if (size < 0) {
return null;
} else {
newKey = new DeltaKey (size);
/* copy children from receiver */
newKey.replaceFromToWithStartingAt(0, size -1, this, 0);
return newKey;
}
}
/**
* Returns a new key containing the first few local names in the
* receiver.
*
* @param count
* number of local names to copy (length of prefix)
* @exception IllegalArgumentException
* receiver contains fewer than count local names, or count is
* negative."
*/
public DeltaKey prefix (int count) {
DeltaKey newNode;
if (count < 0 || this.size() < count) {
throw new IllegalArgumentException (new Integer(count).toString());
}
newNode = new DeltaKey(count);
/* copy children */
newNode.replaceFromToWithStartingAt (0, count - 1, this, 0);
return newNode;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
int l = in.readInt();
fLocalNames = new String[l];
for (int i = 0; i < l; ++i) {
fLocalNames[i] = in.readObject();
}
}
/**
* Replaces the elements of the receiver between the given indices
* with the elements of the supplied key starting at a particular point.
*
* Fail if start is not an Integer. Fail if stop is not an
* Integer. Fail if repStart is not an Integer. Fail if start
* is < 1. Fail if start is > size of the receiver. Fail if
* stop is < 1. Fail if repStart < 1. Fail if repStart > size
* of replacementKey."
*
* @param start
* index in the receiver to start copying at
* @param stop
* index in the receiver to stop copying at
* @param replacementKey
* key to be copied
* @param repStart
* index to start copying from in the replacement key
* @return the modified key
*/
private DeltaKey replaceFromToWithStartingAt (int start, int stop,
DeltaKey replacementKey, int repStart) {
int repCount = repStart;
for (int i = start; i <= stop; i++, repCount++) {
this.fLocalNames[i] = replacementKey.fLocalNames[repCount];
}
return this;
}
/**
* Sets the local names of the key
*/
protected void setLocalNames(String[] names) {
fLocalNames = names;
}
/**
* Returns the branch length of the key
*/
public int size() {
return fLocalNames.length;
}
/**
* Returns a unicode representation of the receiver. This method is used
* for debugging purposes only (no NLS needed)
*/
public String toString () {
StringBuffer buffer = new StringBuffer();
buffer.append("DeltaKey("/*nonNLS*/);
for (int i = 0; i < this.fLocalNames.length; i++) {
buffer.append(this.fLocalNames[i]);
if (i < this.fLocalNames.length - 1) {
buffer.append("/"/*nonNLS*/);
}
}
buffer.append(")"/*nonNLS*/);
return buffer.toString();
}
/**
* Returns a new key containing everything but the first few local
* names in the receiver.
*
* @param count
* number of local names in the key to leave out (length of prefix to cut)
* @exception IllegalArgumentException
* receiver contains fewer than count local names, or count is
* negative.
*/
public DeltaKey withoutPrefix (int count) {
DeltaKey newNode;
int newSize;
if (count < 0 || this.size() < count) {
throw new IllegalArgumentException (new Integer (count).toString());
}
newSize = this.size() - count;
newNode = new DeltaKey (newSize);
newNode.replaceFromToWithStartingAt (0, newSize - 1, this, count);
return newNode;
}
private void writeObject(ObjectOutputStream out) throws IOException {
int l = fLocalNames.length;
out.writeInt(l);
for (int i = 0; i < l; ++i) {
out.writeObject(fLocalNames[i]);
}
}
}// end class definition