/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.jcr.value; import java.io.Serializable; import java.util.Iterator; import java.util.List; import org.modeshape.common.annotation.Immutable; import org.modeshape.common.text.Jsr283Encoder; import org.modeshape.common.text.NoOpEncoder; import org.modeshape.common.text.TextDecoder; import org.modeshape.common.text.TextEncoder; import org.modeshape.common.text.UrlEncoder; import org.modeshape.jcr.value.basic.BasicName; import org.modeshape.jcr.value.basic.BasicPathSegment; import org.modeshape.jcr.value.basic.RootPath; /** * An object representation of a node path within a repository. * <p> * A path consists of zero or more segments that can contain any characters, although the string representation may require some * characters to be encoded. For example, if a path contains a segment with a forward slash, then this forward slash must be * escaped when writing the whole path to a string (since a forward slash is used as the {@link #DELIMITER delimiter} between * segments). * </p> * <p> * Because of this encoding and decoding issue, there is no standard representation of a path as a string. Instead, this class * uses {@link TextEncoder text encoders} to escape certain characters when writing to a string or unescaping the string * representation. These encoders and used only with individual segments, and therefore are not used to encode the * {@link #DELIMITER delimiter}. Three standard encoders are provided, although others can certainly be used: * <ul> * <li>{@link #JSR283_ENCODER Jsr283Encoder} - an encoder and decoder that is compliant with <a * href="http://jcp.org/en/jsr/detail?id=283">JSR-283</a> by converting the reserved characters (namely '*', '/', ':', '[', ']' * and '|') to their unicode equivalent.</td> * </li> * <li>{@link #URL_ENCODER UrlEncoder} - an encoder and decoder that is useful for converting text to be used within a URL, as * defined by Section 2.3 of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. This encoder does encode many characters * (including '`', '@', '#', '$', '^', '&', '{', '[', '}', ']', '|', ':', ';', '\', '"', '<', ',', '>', '?', '/', and ' '), while * others are not encoded (including '-', '_', '.', '!', '~', '*', '\', ''', '(', and ')'). Note that only the '*' character is * the only character reserved by JSR-283 that is not encoded by the URL encoder.</li> * <li>{@link #NO_OP_ENCODER NoOpEncoder} - an {@link TextEncoder encoder} implementation that does nothing.</li> * </ul> * </p> * <p> * This class simplifies working with paths and using a <code>Path</code> is often more efficient that processing and * manipulating the equivalent <code>String</code>. This class can easily {@link #iterator() iterate} over the segments, return * the {@link #size() number of segments}, {@link #compareTo(Path) compare} with other paths, {@link #resolve(Path) resolve} * relative paths, return the {@link #getParent() ancestor (or parent)}, determine whether one path is an * {@link #isAncestorOf(Path) ancestor} or {@link #isDescendantOf(Path) decendent} of another path, and * {@link #getCommonAncestor(Path) finding a common ancestor}. * </p> */ @Immutable public interface Path extends Comparable<Path>, Iterable<Path.Segment>, Serializable, Readable { /** * The text encoder that does nothing. */ public static final TextEncoder NO_OP_ENCODER = new NoOpEncoder(); /** * The text encoder that encodes according to JSR-283. */ public static final TextEncoder JSR283_ENCODER = new Jsr283Encoder(); /** * The text encoder that encodes text according to the rules of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. */ public static final TextEncoder URL_ENCODER = new UrlEncoder().setSlashEncoded(true); /** * The text decoder that does nothing. */ public static final TextDecoder NO_OP_DECODER = new NoOpEncoder(); /** * The text decoder that decodes according to JSR-283. */ public static final TextDecoder JSR283_DECODER = new Jsr283Encoder(); /** * The text decoder that decodes text according to the rules of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. */ public static final TextDecoder URL_DECODER = new UrlEncoder().setSlashEncoded(true); /** * The default text encoder to be used when none is otherwise specified. This is currently the {@link #JSR283_ENCODER JSR-283 * encoder}. */ public static final TextEncoder DEFAULT_ENCODER = JSR283_ENCODER; /** * The default text decoder to be used when none is otherwise specified. This is currently the {@link #JSR283_ENCODER JSR-283 * encoder}. */ public static final TextDecoder DEFAULT_DECODER = JSR283_DECODER; /** * The delimiter character used to separate segments within a path. */ public static final char DELIMITER = '/'; /** * The character used to begin an identifier segment within a path. */ public static final char IDENTIFIER_LEADING_TERMINAL = '['; /** * The character used to end an identifier segment within a path. */ public static final char IDENTIFIER_TRAILING_TERMINAL = ']'; /** * String form of the delimiter used to separate segments within a path. */ public static final String DELIMITER_STR = new String(new char[] {DELIMITER}); /** * String representation of the segment that references a parent. */ public static final String PARENT = ".."; /** * String representation of the segment that references the same segment. */ public static final String SELF = "."; /** * The default index for a {@link Segment}. */ public static final int DEFAULT_INDEX = 1; /** * Representation of the segments that occur within a path. * * @author Randall Hauch */ @Immutable public static interface Segment extends Cloneable, Comparable<Segment>, Serializable, Readable { /** * Get the name component of this segment. * * @return the segment's name */ public Name getName(); /** * Get the index for this segment, which will be 1 by default. * * @return the index */ public int getIndex(); /** * Return whether this segment has an index. * * @return true if this segment has an index, or false otherwise. */ public boolean hasIndex(); /** * Return whether this segment is a self-reference. * * @return true if the segment is a self-reference, or false otherwise. */ public boolean isSelfReference(); /** * Return whether this segment is a reference to a parent. * * @return true if the segment is a parent-reference, or false otherwise. */ public boolean isParentReference(); /** * Return whether this segment is an identifier segment. * * @return true if the segment is an identifier segment, or false otherwise. */ public boolean isIdentifier(); /** * Get the raw string form of the segment using the {@link Path#NO_OP_ENCODER no-op encoder}. This is equivalent to * calling <code>getString(Path.NO_OP_ENCODER)</code>. * * @return the un-encoded string * @see #getString(TextEncoder) */ public String getUnencodedString(); } /** * Singleton instance of the root name. */ public static final Name ROOT_NAME = new BasicName(null, ""); /** * Singleton instance of the name referencing a self, provided as a convenience. */ public static final Name SELF_NAME = new BasicName(null, SELF); /** * Singleton instance of the name referencing a parent, provided as a convenience. */ public static final Name PARENT_NAME = new BasicName(null, PARENT); /** * Singleton instance of the path segment referencing a parent, provided as a convenience. */ public static final Path.Segment SELF_SEGMENT = new BasicPathSegment(SELF_NAME); /** * Singleton instance of the path segment referencing a parent, provided as a convenience. */ public static final Path.Segment PARENT_SEGMENT = new BasicPathSegment(PARENT_NAME); /** * Singleton instance of the root path. */ public static final Path ROOT_PATH = RootPath.INSTANCE; /** * Return the number of segments in this path. * * @return the number of path segments */ public int size(); /** * Return whether this path represents the root path. * * @return true if this path is the root path, or false otherwise */ public boolean isRoot(); /** * Returns whether this path represents an identifier path. An identifier path is absolute, and contains a single * {@link Segment#isIdentifier() identifier Segment} that contains an identifier in the name. * * @return true if this path is an identifier path, or false otherwise. */ public boolean isIdentifier(); /** * Determine whether this path represents the same as the supplied path. This is equivalent to calling <code> * this.compareTo(other) == 0 </code> . * * @param other the other path to compare with this path; may be null * @return true if the paths are equivalent, or false otherwise */ public boolean isSameAs( Path other ); /** * Determine whether this path is the {@link #isSameAs(Path) same as} to or a {@link #isAncestorOf(Path) ancestor of} the * supplied path. This method is equivalent to (but may be more efficient than) calling <code>isSame(other) || * isAncestor(other)</code>, and is a convenience method that is identical to calling <code>other.isAtOrBelow(this)</code>. * * @param other the other path to compare with this path; may be null * @return true if the paths are equivalent or if this path is considered an ancestor of the other path, or false otherwise */ public boolean isAtOrAbove( Path other ); /** * Determine whether this path is the {@link #isSameAs(Path) same as} to or a {@link #isDescendantOf(Path) descendant of} the * supplied path. This method is equivalent to (but may be more efficient than) calling <code>isSame(other) || * isAncestor(other)</code>. * * @param other the other path to compare with this path; may be null * @return true if the paths are equivalent or if this path is considered a descendant of the other path, or false otherwise */ public boolean isAtOrBelow( Path other ); /** * Determine whether this path is an ancestor of the supplied path. A path is considered an ancestor of another path if the * the ancestor path appears in its entirety at the beginning of the descendant path, and where the descendant path contains at * least one additional segment. * * @param descendant the path that may be the descendant; may be null * @return true if this path is an ancestor of the supplied path, or false otherwise */ public boolean isAncestorOf( Path descendant ); /** * Determine whether this path is an descendant of the supplied path. A path is considered a descendant of another path if the * the descendant path starts exactly with the entire ancestor path but contains at least one additional segment. * * @param ancestor the path that may be the ancestor; may be null * @return true if this path is an descendant of the supplied path, or false otherwise */ public boolean isDescendantOf( Path ancestor ); /** * Return whether this path is an absolute path. A path is either relative or {@link #isAbsolute() absolute}. An absolute path * starts with a "/". * * @return true if the path is absolute, or false otherwise */ public boolean isAbsolute(); /** * Return whether this path is normalized and contains no unnecessary "." segments and as few ".." segments as possible. For * example, the path "../a" is normalized, while "/a/b/c/../d" is not normalized. * * @return true if this path is normalized, or false otherwise */ public boolean isNormalized(); /** * Get a normalized path with as many ".." segments and all "." resolved. The relative path ".", however, will return itself * as the normalized path, since it cannot be resolved any further. * * @return the normalized path, or this object if this path is already normalized * @throws InvalidPathException if the normalized form would result in a path with negative length (e.g., "/a/../../..") */ public Path getNormalizedPath(); /** * Get the canonical form of this path. A canonical path has is {@link #isAbsolute() absolute} and {@link #isNormalized()}. * * @return the canonical path, or this object if it is already in its canonical form * @throws InvalidPathException if the path is not absolute and cannot be canonicalized */ public Path getCanonicalPath(); /** * Obtain a path that is relative to the root node. This is equivalent to calling {@link #relativeTo(Path)} with the root * path. * * @return the relative path from the root node; never null */ public Path relativeToRoot(); /** * Get a relative path from the supplied path to this path. * * @param startingPath the path specifying the starting point for the new relative path; may not be null * @return the relative path * @throws IllegalArgumentException if the supplied path is null * @throws PathNotFoundException if both this path and the supplied path are not absolute */ public Path relativeTo( Path startingPath ); /** * Get the absolute path by resolving the supplied relative (non-absolute) path against this absolute path. * * @param relativePath the relative path that is to be resolved against this path * @return the absolute and normalized path resolved from this path and the supplied absolute path * @throws IllegalArgumentException if the supplied path is null * @throws InvalidPathException if the this path is not absolute or if the supplied path is not relative. */ public Path resolve( Path relativePath ); /** * Get the absolute path by resolving this relative (non-absolute) path against the supplied absolute path. * * @param absolutePath the absolute path to which this relative path should be resolve * @return the absolute path resolved from this path and the supplied absolute path * @throws IllegalArgumentException if the supplied path is null * @throws InvalidPathException if the supplied path is not absolute or if this path is not relative. */ public Path resolveAgainst( Path absolutePath ); /** * Return the path to the parent, or this path if it is the {@link #isRoot() root}. This is an efficient operation that does * not require copying any data. * * @return the parent path, or this null if it is already the root */ public Path getParent(); /** * Return the path to the ancestor of the supplied degree. An ancestor of degree <code>x</code> is the path that is <code>x * </code> levels up along the path. For example, <code>degree = 0</code> returns this path, while <code>degree = 1</code> * returns the parent of this path, <code>degree = 2</code> returns the grandparent of this path, and so on. Note that the * result may be unexpected if this path is not {@link #isNormalized() normalized}, as a non-normalized path contains ".." and * "." segments. * * @param degree * @return the ancestor of the supplied degree * @throws IllegalArgumentException if the degree is negative * @throws InvalidPathException if the degree is greater than the {@link #size() length} of this path */ public Path getAncestor( int degree ); /** * Determine whether this path and the supplied path have the same immediate ancestor. In other words, this method determines * whether the node represented by this path is a sibling of the node represented by the supplied path. * * @param that the other path * @return true if this path and the supplied path have the same immediate ancestor. * @throws IllegalArgumentException if the supplied path is null */ public boolean hasSameAncestor( Path that ); /** * Find the lowest common ancestor of this path and the supplied path. * * @param that the other path * @return the lowest common ancestor, which may be the root path if there is no other. * @throws IllegalArgumentException if the supplied path is null */ public Path getCommonAncestor( Path that ); /** * Get the last segment in this path. * * @return the last segment, or null if the path is empty */ public Segment getLastSegment(); /** * Determine if the path's {@link #getLastSegment()} has the supplied name and no {@link Segment#getIndex() SNS index}. * * @param nameOfLastSegment the name * @return return true if the supplied name is the name in the path's last segment (and there is no SNS index), or false * otherwise */ public boolean endsWith( Name nameOfLastSegment ); /** * Determine if the path's {@link #getLastSegment()} has the supplied name and {@link Segment#getIndex() SNS index}. * * @param nameOfLastSegment the name * @param snsIndex the SNS index * @return return true if the path's last segment has the supplied name and SNS, or false otherwise */ public boolean endsWith( Name nameOfLastSegment, int snsIndex ); /** * Get the segment at the supplied index. * * @param index the index * @return the segment * @throws IndexOutOfBoundsException if the index is out of bounds */ public Segment getSegment( int index ); /** * Return a new path consisting of the segments starting at <code>beginIndex</code> index (inclusive). This is equivalent to * calling <code>path.subpath(beginIndex,path.size()-1)</code>. * * @param beginIndex the beginning index, inclusive. * @return the specified subpath * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative or larger than the length of this <code> * Path</code> object */ public Path subpath( int beginIndex ); /** * Return a new path consisting of the segments between the <code>beginIndex</code> index (inclusive) and the <code>endIndex * </code> index (exclusive). * * @param beginIndex the beginning index, inclusive. * @param endIndex the ending index, exclusive. * @return the specified subpath * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative, or <code>endIndex</code> is larger than * the length of this <code>Path</code> object, or <code>beginIndex</code> is larger than <code>endIndex</code>. */ public Path subpath( int beginIndex, int endIndex ); /** * Return an iterator that walks the paths from the root path down to this path. This method always returns at least one path * (the root returns an iterator containing itself). * * @return the path iterator; never null */ public Iterator<Path> pathsFromRoot(); /** * Obtain a copy of the segments in this path. None of the segments are encoded. * * @return the array of segments as a copy */ public Segment[] getSegmentsArray(); /** * Get an unmodifiable list of the path segments. * * @return the unmodifiable list of path segments; never null */ public List<Segment> getSegmentsList(); }