/** * Copyright 2007 the dCache team */ package org.dcache.services.info.base; import java.util.ArrayList; import java.util.List; /** * StatePath provides a representation of a value's location within the * dCache State. A path consists of an ordered list of path elements, each * element is a String. * <p> * In addition to the constructor, various methods exist to create derived * paths: StatePaths that are, in some sense, relative to an existing * StatePath; for examples of these, see the <tt>newChild()</tt> and * <tt>childPath()</tt> methods. * <p> * The constructor provides an easy method of creating a complex StatePath. * It will parse the String and split it at dot boundaries, forming the * path. Some paths may have elements that contain dots. To construct * corresponding StatePath representations, use the <tt>newChild()</tt> * method. * * @author Paul Millar <paul.millar@desy.de> */ public class StatePath { private static final int NULL_ELEMENT_HASH = 0xDEADBEAF; protected final List<String> _elements; private int _myHashCode; private boolean _haveHashCode; private String _toString; /** * Parse a dot-separated path to build a StatePath * @param path the path, as an ordered list of path elements, each element separated by a dot. * @return the corresponding StatePath. */ public static StatePath parsePath(String path) { String elements[] = path.split("\\."); return new StatePath(elements); } /** * Create a new StatePath that duplicates an existing one. * @param path the StatePath to copy. */ protected StatePath(StatePath path) { _elements = new ArrayList<>(path._elements); } /** * Create a new StatePath when given a List of path elements. * @param pathElements The List of path elements from which to construct the state path. * @return the corresponding StatePath */ public static StatePath buildFromList(List<String> pathElements ) { return new StatePath(pathElements, pathElements.size()); } /** * Create a new StatePath based on a List of path elements. * @param elements */ private StatePath(List<String> elements, int elementCount) { _elements = new ArrayList<>(elementCount); for (String element : elements) { _elements.add(element.intern()); } } /** * Build a new StatePath based on an array of elements. * @param elements the path elements, in order. */ protected StatePath (String[] elements) { _elements = new ArrayList<>(elements.length); for (String element : elements) { _elements.add(element.intern()); } } /** * Provide a new StatePath with a single path element. The result is the * same as new StatePath().newChild(name); * @param name: the name of the path element. */ public StatePath(String element) { _elements = new ArrayList<>(1); _elements.add(element != null ? element.intern() : null); } /** * Calculate the hash code and store it for later quick reference. */ void calcHashCode() { int code = 0; int elementCount = 0; for (String element : _elements) { int stringHash=0; if (element == null) { stringHash = NULL_ELEMENT_HASH; } else { // Since Java's String hashCode is so poor, spice it up a little. byte bytes[] = element.getBytes(); int len = bytes.length > 10 ? 10 : bytes.length; // limit length for (int i = 0; i < len; i++) { stringHash ^= (bytes[i]) << (i * 5 + elementCount) % 24; } } code ^= stringHash; elementCount++; } _myHashCode = code; _haveHashCode = true; } /** * Check whether another path points to the same location. * @param otherPath: the other path to compare * @return: whether the other path point to the same location. */ @Override public boolean equals(Object otherObject) { if (!(otherObject instanceof StatePath)) { return false; } if (otherObject == this) { return true; } StatePath otherPath = (StatePath) otherObject; if (otherPath._elements.size() != _elements.size()) { return false; } for (int i=0; i < _elements.size(); i++) { // Our use of intern() allows this if (otherPath._elements.get(i) != _elements.get(i)) { return false; } } return true; } /** * Overload the hashCode to honour the contract: * A.hashCode()==B.hashCode() => A.equals(B) */ @Override public int hashCode() { if (!_haveHashCode) { calcHashCode(); } return _myHashCode; } /** * Check whether otherPath points to the same location, or * is a child of this path. This is true iff each element of * this path is identical to the corresponding element in otherPath. * <pre> * StatePath p1 = new StatePath("foo.bar"); * StatePath p2 = new StatePath("foo.bar.baz"); * * p1.equalsOrHasChild(p1) // true * p2.equalsOrHasChild(p2) // true * p1.equalsOrHasChild(p2) // true * p2.equalsOrHasChild(p1) // false * </pre> * @param otherPath the potential child path * @return true if otherPath is a child of this path. */ public boolean equalsOrHasChild(StatePath otherPath) { /* * A StatePath variable with value null corresponds to * the root element of the info tree. All non-null * StatePath objects correspond to a location in the * info tree below the root element. * * When given null as an argument, the query is against * the root element. Any object where the * equalsOrHasChild() method is being called is not * null (obviously), therefore is not the root * element. */ if (otherPath == null) { return false; } // Check for an obviously mismatch. if (_elements.size() > otherPath._elements.size()) { return false; } for (int i = 0; i < _elements.size(); i++) { // We use intern()ed strings for this to work. if (_elements.get(i) != otherPath._elements.get(i)) { return false; } } return true; } /** * Check whether otherPath points to a location that is a child of this location. This * is true iff each element of this path is identical to the corresponding element in * otherPath and otherPath has length precisely greater by one. * <pre> * StatePath p1 = new StatePath("foo.bar"); * StatePath p2 = new StatePath("foo.bar.baz"); * StatePath p3 = new StatePath("foo.bar.baz.other"); * * p1.isParentOf(p1) // false * p1.isParentOf(p2) // true * p1.isParentOf(p3) // false * p2.isParentOf(p1) // false * p2.isParentOf(p2) // false * p2.isParentOf(p3) // true * </pre> * * @param otherPath * @return */ public boolean isParentOf(StatePath otherPath) { if (otherPath == null) { return false; } if ((_elements.size() + 1) != otherPath._elements.size()) { return false; } for (int i = 0; i < _elements.size(); i++) { // intern()ed Strings allows this. if (_elements.get(i) != otherPath._elements.get(i)) { return false; } } return true; } /** * Convert a StatePath to it's corresponding string value. This * is identical to calling toString("."); */ @Override public String toString() { if (_toString == null) { _toString = toString(".", 0); } return _toString; } /** * Convert a StatePath to a String representation. Each element is * separated by the separator String. * @param separator the String to separate each path element * @param don't display this number of initial elements * @return the String representation. */ public String toString(String separator, int count) { StringBuilder out = new StringBuilder(); int i=0; for (String e : _elements) { if (i >= count) { if (i > count) { out.append(separator); } out.append(e); } i++; } return out.toString(); } /** * Create a String representing this state with some initial elements removed. * The number of suppressed initial elements is the same as the number of * elements in the prefix StatePath * @param prefix the prefix to remove. * @return a string representation */ public String toString(StatePath prefix) { int count = prefix != null ? prefix._elements.size() : 0; return toString(".", count); } /** * @return the first element of the path. */ public String getFirstElement() { return _elements.get(0); } /** * @return the last element of the path. */ public String getLastElement() { return _elements.get(_elements.size()-1); } /** * Create a new StatePath with an extra path element. This method does no * splitting of the parameter: it is safe to pass a String with dots. * <p> * If you want to create a newChild with dot-splitting, one solution is * to first create a StatePath with the new path: * <p> * <pre> * path = path.newChild(StatePath.parsePath(pathWithDots)); * </pre> * * @param element: the name of the child path element * @return a new StatePath with the additional, final path element */ public StatePath newChild(String element) { StatePath newPath = new StatePath(_elements, _elements.size() + 1); newPath._elements.add(element.intern()); return newPath; } /** * Create a new StatePath with extra path elements; for example, if the path is * representing <tt>aa.bb</tt>, then <tt>path.newChild("cc")</tt> will return * a new StatePath representing <tt>aa.bb.cc</tt> * <p> * @param subPath: the extra path elements to append. * @return a new StatePath with a combined path. */ public StatePath newChild(StatePath subPath) { StatePath newPath = new StatePath(_elements, _elements.size() + subPath._elements.size()); newPath._elements.addAll(subPath._elements); return newPath; } /** * Build a new StatePath that points to the same location from the immediate * child's point-of-view. For example, if the * current path is characterised as <tt>aa.bb.cc</tt>, then * the returned StatePath is characterised by <tt>bb.cc</tt>. * <p> * If the path has no children of children, null is returned. * * @return the path for the child element, or null if there is no child. */ public StatePath childPath() { if (_elements == null || _elements.size() <= 1) { return null; } return new StatePath(_elements.subList(1, _elements.size()), _elements.size()-1); } /** * Build a new StatePath that points to this StatePath node's parent node; for example, * if <tt>path</tt> is characterised by <tt>aa.bb.cc</tt> then <tt>path.parentPath()</tt> * returns a new StatePath that is characterised by <tt>aa.bb</tt> * @return the new StatePath, pointing to the parent, or null if the node has no parent. */ public StatePath parentPath() { if (_elements.size() <= 1) { return null; } return new StatePath(_elements.subList(0, _elements.size()-1), _elements.size()-1); } /** * Check whether this path contains any branches; i.e., if the number of elements * in the path is strictly greater than one. * @return true if the path contains no branches, false otherwise. */ public boolean isSimplePath() { return _elements.size() == 1; } }