/* * JBoss, Home of Professional Open Source. * Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file 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.jboss.cache; import net.jcip.annotations.Immutable; import org.jboss.cache.annotations.Compat; import org.jboss.cache.util.Immutables; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * 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} or sometimes a {@link Region} in a {@link Cache}. *

* 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 org.jboss.cache.Fqn}s will tell you whether the API expects a * relative or absolute Fqn. *

* 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.) *

 * Fqn abc = Fqn.fromString("/people/Smith/Joe/");
 * Node joesmith = Cache.getRoot().getChild(abc);
 * 
* Alternatively, the same Fqn could be constructed using a List or varargs: *
 * Fqn abc = Fqn.fromElements("people", "Smith", "Joe");
 * 
* This is a bit more efficient to construct. *

* Note that
*

* Fqn f = Fqn.fromElements("/a/b/c"); *

* is not the same as *

* Fqn f = Fqn.fromString("/a/b/c"); *

* The former will result in a single Fqn, called "/a/b/c" which hangs directly under Fqn.ROOT. *

* 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. *

* 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. *

* Best practices: 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.: * * // BAD!! * for (int i=0; i * instead, do: * * // Much better * Fqn f = Fqn.fromString("/a/b/c"); * for (int i=0; i * * @version $Revision: 1.1 $ */ @Immutable @Compat(notes = "The generics, while originally intended to be removed in 3.0, have been retained for backward compat.") public class Fqn implements Comparable>, Externalizable { /** * Separator between FQN elements. */ public static final String SEPARATOR = "/"; protected List elements; private transient int hash_code = 0; protected int size = 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; // ----------------- START: Private constructors for use by factory methods only. ---------------------- /** * Public to satisfy Externalization. // TODO: Remove this ctor as well as Externalization!! */ public Fqn() { elements = Collections.emptyList(); size = 0; } // --- deprecated compat stuff /** * Constructs a FQN from a list of names. * * @param names List of names * @deprecated use {@link #fromList(java.util.List)} instead. This constructor will be removed in 3.0.0. */ @Deprecated @Compat public Fqn(List names) { // the list is unsafe - may be referenced externally this(names, false); } /** * Constructs a Fqn from an array of names. * * @param names Names that comprose this Fqn * @deprecated use {@link #fromElements(Object[])} instead. This constructor will be removed in 3.0.0. */ @Deprecated @Compat public Fqn(E... names) { // safe - the list is created here. this(Arrays.asList(names), true); } /** * Constructs a Fqn from a base and relative Fqn. * * @param base parent Fqn * @param relative Sub-Fqn relative to the parent * @deprecated use {@link #fromRelativeFqn(Fqn, Fqn)} instead. This constructor will be removed in 3.0.0. */ @Deprecated @Compat public Fqn(Fqn base, Fqn relative) { this(base, relative.elements); } /** * Constructs a Fqn from a base and two relative names. * * @param base parent Fqn * @param childNames elements that denote the path to the Fqn, under the parent * @deprecated use {@link #fromRelativeElements(Fqn, Object[])} instead. This constructor will be removed in 3.0.0. */ @Deprecated @Compat public Fqn(Fqn base, E... childNames) { this(base, Arrays.asList(childNames)); } // --- end deprecated stuff /** * 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 * @param safe whether this list is referenced externally (safe = false) or not (safe = true). * @deprecated use {@link #fromList(java.util.List)} instead. The boolean "safety" hint is calculated internally. This constructor will be removed in 3.0.0. */ @Deprecated @Compat(notes = "Not truly deprecated, this constructor should really be protected and not public. Marked as deprecated for anyone using it as a public API.") @SuppressWarnings("unchecked") protected Fqn(List names, boolean safe) { if (names != null) { // if not safe make a defensive copy elements = safe ? (List) names : Immutables.immutableListCopy(names); size = elements.size(); } else { elements = Collections.emptyList(); size = 0; } } protected Fqn(Fqn base, List relative) { elements = Immutables.immutableListMerge(base.elements, relative); size = elements.size(); } // ----------------- 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 2.2.0 */ @SuppressWarnings("unchecked") public static Fqn fromList(List names) { return new Fqn(names, false); } /** * Retrieves an Fqn that represents the list of elements passed in. * * @param names list of elements that comprise the Fqn * @param safe if true, the list passed in is not defensively copied but used directly. Use with care. Make sure * you know what you are doing before you pass in a true value to safe, as it can have adverse effects on * performance or correctness. The defensive copy of list elements is not just for safety but also for performance as * an appropriare List implementation is used, which works well with Fqn operations. * @return an Fqn */ @SuppressWarnings("unchecked") public static Fqn fromList(List names, boolean safe) { return new Fqn(names, safe); } /** * Retrieves an Fqn that represents the array of elements passed in. * * @param elements array of elements that comprise the Fqn * @return an Fqn * @since 2.2.0 */ public static Fqn fromElements(T... elements) { return new Fqn(Arrays.asList(elements), true); } /** * 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 2.2.0 */ public static Fqn fromRelativeFqn(Fqn base, Fqn relative) { return new Fqn(base, relative.elements); } /** * Retrieves an Fqn that represents the List of elements passed in, relative to the base Fqn. * * @param base base Fqn * @param relativeElements relative List of elements * @return an Fqn * @since 2.2.0 */ public static Fqn fromRelativeList(Fqn base, List relativeElements) { return new Fqn(base, relativeElements); } /** * 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 2.2.0 */ public static Fqn fromRelativeElements(Fqn base, T... relativeElements) { return new Fqn(base, Arrays.asList(relativeElements)); } /** * Returns a new Fqn from a string, where the elements are deliminated by * one or more separator ({@link #SEPARATOR}) characters.

* Example use:
*
    * Fqn.fromString("/a/b/c/");
    * 

* is equivalent to:
*
    * Fqn.fromElements("a", "b", "c");
    * 
* * @param stringRepresentation String representation of the Fqn * @return an Fqn constructed from the string representation passed in */ @SuppressWarnings("unchecked") public static Fqn fromString(String stringRepresentation) { if (stringRepresentation == null || stringRepresentation.equals(SEPARATOR) || stringRepresentation.equals("")) return root(); String toMatch = stringRepresentation.startsWith(SEPARATOR) ? stringRepresentation.substring(1) : stringRepresentation; Object[] el = toMatch.split(SEPARATOR); return new Fqn(Immutables.immutableListWrap(el), true); } /** * Retrieves an Fqn read from an object input stream, typically written to using {@link #writeExternal(java.io.ObjectOutput)}. * * @param in input stream * @return an Fqn * @throws IOException in the event of a problem reading the stream * @throws ClassNotFoundException in the event of classes that comprise the element list of this Fqn not being found * @since 2.2.0 */ public static Fqn fromExternalStream(ObjectInput in) throws IOException, ClassNotFoundException { Fqn f = new Fqn(); f.readExternal(in); return f; } /** * Obtains an ancestor of the current Fqn. Literally performs elements.subList(0, generation) * such that if * * generation == Fqn.size() * * then the return value is the Fqn itself (current generation), and if * * generation == Fqn.size() - 1 * * then the return value is the same as * * Fqn.getParent() * * i.e., just one generation behind the current generation. * * generation == 0 * * 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 elements.subList(startIndex, endIndex) * * @param startIndex starting index * @param endIndex end index * @return a subFqn */ public Fqn getSubFqn(int startIndex, int endIndex) { List el = elements.subList(startIndex, endIndex); return new Fqn(el, true); } /** * @return the number of elements in the Fqn. The root node contains zero. */ public int size() { return size; } /** * @param n index of the element to return * @return Returns the nth element in the Fqn. */ public Object get(int n) { return elements.get(n); } /** * @return the last element in the Fqn. * @see #getLastElementAsString */ public Object getLastElement() { if (isRoot()) return null; return elements.get(size - 1); } /** * @param element element to find * @return true if the Fqn contains this element, false otherwise. */ public boolean hasElement(Object element) { return elements.indexOf(element) != -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; return size == other.size() && elements.equals(other.elements); } /** * 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: *
    * new Fqn(new Object[] { "a", "b", "c" }).toString(); // "/a/b/c"
    * Fqn.ROOT.toString(); // "/"
    * 
*/ @Override public String toString() { if (stringRepresentation == null) { stringRepresentation = getStringRepresentation(elements); } return stringRepresentation; } public void writeExternal(ObjectOutput out) throws IOException { out.writeShort(size); for (Object element : elements) { out.writeObject(element); } } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { size = in.readShort(); this.elements = new ArrayList(size); for (int i = 0; i < size; i++) elements.add((E) in.readObject()); } /** * Returns true if this Fqn is child of parentFqn. * Example usage: *
    * Fqn f1 = Fqn.fromString("/a/b");
    * Fqn f2 = Fqn.fromString("/a/b/c");
    * assertTrue(f1.isChildOf(f2));
    * assertFalse(f1.isChildOf(f1));
    * assertFalse(f2.isChildOf(f1));
    * 
* * @param parentFqn candidate parent to test against * @return true if the target is a child of parentFqn */ public boolean isChildOf(Fqn parentFqn) { return parentFqn.size() != size && isChildOrEquals(parentFqn); } /** * Returns true if this Fqn is a direct 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 size == parentFqn.size() + 1 && isChildOf(parentFqn); } /** * Returns true if this Fqn is equals or the child of parentFqn. * Example usage: *
    * Fqn f1 = Fqn.fromString("/a/b");
    * Fqn f2 = Fqn.fromString("/a/b/c");
    * assertTrue(f1.isChildOrEquals(f2));
    * assertTrue(f1.isChildOrEquals(f1));
    * assertFalse(f2.isChildOrEquals(f1));
    * 
* * @param parentFqn candidate parent to test against * @return true if this Fqn is equals or the child of parentFqn. */ public boolean isChildOrEquals(Fqn parentFqn) { List parentList = parentFqn.elements; if (parentList.size() > size) { return false; } for (int i = parentList.size() - 1; i >= 0; i--) { if (!parentList.get(i).equals(elements.get(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(List 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: *
    * Fqn f1 = Fqn.fromString("/a");
    * Fqn f2 = Fqn.fromString("/a/b");
    * assertEquals(f1, f2.getParent());
    * assertEquals(Fqn.ROOT, f1.getParent().getParent());
    * assertEquals(Fqn.ROOT, Fqn.ROOT.getParent());
    * 
* * @return the parent Fqn */ public Fqn getParent() { switch (size) { case 0: case 1: return root(); default: return new Fqn(elements.subList(0, size - 1), true); } } public static final 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 size == 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 peekElements() { return elements; } /** * Compares this Fqn to another using {@link FqnComparator}. */ 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); } }