/*
* JBoss, Home of Professional Open Source
* Copyright 2009 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.tree;
import net.jcip.annotations.Immutable;
import org.infinispan.marshall.AbstractExternalizer;
import org.infinispan.util.ReflectionUtil;
import org.infinispan.util.Util;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* A Fully Qualified Name (Fqn) is a list of names (typically Strings but can be any Object), which represent a path to
* a particular {@link Node} in a {@link TreeCache}.
* <p/>
* This name can be absolute (i.e., relative from the root node - {@link #ROOT}), or relative to any node in the cache.
* Reading the documentation on each API call that makes use of {@link Fqn}s will tell you whether the API expects a
* relative or absolute Fqn.
* <p/>
* For instance, using this class to fetch a particular node might look like this. (Here data on "Joe" is kept under
* the "Smith" surname node, under the "people" tree.)
* <pre>
* Fqn<String> abc = Fqn.fromString("/people/Smith/Joe/");
* Node joesmith = Cache.getRoot().getChild(abc);
* </pre>
* Alternatively, the same Fqn could be constructed using a List<Object> or varargs:
* <pre>
* Fqn<String> abc = Fqn.fromElements("people", "Smith", "Joe");
* </pre>
* This is a bit more efficient to construct.
* <p/>
* Note that<br>
* <p/>
* <code>Fqn<String> f = Fqn.fromElements("/a/b/c");</code>
* <p/>
* is <b>not</b> the same as
* <p/>
* <code>Fqn<String> f = Fqn.fromString("/a/b/c");</code>
* <p/>
* The former will result in a single Fqn, called "/a/b/c" which hangs directly under Fqn.ROOT.
* <p/>
* The latter will result in 3 Fqns, called "a", "b" and "c", where "c" is a child of "b", "b" is a child of "a", and
* "a" hangs off Fqn.ROOT.
* <p/>
* Another way to look at it is that the "/" separarator is only parsed when it forms part of a String passed in to
* Fqn.fromString() and not otherwise.
* <p/>
* <B>Best practices</B>: Always creating Fqns - even when using some factory methods - can be expensive in the long
* run, and as far as possible we recommend that client code holds on to their Fqn references and reuse them. E.g.:
* <code> // BAD!! for (int i=0; i<someBigNumber; i++) { cache.get(Fqn.fromString("/a/b/c"), "key" + i); } </code>
* instead, do: <code> // Much better Fqn f = Fqn.fromString("/a/b/c"); for (int i=0; i<someBigNumber; i++) {
* cache.get(f, "key" + i); } </code>
*
* @author (various)
* @since 4.0
*/
@Immutable
public class Fqn implements Comparable<Fqn>, Serializable {
/**
* Separator between FQN elements.
*/
public static final String SEPARATOR = "/";
private final Object[] elements;
private transient int hash_code = 0;
/**
* Immutable root Fqn.
*/
public static final Fqn ROOT = new Fqn();
/**
* A cached string representation of this Fqn, used by toString to it isn't calculated again every time.
*/
protected String stringRepresentation;
private static final Object[] EMPTY_ARRAY = ReflectionUtil.EMPTY_CLASS_ARRAY;
// ----------------- START: Private constructors for use by factory methods only. ----------------------
private Fqn(Object... elements) {
this.elements = elements;
}
/**
* If safe is false, Collections.unmodifiableList() is used to wrap the list passed in. This is an optimisation so
* Fqn.fromString(), probably the most frequently used factory method, doesn't end up needing to use the
* unmodifiableList() since it creates the list internally.
*
* @param names List of names
*/
private Fqn(List<Object> names) {
if (names != null)
elements = names.toArray();
else
elements = EMPTY_ARRAY;
}
private Fqn(Fqn base, Object... relative) {
elements = new Object[base.elements.length + relative.length];
System.arraycopy(base.elements, 0, elements, 0, base.elements.length);
System.arraycopy(relative, 0, elements, base.elements.length, relative.length);
}
// ----------------- END: Private constructors for use by factory methods only. ----------------------
/**
* Retrieves an Fqn that represents the list of elements passed in.
*
* @param names list of elements that comprise the Fqn
* @return an Fqn
* @since 4.0
*/
@SuppressWarnings("unchecked")
public static Fqn fromList(List<?> names) {
return new Fqn(names);
}
/**
* Retrieves an Fqn that represents the array of elements passed in.
*
* @param elements array of elements that comprise the Fqn
* @return an Fqn
* @since 4.0
*/
public static Fqn fromElements(Object... elements) {
Object[] copy = new Object[elements.length];
System.arraycopy(elements, 0, copy, 0, elements.length);
return new Fqn(copy);
}
/**
* Retrieves an Fqn that represents the absolute Fqn of the relative Fqn passed in.
*
* @param base base Fqn
* @param relative relative Fqn
* @return an Fqn
* @since 4.0
*/
public static Fqn fromRelativeFqn(Fqn base, Fqn relative) {
return new Fqn(base, relative.elements);
}
/**
* Retrieves an Fqn that represents the List<Object> of elements passed in, relative to the base Fqn.
*
* @param base base Fqn
* @param relativeElements relative List<Object> of elements
* @return an Fqn
* @since 4.0
*/
public static Fqn fromRelativeList(Fqn base, List<?> relativeElements) {
return new Fqn(base, relativeElements.toArray());
}
/**
* Retrieves an Fqn that represents the array of elements passed in, relative to the base Fqn.
*
* @param base base Fqn
* @param relativeElements relative elements
* @return an Fqn
* @since 4.0
*/
public static Fqn fromRelativeElements(Fqn base, Object... relativeElements) {
return new Fqn(base, relativeElements);
}
/**
* Returns a new Fqn from a string, where the elements are deliminated by one or more separator ({@link #SEPARATOR})
* characters.<br><br> Example use:<br>
* <pre>
* Fqn.fromString("/a/b/c/");
* </pre><br>
* is equivalent to:<br>
* <pre>
* Fqn.fromElements("a", "b", "c");
* </pre>
*
* @param stringRepresentation String representation of the Fqn
* @return an Fqn<String> constructed from the string representation passed in
*/
@SuppressWarnings("unchecked")
public static Fqn fromString(String stringRepresentation) {
if (stringRepresentation == null || stringRepresentation.equals(SEPARATOR) || stringRepresentation.length() == 0)
return root();
String toMatch = stringRepresentation.startsWith(SEPARATOR) ? stringRepresentation.substring(1) : stringRepresentation;
Object[] el = toMatch.split(SEPARATOR);
return new Fqn(el);
}
/**
* Obtains an ancestor of the current Fqn. Literally performs <code>elements.subList(0, generation)</code> such that
* if <code> generation == Fqn.size() </code> then the return value is the Fqn itself (current generation), and if
* <code> generation == Fqn.size() - 1 </code> then the return value is the same as <code> Fqn.getParent() </code>
* i.e., just one generation behind the current generation. <code> generation == 0 </code> would return Fqn.ROOT.
*
* @param generation the generation of the ancestor to retrieve
* @return an ancestor of the current Fqn
*/
public Fqn getAncestor(int generation) {
if (generation == 0) return root();
return getSubFqn(0, generation);
}
/**
* Obtains a sub-Fqn from the given Fqn. Literally performs <code>elements.subList(startIndex, endIndex)</code>
*
* @param startIndex starting index
* @param endIndex end index
* @return a subFqn
*/
public Fqn getSubFqn(int startIndex, int endIndex) {
if (endIndex < startIndex) throw new IllegalArgumentException("End index cannot be less than the start index!");
int len = endIndex - startIndex;
Object[] el = new Object[len];
System.arraycopy(elements, startIndex, el, 0, len);
return new Fqn(el);
}
/**
* @return the number of elements in the Fqn. The root node contains zero.
*/
public int size() {
return elements.length;
}
/**
* @param n index of the element to return
* @return Returns the nth element in the Fqn.
*/
public Object get(int n) {
return elements[n];
}
/**
* @return the last element in the Fqn.
* @see #getLastElementAsString
*/
public Object getLastElement() {
if (isRoot()) return null;
return elements[elements.length - 1];
}
/**
* @param element element to find
* @return true if the Fqn contains this element, false otherwise.
*/
public boolean hasElement(Object element) {
return indexOf(element) != -1;
}
private int indexOf(Object element) {
if (element == null) {
for (int i = 0; i < elements.length; i++) {
if (elements[i] == null) return i;
}
} else {
for (int i = 0; i < elements.length; i++) {
if (element.equals(elements[i])) return i;
}
}
return -1;
}
/**
* Returns true if obj is a Fqn with the same elements.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Fqn)) {
return false;
}
Fqn other = (Fqn) obj;
if (elements.length != other.elements.length) return false;
for (int i = elements.length - 1; i >= 0; i--) {
if (!Util.safeEquals(elements[i], other.elements[i])) return false;
}
return true;
}
/**
* Returns a hash code with Fqn elements.
*/
@Override
public int hashCode() {
if (hash_code == 0) {
hash_code = calculateHashCode();
}
return hash_code;
}
/**
* Returns this Fqn as a string, prefixing the first element with a {@link Fqn#SEPARATOR} and joining each subsequent
* element with a {@link Fqn#SEPARATOR}. If this is the root Fqn, returns {@link Fqn#SEPARATOR}. Example:
* <pre>
* new Fqn(new Object[] { "a", "b", "c" }).toString(); // "/a/b/c"
* Fqn.ROOT.toString(); // "/"
* </pre>
*/
@Override
public String toString() {
if (stringRepresentation == null) {
stringRepresentation = getStringRepresentation(elements);
}
return stringRepresentation;
}
/**
* Returns true if this Fqn is child of parentFqn. Example usage:
* <pre>
* Fqn<String> f1 = Fqn.fromString("/a/b");
* Fqn<String> f2 = Fqn.fromString("/a/b/c");
* assertTrue(f1.isChildOf(f2));
* assertFalse(f1.isChildOf(f1));
* assertFalse(f2.isChildOf(f1));
* </pre>
*
* @param parentFqn candidate parent to test against
* @return true if the target is a child of parentFqn
*/
public boolean isChildOf(Fqn parentFqn) {
return parentFqn.elements.length != elements.length && isChildOrEquals(parentFqn);
}
/**
* Returns true if this Fqn is a <i>direct</i> child of a given Fqn.
*
* @param parentFqn parentFqn to compare with
* @return true if this is a direct child, false otherwise.
*/
public boolean isDirectChildOf(Fqn parentFqn) {
return elements.length == parentFqn.elements.length + 1 && isChildOf(parentFqn);
}
/**
* Returns true if this Fqn is equals or the child of parentFqn. Example usage:
* <pre>
* Fqn<String> f1 = Fqn.fromString("/a/b");
* Fqn<String> f2 = Fqn.fromString("/a/b/c");
* assertTrue(f1.isChildOrEquals(f2));
* assertTrue(f1.isChildOrEquals(f1));
* assertFalse(f2.isChildOrEquals(f1));
* </pre>
*
* @param parentFqn candidate parent to test against
* @return true if this Fqn is equals or the child of parentFqn.
*/
public boolean isChildOrEquals(Fqn parentFqn) {
Object[] parentEl = parentFqn.elements;
if (parentEl.length > elements.length) {
return false;
}
for (int i = parentEl.length - 1; i >= 0; i--) {
if (!Util.safeEquals(parentEl[i], elements[i])) return false;
}
return true;
}
/**
* Calculates a hash code by summing the hash code of all elements.
*
* @return a calculated hashcode
*/
protected int calculateHashCode() {
int hashCode = 19;
for (Object o : elements) hashCode = 31 * hashCode + (o == null ? 0 : o.hashCode());
if (hashCode == 0) hashCode = 0xDEADBEEF; // degenerate case
return hashCode;
}
protected String getStringRepresentation(Object[] elements) {
StringBuilder builder = new StringBuilder();
for (Object e : elements) {
// incase user element 'e' does not implement equals() properly, don't rely on their implementation.
if (!SEPARATOR.equals(e) && !"".equals(e)) {
builder.append(SEPARATOR);
builder.append(e);
}
}
return builder.length() == 0 ? SEPARATOR : builder.toString();
}
/**
* Returns the parent of this Fqn. The parent of the root node is {@link #ROOT}. Examples:
* <pre>
* Fqn<String> f1 = Fqn.fromString("/a");
* Fqn<String> f2 = Fqn.fromString("/a/b");
* assertEquals(f1, f2.getParent());
* assertEquals(Fqn.ROOT, f1.getParent().getParent());
* assertEquals(Fqn.ROOT, Fqn.ROOT.getParent());
* </pre>
*
* @return the parent Fqn
*/
public Fqn getParent() {
switch (elements.length) {
case 0:
case 1:
return root();
default:
return getSubFqn(0, elements.length - 1);
}
}
public static Fqn root() // declared final so compilers can optimise and in-line.
{
return ROOT;
}
/**
* Returns true if this is a root Fqn.
*
* @return true if the Fqn is Fqn.ROOT.
*/
public boolean isRoot() {
return elements.length == 0;
}
/**
* If this is the root, returns {@link Fqn#SEPARATOR}.
*
* @return a String representation of the last element that makes up this Fqn.
*/
public String getLastElementAsString() {
if (isRoot()) {
return SEPARATOR;
} else {
Object last = getLastElement();
if (last instanceof String)
return (String) last;
else
return String.valueOf(getLastElement());
}
}
/**
* Peeks into the elements that build up this Fqn. The list returned is read-only, to maintain the immutable nature
* of Fqn.
*
* @return an unmodifiable list
*/
public List<Object> peekElements() {
return Arrays.asList(elements);
}
/**
* Compares this Fqn to another using {@link FqnComparator}.
*/
@Override
public int compareTo(Fqn fqn) {
return FqnComparator.INSTANCE.compare(this, fqn);
}
/**
* Creates a new Fqn whose ancestor has been replaced with the new ancestor passed in.
*
* @param oldAncestor old ancestor to replace
* @param newAncestor nw ancestor to replace with
* @return a new Fqn with ancestors replaced.
*/
public Fqn replaceAncestor(Fqn oldAncestor, Fqn newAncestor) {
if (!isChildOf(oldAncestor))
throw new IllegalArgumentException("Old ancestor must be an ancestor of the current Fqn!");
Fqn subFqn = this.getSubFqn(oldAncestor.size(), size());
return Fqn.fromRelativeFqn(newAncestor, subFqn);
}
public static class Externalizer extends AbstractExternalizer<Fqn> {
@Override
public void writeObject(ObjectOutput output, Fqn fqn) throws IOException {
output.writeInt(fqn.elements.length);
for (Object element : fqn.elements) output.writeObject(element);
}
@Override
public Fqn readObject(ObjectInput input) throws IOException, ClassNotFoundException {
int size = input.readInt();
Object[] elements = new Object[size];
for (int i = 0; i < size; i++) elements[i] = input.readObject();
return new Fqn(elements);
}
@Override
public Set<Class<? extends Fqn>> getTypeClasses() {
return Util.<Class<? extends Fqn>>asSet(Fqn.class);
}
}
}