org.modeshape.jcr.value.Path Maven / Gradle / Ivy
/*
* 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.
*
* 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).
*
*
* 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:
*
* - {@link #JSR283_ENCODER Jsr283Encoder} - an encoder and decoder that is compliant with JSR-283 by converting the reserved characters (namely '*', '/', ':', '[', ']'
* and '|') to their unicode equivalent.
*
* - {@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 RFC 2396. 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.
* - {@link #NO_OP_ENCODER NoOpEncoder} - an {@link TextEncoder encoder} implementation that does nothing.
*
*
*
* This class simplifies working with paths and using a Path
is often more efficient that processing and
* manipulating the equivalent String
. 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}.
*
*/
@Immutable
public interface Path extends Comparable, Iterable, 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 RFC 2396.
*/
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 RFC 2396.
*/
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, 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 getString(Path.NO_OP_ENCODER)
.
*
* @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
* this.compareTo(other) == 0
.
*
* @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 isSame(other) ||
* isAncestor(other)
, and is a convenience method that is identical to calling other.isAtOrBelow(this)
.
*
* @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 isSame(other) ||
* isAncestor(other)
.
*
* @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 x
is the path that is x
*
levels up along the path. For example, degree = 0
returns this path, while degree = 1
* returns the parent of this path, degree = 2
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 beginIndex
index (inclusive). This is equivalent to
* calling path.subpath(beginIndex,path.size()-1)
.
*
* @param beginIndex the beginning index, inclusive.
* @return the specified subpath
* @exception IndexOutOfBoundsException if the beginIndex
is negative or larger than the length of this
* Path
object
*/
public Path subpath( int beginIndex );
/**
* Return a new path consisting of the segments between the beginIndex
index (inclusive) and the endIndex
*
index (exclusive).
*
* @param beginIndex the beginning index, inclusive.
* @param endIndex the ending index, exclusive.
* @return the specified subpath
* @exception IndexOutOfBoundsException if the beginIndex
is negative, or endIndex
is larger than
* the length of this Path
object, or beginIndex
is larger than endIndex
.
*/
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 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 getSegmentsList();
}