You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by st...@apache.org on 2005/07/04 16:52:27 UTC
svn commit: r209089 [2/7] - in /incubator/jackrabbit/trunk: commons/
commons/src/java/org/apache/jackrabbit/
commons/src/java/org/apache/jackrabbit/core/
commons/src/java/org/apache/jackrabbit/name/
commons/src/java/org/apache/jackrabbit/util/ commons/...
Added: incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/Path.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/Path.java?rev=209089&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/Path.java (added)
+++ incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/Path.java Mon Jul 4 07:52:19 2005
@@ -0,0 +1,1492 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ * as applicable.
+ *
+ * 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.apache.jackrabbit.name;
+
+import org.apache.jackrabbit.Constants;
+import org.apache.jackrabbit.util.Text;
+import org.apache.xerces.util.XMLChar;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.PathNotFoundException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The <code>Path</code> utility class provides misc. methods to resolve and
+ * nornalize JCR-style item paths.
+ * <p/>
+ * Each path consistnes of path elements and is immutable. It has the following
+ * properties:
+ * <p/>
+ * <code>isAbsolute()</code>:<br>
+ * A path is absolute if the first path element denotes the root element '/'.
+ * <p/>
+ * <code>isNormalized()</code>:<br>
+ * A path is normalized if all '.' and '..' path elements are resolved as much
+ * as possible. If the path is absolute it is normalized if it contains
+ * no such elements. For example the path '../../a' is normalized where as
+ * '../../b/../a/.' is not. Normalized paths never have '.' elements.
+ * Absolute normalized paths have no and relative normalized paths have no or
+ * only leading '..' elements.
+ * <p/>
+ * <code>isCanonical()</code>:<br>
+ * A path is canonical if its absolute and normalized.
+ *
+ * <h2>String representations</h2>
+ * <p/>
+ * The JCR path format is specified by JSR 170 as follows:
+ * <pre>
+ * path ::= properpath ['/']
+ * properpath ::= abspath | relpath
+ * abspath ::= '/' relpath
+ * relpath ::= pathelement | relpath '/' pathelement
+ * pathelement ::= name | name '[' number ']' | '..' | '.'
+ * number ::= (* An integer > 0 *)
+ * name ::= simplename | prefixedname
+ * simplename ::= onecharsimplename |
+ * twocharsimplename |
+ * threeormorecharname
+ * prefixedname ::= prefix ':' localname
+ * localname ::= onecharlocalname |
+ * twocharlocalname |
+ * threeormorecharname
+ * onecharsimplename ::= (* Any Unicode character except:
+ * '.', '/', ':', '[', ']', '*',
+ * ''', '"', '|' or any whitespace
+ * character *)
+ * twocharsimplename ::= '.' onecharsimplename |
+ * onecharsimplename '.' |
+ * onecharsimplename onecharsimplename
+ * onecharlocalname ::= nonspace
+ * twocharlocalname ::= nonspace nonspace
+ * threeormorecharname ::= nonspace string nonspace
+ * prefix ::= (* Any valid XML Name *)
+ * string ::= char | string char
+ * char ::= nonspace | ' '
+ * nonspace ::= (* Any Unicode character except:
+ * '/', ':', '[', ']', '*',
+ * ''', '"', '|' or any whitespace
+ * character *)
+ * </pre>
+ */
+public final class Path {
+
+ /**
+ * the 'root' element. i.e. '/'
+ */
+ private static final PathElement ROOT_ELEMENT = new RootElement();
+
+ /**
+ * the 'current' element. i.e. '.'
+ */
+ private static final PathElement CURRENT_ELEMENT = new CurrentElement();
+
+ /**
+ * the 'parent' element. i.e. '..'
+ */
+ private static final PathElement PARENT_ELEMENT = new ParentElement();
+
+ /**
+ * the root path
+ */
+ public static final Path ROOT = new Path(new PathElement[]{ROOT_ELEMENT}, true);
+
+ /**
+ * Pattern used to validate and parse path elements:<p>
+ * <ul>
+ * <li>group 1 is .
+ * <li>group 2 is ..
+ * <li>group 3 is namespace prefix incl. delimiter (colon)
+ * <li>group 4 is namespace prefix excl. delimiter (colon)
+ * <li>group 5 is localName
+ * <li>group 6 is index incl. brackets
+ * <li>group 7 is index excl. brackets
+ * </ul>
+ */
+ private static final Pattern PATH_ELEMENT_PATTERN =
+ Pattern.compile("(\\.)|"
+ + "(\\.\\.)|"
+ + "(([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?):)?"
+ + "([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?)"
+ + "(\\[([1-9]\\d*)\\])?");
+
+ /**
+ * the elements of this path
+ */
+ private final PathElement[] elements;
+
+ /**
+ * flag indicating if this path is normalized
+ */
+ private final boolean normalized;
+
+ /**
+ * flag indicating if this path is absolute
+ */
+ private final boolean absolute;
+
+ /**
+ * the cached hashcode of this path
+ */
+ private int hash = 0;
+
+ /**
+ * the cached 'toString' of this path
+ */
+ private String string;
+
+ /**
+ * Private constructor
+ *
+ * @param elements
+ * @param isNormalized
+ */
+ private Path(PathElement[] elements, boolean isNormalized) {
+ if (elements == null || elements.length == 0) {
+ throw new IllegalArgumentException("Empty paths are not allowed");
+ }
+ this.elements = elements;
+ this.absolute = elements[0].denotesRoot();
+ this.normalized = isNormalized;
+ }
+
+ //------------------------------------------------------< factory methods >
+ /**
+ * Creates a new <code>Path</code> from the given <code>jcrPath</code>
+ * string. If <code>normalize</code> is <code>true</code>, the returned
+ * path will be normalized (or canonicalized if absolute).
+ *
+ * @param jcrPath
+ * @param resolver
+ * @param normalize
+ * @return
+ * @throws MalformedPathException
+ */
+ public static Path create(String jcrPath, NamespaceResolver resolver,
+ boolean normalize)
+ throws MalformedPathException {
+ Path path = parse(jcrPath, null, resolver);
+ if (normalize) {
+ return path.getNormalizedPath();
+ } else {
+ return path;
+ }
+ }
+
+ /**
+ * Creates a new <code>Path</code> out of the given <code>parent</code> path
+ * and a relative path string. If <code>canonicalize</code> is
+ * <code>true</code>, the returned path will be canonicalized.
+ *
+ * @param parent
+ * @param relJCRPath
+ * @param resolver
+ * @param canonicalize
+ * @return
+ * @throws MalformedPathException
+ */
+ public static Path create(Path parent, String relJCRPath,
+ NamespaceResolver resolver, boolean canonicalize)
+ throws MalformedPathException {
+ Path path = parse(relJCRPath, parent, resolver);
+ if (canonicalize) {
+ return path.getCanonicalPath();
+ } else {
+ return path;
+ }
+ }
+
+ /**
+ * Creates a new <code>Path</code> out of the given <code>parent<code> path
+ * string and the given relative path string. If <code>normalize</code> is
+ * <code>true</code>, the returned path will be normalized (or
+ * canonicalized, if the parent path is absolute).
+ *
+ * @param parent
+ * @param relPath
+ * @param normalize
+ * @return
+ * @throws MalformedPathException
+ */
+ public static Path create(Path parent, Path relPath, boolean normalize)
+ throws MalformedPathException {
+ if (relPath.isAbsolute()) {
+ throw new MalformedPathException("relPath is not a relative path");
+ }
+
+ PathBuilder pb = new PathBuilder(parent.getElements());
+ pb.addAll(relPath.getElements());
+
+ Path path = pb.getPath();
+ if (normalize) {
+ return path.getNormalizedPath();
+ } else {
+ return path;
+ }
+ }
+
+ /**
+ * Creates a new <code>Path</code> out of the given <code>parent<code> path
+ * string and the give name. If <code>normalize</code> is <code>true</code>,
+ * the returned path will be normalized (or canonicalized, if the parent
+ * path is absolute).
+ *
+ * @param parent
+ * @param name
+ * @param normalize
+ * @return
+ * @throws MalformedPathException
+ */
+ public static Path create(Path parent, QName name, boolean normalize)
+ throws MalformedPathException {
+ PathBuilder pb = new PathBuilder(parent.getElements());
+ pb.addLast(name);
+
+ Path path = pb.getPath();
+ if (normalize) {
+ return path.getNormalizedPath();
+ } else {
+ return path;
+ }
+ }
+
+ /**
+ * Creates a new <code>Path</code> out of the given <code>parent<code> path
+ * and the give name and index. If <code>normalize</code> is
+ * <code>true</code>, the returned path will be normalized
+ * (or canonicalized, if the parent path is absolute).
+ *
+ * @param parent
+ * @param name
+ * @param index
+ * @param normalize
+ * @return
+ * @throws MalformedPathException
+ */
+ public static Path create(Path parent, QName name, int index,
+ boolean normalize)
+ throws MalformedPathException {
+ PathBuilder pb = new PathBuilder(parent.getElements());
+ pb.addLast(name, index);
+
+ Path path = pb.getPath();
+ if (normalize) {
+ return path.getNormalizedPath();
+ } else {
+ return path;
+ }
+ }
+
+ /**
+ * Creates a relative path based on a {@link QName} and an index.
+ *
+ * @param name single {@link QName} for this relative path.
+ * @param index index of the sinlge name element.
+ * @return the relative path created from <code>name</code>.
+ * @throws IllegalArgumentException if <code>index</code> is negative.
+ */
+ public static Path create(QName name, int index)
+ throws IllegalArgumentException {
+ if (index < 0) {
+ throw new IllegalArgumentException("index must not be negative: " + index);
+ }
+ PathElement elem;
+ if (index < 1) {
+ elem = new PathElement(name);
+ } else {
+ elem = new PathElement(name, index);
+ }
+ return new Path(new PathElement[]{elem}, !elem.equals(CURRENT_ELEMENT));
+ }
+
+ //-------------------------------------------------------< implementation >
+ /**
+ * Parses the give string an d returns an array of path elements. if
+ * <code>master</code> is not <code>null</code>, it is prepended to the
+ * returned list. If <code>resolver</code> is <code>null</code>, this
+ * method only checks the format of the string and returns <code>null</code>.
+ *
+ * @param jcrPath
+ * @param master
+ * @param resolver
+ * @return
+ * @throws MalformedPathException
+ */
+ private static Path parse(String jcrPath, Path master, NamespaceResolver resolver)
+ throws MalformedPathException {
+ // shortcut
+ if ("/".equals(jcrPath)) {
+ return ROOT;
+ }
+
+ // split path into path elements
+ String[] elems = Text.explode(jcrPath, '/', true);
+ if (elems.length == 0) {
+ throw new MalformedPathException("empty path");
+ }
+
+ ArrayList list = new ArrayList();
+ boolean isNormalized = true;
+ boolean leadingParent = true;
+ if (master != null) {
+ isNormalized = master.normalized;
+ // a master path was specified; the 'path' argument is assumed
+ // to be a relative path
+ for (int i = 0; i < master.elements.length; i++) {
+ list.add(master.elements[i]);
+ leadingParent &= master.elements[i].denotesParent();
+ }
+ }
+
+ for (int i = 0; i < elems.length; i++) {
+ // validate & parse path element
+ String prefix;
+ String localName;
+ int index;
+
+ String elem = elems[i];
+ if (i == 0 && elem.length() == 0) {
+ // path is absolute, i.e. the first element is the root element
+ if (!list.isEmpty()) {
+ throw new MalformedPathException("'" + jcrPath + "' is not a relative path");
+ }
+ list.add(ROOT_ELEMENT);
+ leadingParent = false;
+ continue;
+ }
+ Matcher matcher = PATH_ELEMENT_PATTERN.matcher(elem);
+ if (matcher.matches()) {
+ if (resolver == null) {
+ // check only
+ continue;
+ }
+
+ if (matcher.group(1) != null) {
+ // group 1 is .
+ list.add(CURRENT_ELEMENT);
+ leadingParent = false;
+ isNormalized = false;
+ } else if (matcher.group(2) != null) {
+ // group 2 is ..
+ list.add(PARENT_ELEMENT);
+ isNormalized &= leadingParent;
+ } else {
+ // element is a name
+
+ // check for prefix (group 3)
+ if (matcher.group(3) != null) {
+ // prefix specified
+ // group 4 is namespace prefix excl. delimiter (colon)
+ prefix = matcher.group(4);
+ // check if the prefix is a valid XML prefix
+ if (!XMLChar.isValidNCName(prefix)) {
+ // illegal syntax for prefix
+ throw new MalformedPathException("'" + jcrPath + "' is not a valid path: '"
+ + elem + "' specifies an illegal namespace prefix");
+ }
+ } else {
+ // no prefix specified
+ prefix = "";
+ }
+
+ // group 5 is localName
+ localName = matcher.group(5);
+
+ // check for index (group 6)
+ if (matcher.group(6) != null) {
+ // index specified
+ // group 7 is index excl. brackets
+ index = Integer.parseInt(matcher.group(7));
+ } else {
+ // no index specified
+ index = 0;
+ }
+
+ String nsURI;
+ try {
+ nsURI = resolver.getURI(prefix);
+ } catch (NamespaceException nse) {
+ // unknown prefix
+ throw new MalformedPathException("'" + jcrPath + "' is not a valid path: '"
+ + elem + "' specifies an unmapped namespace prefix");
+ }
+
+ PathElement element;
+ if (index == 0) {
+ element = new PathElement(nsURI, localName);
+ } else {
+ element = new PathElement(nsURI, localName, index);
+ }
+ list.add(element);
+ leadingParent = false;
+ }
+ } else {
+ // illegal syntax for path element
+ throw new MalformedPathException("'" + jcrPath + "' is not a valid path: '"
+ + elem + "' is not a legal path element");
+ }
+ }
+ if (resolver != null) {
+ return new Path((PathElement[]) list.toArray(new PathElement[list.size()]),
+ isNormalized);
+ } else {
+ return null;
+ }
+ }
+
+ //------------------------------------------------------< utility methods >
+ /**
+ * Checks if <code>jcrPath</code> is a valid JCR-style absolute or relative
+ * path.
+ *
+ * @param jcrPath the path to be checked
+ * @throws MalformedPathException If <code>jcrPath</code> is not a valid
+ * JCR-style path.
+ */
+ public static void checkFormat(String jcrPath) throws MalformedPathException {
+ parse(jcrPath, null, null);
+ }
+
+ //-------------------------------------------------------< public methods >
+ /**
+ * Tests whether this path represents the root path, i.e. "/".
+ *
+ * @return true if this path represents the root path; false otherwise.
+ */
+ public boolean denotesRoot() {
+ return absolute && elements.length == 1;
+ }
+
+ /**
+ * Tests whether this path is absolute, i.e. whether it starts with "/".
+ *
+ * @return true if this path is absolute; false otherwise.
+ */
+ public boolean isAbsolute() {
+ return absolute;
+ }
+
+ /**
+ * Tests whether this path is canonical, i.e. whether it is absolute and
+ * does not contain redundant elements such as "." and "..".
+ *
+ * @return true if this path is canonical; false otherwise.
+ * @see #isAbsolute()
+ */
+ public boolean isCanonical() {
+ return absolute && normalized;
+ }
+
+ /**
+ * Tests whether this path is normalized, i.e. whether it does not
+ * contain redundant elements such as "." and "..".
+ * <p/>
+ * Note that a normalized path can still contain ".." elements if they are
+ * not redundant, e.g. "../../a/b/c" would be a normalized relative path,
+ * whereas "../a/../../a/b/c" wouldn't (although they're semantically
+ * equivalent).
+ *
+ * @return true if this path is normalized; false otherwise.
+ * @see #getNormalizedPath()
+ */
+ public boolean isNormalized() {
+ return normalized;
+ }
+
+ /**
+ * Returns the normalized path representation of this path. This typically
+ * involves removing/resolving redundant elements such as "." and ".." from
+ * the path, e.g. "/a/./b/.." will be normalized to "/a", "../../a/b/c/.."
+ * will be normalized to "../../a/b", and so on.
+ * <p/>
+ * If the normalized path results in an empty path (eg: 'a/..') or if an
+ * absolute path is normalized that would result in a 'negative' path
+ * (eg: /a/../../) a MalformedPathException is thrown.
+ *
+ * @return a normalized path representation of this path
+ * @throws MalformedPathException if the path cannot be normalized.
+ * @see #isNormalized()
+ */
+ public Path getNormalizedPath() throws MalformedPathException {
+ if (isNormalized()) {
+ return this;
+ }
+ LinkedList queue = new LinkedList();
+ PathElement last = null;
+ for (int i = 0; i < elements.length; i++) {
+ PathElement elem = elements[i];
+ if (elem.denotesCurrent()) {
+ continue;
+ } else if (elem.denotesParent() && last != null && !last.denotesParent()) {
+ if (last.denotesRoot()) {
+ // the first element is the root element;
+ // ".." would refer to the parent of root
+ throw new MalformedPathException("Path can not be canonicalized: unresolvable '..' element");
+ }
+ queue.removeLast();
+ if (queue.isEmpty()) {
+ last = null;
+ } else {
+ last = (PathElement) queue.getLast();
+ }
+ } else {
+ last = elem;
+ queue.add(elem);
+ }
+ }
+ if (queue.isEmpty()) {
+ throw new MalformedPathException("Path can not be normalized: would result in an empty path.");
+ }
+ return new Path((PathElement[]) queue.toArray(new PathElement[queue.size()]), true);
+ }
+
+ /**
+ * Returns the canonical path representation of this path. This typically
+ * involves removing/resolving redundant elements such as "." and ".." from
+ * the path.
+ *
+ * @return a canonical path representation of this path
+ * @throws MalformedPathException if this path can not be canonicalized
+ * (e.g. if it is relative)
+ */
+ public Path getCanonicalPath() throws MalformedPathException {
+ if (isCanonical()) {
+ return this;
+ }
+ if (!isAbsolute()) {
+ throw new MalformedPathException("only an absolute path can be canonicalized.");
+ }
+ return getNormalizedPath();
+ }
+
+ /**
+ * Computes the relative path from <code>this</code> absolute path to
+ * <code>other</code>.
+ *
+ * @param other an absolute path
+ * @return the relative path from <code>this</code> path to
+ * <code>other</code> path
+ * @throws MalformedPathException if either <code>this</code> or
+ * <code>other</code> path is not absolute
+ */
+ public Path computeRelativePath(Path other) throws MalformedPathException {
+ if (other == null) {
+ throw new IllegalArgumentException("null argument");
+ }
+
+ // make sure both paths are absolute
+ if (!isAbsolute() || !other.isAbsolute()) {
+ throw new MalformedPathException("not an absolute path");
+ }
+
+ // make sure we're comparing canonical paths
+ Path p0 = getCanonicalPath();
+ Path p1 = other.getCanonicalPath();
+
+ if (p0.equals(p1)) {
+ // both paths are equal, the relative path is therefore '.'
+ PathBuilder pb = new PathBuilder();
+ pb.addLast(CURRENT_ELEMENT);
+ return pb.getPath();
+ }
+
+ // determine length of common path fragment
+ int lengthCommon = 0;
+ for (int i = 0; i < p0.elements.length && i < p1.elements.length; i++) {
+ if (!p0.elements[i].equals(p1.elements[i])) {
+ break;
+ }
+ lengthCommon++;
+ }
+
+ PathBuilder pb = new PathBuilder();
+ if (lengthCommon < p0.elements.length) {
+ /**
+ * the common path fragment is an ancestor of this path;
+ * this has to be accounted for by prepending '..' elements
+ * to the relative path
+ */
+ int tmp = p0.elements.length - lengthCommon;
+ while (tmp-- > 0) {
+ pb.addFirst(PARENT_ELEMENT);
+ }
+ }
+ // add remainder of other path
+ for (int i = lengthCommon; i < p1.elements.length; i++) {
+ pb.addLast(p1.elements[i]);
+ }
+ // we're done
+ return pb.getPath();
+ }
+
+ /**
+ * Returns the ancestor path of the specified relative degree.
+ * <p/>
+ * An ancestor of relative degree <i>x</i> is the path that is <i>x</i>
+ * levels up along the path.
+ * <ul>
+ * <li><i>degree</i> = 0 returns this path.
+ * <li><i>degree</i> = 1 returns the parent of this path.
+ * <li><i>degree</i> = 2 returns the grandparent of this path.
+ * <li>And so on to <i>degree</i> = <i>n</i>, where <i>n</i> is the depth
+ * of this path, which returns the root path.
+ * </ul>
+ * <p/>
+ * Note that there migth be an unexpected result if <i>this</i> path is not
+ * normalized, e.g. the ancestor of degree = 1 of the path "../.." would
+ * be ".." although this is not the parent of "../..".
+ *
+ * @param degree the relative degree of the requested ancestor.
+ * @return the ancestor path of the specified degree.
+ * @throws javax.jcr.PathNotFoundException if there is no ancestor of the specified
+ * degree
+ * @throws IllegalArgumentException if <code>degree</code> is negative
+ */
+ public Path getAncestor(int degree)
+ throws IllegalArgumentException, PathNotFoundException {
+ if (degree < 0) {
+ throw new IllegalArgumentException("degree must be >= 0");
+ } else if (degree == 0) {
+ return this;
+ }
+ int length = elements.length - degree;
+ if (length < 1) {
+ throw new PathNotFoundException("no such ancestor path of degree " + degree);
+ }
+ PathElement[] elements = new PathElement[length];
+ for (int i = 0; i < length; i++) {
+ elements[i] = this.elements[i];
+ }
+ return new Path(elements, normalized);
+ }
+
+ /**
+ * Returns the number of ancestors of this path. This is the equivalent
+ * of <code>{@link #getDepth()} - 1</code>.
+ * <p/>
+ * Note that the returned value might be negative if this path is not
+ * canonical, e.g. the depth of "../../a" is -1, its ancestor count is
+ * therefore -2.
+ *
+ * @return the number of ancestors of this path
+ * @see #getDepth()
+ * @see #getLength()
+ * @see #isCanonical()
+ */
+ public int getAncestorCount() {
+ return getDepth() - 1;
+ }
+
+ /**
+ * Returns the length of this path, i.e. the number of its elements.
+ * Note that the root element "/" counts as a separate element, e.g.
+ * the length of "/a/b/c" is 4 whereas the length of "a/b/c" is 3.
+ * <p/>
+ * Also note that the special elements "." and ".." are not treated
+ * specially, e.g. both "/a/./.." and "/a/b/c" have a length of 4
+ * but this value does not necessarily reflect the true hierarchy level as
+ * returned by <code>{@link #getDepth()}</code>.
+ *
+ * @return the length of this path
+ * @see #getDepth()
+ * @see #getAncestorCount()
+ */
+ public int getLength() {
+ return elements.length;
+ }
+
+ /**
+ * Returns the depth of this path. The depth reflects the absolute or
+ * relative hierarchy level this path is representing, depending on whether
+ * this path is an absolute or a relative path. The depth also takes '.'
+ * and '..' elements into account.
+ * <p/>
+ * Note that the returned value might be negative if this path is not
+ * canonical, e.g. the depth of "../../a" is -1.
+ *
+ * @return the depth this path
+ * @see #getLength()
+ * @see #getAncestorCount()
+ */
+ public int getDepth() {
+ int depth = 0;
+ for (int i = 0; i < elements.length; i++) {
+ if (elements[i].denotesParent()) {
+ depth--;
+ } else if (!elements[i].denotesCurrent()) {
+ depth++;
+ }
+ }
+ return depth;
+ }
+
+ /**
+ * Determines if <i>this</i> path is an ancestor of the specified path,
+ * based on their (absolute or relative) hierarchy level as returned by
+ * <code>{@link #getDepth()}</code>.
+ *
+ * @return <code>true</code> if <code>other</code> is a descendant;
+ * otherwise <code>false</code>
+ * @throws MalformedPathException if not both paths are either absolute or
+ * relative.
+ * @see #getDepth()
+ */
+ public boolean isAncestorOf(Path other) throws MalformedPathException {
+ if (other == null) {
+ throw new IllegalArgumentException("null argument");
+ }
+ // make sure both paths are either absolute or relative
+ if (isAbsolute() != other.isAbsolute()) {
+ throw new MalformedPathException("cannot compare a relative path with an absolute path");
+ }
+ // make sure we're comparing normalized paths
+ Path p0 = getNormalizedPath();
+ Path p1 = other.getNormalizedPath();
+
+ if (p0.equals(p1)) {
+ return false;
+ }
+ // calculate depth of paths (might be negative)
+ if (p0.getDepth() >= p1.getDepth()) {
+ return false;
+ }
+ for (int i = 0; i < p0.elements.length; i++) {
+ if (!p0.elements[i].equals(p1.elements[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Determines if <i>this</i> path is a descendant of the specified path,
+ * based on their (absolute or relative) hierarchy level as returned by
+ * <code>{@link #getDepth()}</code>.
+ *
+ * @return <code>true</code> if <code>other</code> is an ancestor;
+ * otherwise <code>false</code>
+ * @throws MalformedPathException if not both paths are either absolute or
+ * relative.
+ * @see #getDepth()
+ */
+ public boolean isDescendantOf(Path other) throws MalformedPathException {
+ if (other == null) {
+ throw new IllegalArgumentException("null argument");
+ }
+ return other.isAncestorOf(this);
+ }
+
+ /**
+ * Returns the name element (i.e. the last element) of this path.
+ *
+ * @return the name element of this path
+ */
+ public PathElement getNameElement() {
+ return elements[elements.length - 1];
+ }
+
+ /**
+ * Returns the elements of this path.
+ *
+ * @return the elements of this path.
+ */
+ public PathElement[] getElements() {
+ return elements;
+ }
+
+ /**
+ * Returns a string representation of this <code>Path</code> in the
+ * JCR path format.
+ *
+ * @param resolver namespace resolver
+ * @return JCR path
+ * @throws NoPrefixDeclaredException if a namespace can not be resolved
+ */
+ public String toJCRPath(NamespaceResolver resolver)
+ throws NoPrefixDeclaredException {
+ if (denotesRoot()) {
+ // shortcut
+ return "/";
+ }
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < elements.length; i++) {
+ if (i > 0) {
+ sb.append('/');
+ }
+ PathElement element = elements[i];
+ // name
+ element.toJCRName(resolver, sb);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns the internal string representation of this <code>Path</code>.
+ * <p/>
+ * Note that the returned string is not a valid JCR path, i.e. the
+ * namespace URI's of the individual path elements are not replaced with
+ * their mapped prefixes. Call
+ * <code>{@link #toJCRPath(NamespaceResolver)}</code>
+ * for a JCR path representation.
+ *
+ * @return the internal string representation of this <code>Path</code>.
+ */
+ public String toString() {
+ // Path is immutable, we can store the string representation
+ if (string == null) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < elements.length; i++) {
+ if (i > 0) {
+ // @todo find safe path separator char that does not conflict with chars in serialized QName
+ sb.append('\t');
+ }
+ PathElement element = elements[i];
+ String elem = element.toString();
+ sb.append(elem);
+ }
+ string = sb.toString();
+ }
+ return string;
+ }
+
+ /**
+ * Returns a <code>Path</code> holding the value of the specified
+ * string. The string must be in the format returned by the
+ * <code>Path.toString()</code> method.
+ *
+ * @param s a <code>String</code> containing the <code>Path</code>
+ * representation to be parsed.
+ * @return the <code>Path</code> represented by the argument
+ * @throws IllegalArgumentException if the specified string can not be parsed
+ * as a <code>Path</code>.
+ * @see #toString()
+ */
+ public static Path valueOf(String s) throws IllegalArgumentException {
+ if ("".equals(s) || s == null) {
+ throw new IllegalArgumentException("invalid Path literal");
+ }
+
+ // split into path elements
+
+ // @todo find safe path separator char that does not conflict with chars in serialized QName
+ String[] elements = Text.explode(s, '\t', true);
+ ArrayList list = new ArrayList();
+ boolean isNormalized = true;
+ boolean leadingParent = true;
+ for (int i = 0; i < elements.length; i++) {
+ PathElement elem = PathElement.fromString(elements[i]);
+ list.add(elem);
+ leadingParent &= elem.denotesParent();
+ isNormalized &= !elem.denotesCurrent() && (leadingParent || !elem.denotesParent());
+ }
+
+ return new Path((PathElement[]) list.toArray(new PathElement[list.size()]), isNormalized);
+ }
+
+ /**
+ * Returns a hash code value for this path.
+ *
+ * @return a hash code value for this path.
+ * @see Object#hashCode()
+ */
+ public int hashCode() {
+ // Path is immutable, we can store the computed hash code value
+ int h = hash;
+ if (h == 0) {
+ h = 17;
+ for (int i = 0; i < elements.length; i++) {
+ h = 37 * h + elements[i].hashCode();
+ }
+ hash = h;
+ }
+ return h;
+ }
+
+ /**
+ * Compares the specified object with this path for equality.
+ *
+ * @param obj the object to be compared for equality with this path.
+ * @return <tt>true</tt> if the specified object is equal to this path.
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof Path) {
+ Path other = (Path) obj;
+ return Arrays.equals(elements, other.elements);
+ }
+ return false;
+ }
+
+ //--------------------------------------------------------< inner classes >
+ /**
+ * Internal helper class used to build a path from pre-parsed path elements.
+ * <p/>
+ * <strong>Warning!</strong> This class does neither validate the format of
+ * the path elements nor does it validate the format of the entire path!
+ * This class should therefore only be used in special situations. The
+ * regular way of creating/building a <code>Path</code> object is by calling
+ * any of the overloaded <code>Path.create()</code> factory methods.
+ */
+ public static final class PathBuilder implements Cloneable {
+
+ /**
+ * the list of path elements of the constructed path
+ */
+ private final LinkedList queue;
+
+ /**
+ * flag indicating if the current path is normalized
+ */
+ boolean isNormalized = true;
+
+ /**
+ * flag indicating if the current path has leading parent '..' elements
+ */
+ boolean leadingParent = true;
+
+ /**
+ * Creates a new PathBuilder.
+ */
+ public PathBuilder() {
+ queue = new LinkedList();
+ }
+
+ /**
+ * Creates a new PathBuilder and initialized it with the given path
+ * elements.
+ *
+ * @param elements
+ */
+ public PathBuilder(PathElement[] elements) {
+ this();
+ addAll(elements);
+ }
+
+ /**
+ * Adds the {@link Path#ROOT_ELEMENT}.
+ */
+ public void addRoot() {
+ addFirst(ROOT_ELEMENT);
+ }
+
+ /**
+ * Adds the given elemenets
+ *
+ * @param elements
+ */
+ public void addAll(PathElement[] elements) {
+ for (int i = 0; i < elements.length; i++) {
+ addLast(elements[i]);
+ }
+ }
+
+ /**
+ * Inserts the element at the beginning of the path to be built.
+ *
+ * @param elem
+ */
+ public void addFirst(PathElement elem) {
+ if (queue.isEmpty()) {
+ isNormalized &= !elem.denotesCurrent();
+ leadingParent = elem.denotesParent();
+ } else {
+ isNormalized &= !elem.denotesCurrent() && (!leadingParent || elem.denotesParent());
+ leadingParent |= elem.denotesParent();
+ }
+ queue.addFirst(elem);
+ }
+
+ /**
+ * Inserts the element at the beginning of the path to be built.
+ *
+ * @param name
+ */
+ public void addFirst(QName name) {
+ addFirst(new PathElement(name));
+ }
+
+ /**
+ * Inserts the element at the beginning of the path to be built.
+ *
+ * @param name
+ * @param index
+ */
+ public void addFirst(QName name, int index) {
+ addFirst(new PathElement(name, index));
+ }
+
+ /**
+ * Inserts the element at the end of the path to be built.
+ *
+ * @param elem
+ */
+ public void addLast(PathElement elem) {
+ queue.addLast(elem);
+ leadingParent &= elem.denotesParent();
+ isNormalized &= !elem.denotesCurrent() && (leadingParent || !elem.denotesParent());
+ }
+
+ /**
+ * Inserts the element at the end of the path to be built.
+ *
+ * @param name
+ */
+ public void addLast(QName name) {
+ addLast(new PathElement(name));
+ }
+
+ /**
+ * Inserts the element at the end of the path to be built.
+ *
+ * @param name
+ * @param index
+ */
+ public void addLast(QName name, int index) {
+ addLast(new PathElement(name, index));
+ }
+
+ /**
+ * Assembles the built path and returns a new {@link Path}.
+ *
+ * @return a new {@link Path}
+ * @throws MalformedPathException if the internal path element queue is empty.
+ */
+ public Path getPath() throws MalformedPathException {
+ PathElement[] elements = (PathElement[]) queue.toArray(new PathElement[queue.size()]);
+ // validate path
+ if (elements.length == 0) {
+ throw new MalformedPathException("empty path");
+ }
+
+ // no need to check the path format, assuming all names correct
+ return new Path(elements, isNormalized);
+ }
+
+ public Object clone() {
+ PathBuilder clone = new PathBuilder();
+ clone.queue.addAll(queue);
+ return clone;
+ }
+ }
+
+ public static final class RootElement extends PathElement {
+ // use a literal that is an illegal name character to avoid collisions
+ static final String LITERAL = "*";
+
+ private RootElement() {
+ super(Constants.NS_DEFAULT_URI, "");
+ }
+
+ // PathElement override
+ public boolean denotesRoot() {
+ return true;
+ }
+
+ // PathElement override
+ public boolean denotesCurrent() {
+ return false;
+ }
+
+ // PathElement override
+ public boolean denotesParent() {
+ return false;
+ }
+
+ // PathElement override
+ public boolean denotesName() {
+ return false;
+ }
+
+ // PathElement override
+ public String toJCRName(NamespaceResolver resolver)
+ throws NoPrefixDeclaredException {
+ return "";
+ }
+
+ // Object override
+ public String toString() {
+ return LITERAL;
+ }
+ }
+
+ public static final class CurrentElement extends PathElement {
+ static final String LITERAL = ".";
+
+ private CurrentElement() {
+ super(Constants.NS_DEFAULT_URI, LITERAL);
+ }
+
+ // PathElement override
+ public boolean denotesRoot() {
+ return false;
+ }
+
+ // PathElement override
+ public boolean denotesCurrent() {
+ return true;
+ }
+
+ // PathElement override
+ public boolean denotesParent() {
+ return false;
+ }
+
+ // PathElement override
+ public boolean denotesName() {
+ return false;
+ }
+
+ // PathElement override
+ public String toJCRName(NamespaceResolver resolver)
+ throws NoPrefixDeclaredException {
+ return LITERAL;
+ }
+
+ // Object override
+ public String toString() {
+ return LITERAL;
+ }
+ }
+
+ public static final class ParentElement extends PathElement {
+ static final String LITERAL = "..";
+
+ private ParentElement() {
+ super(Constants.NS_DEFAULT_URI, LITERAL);
+ }
+
+ // PathElement override
+ public boolean denotesRoot() {
+ return false;
+ }
+
+ // PathElement override
+ public boolean denotesCurrent() {
+ return false;
+ }
+
+ // PathElement override
+ public boolean denotesParent() {
+ return true;
+ }
+
+ // PathElement override
+ public boolean denotesName() {
+ return false;
+ }
+
+ // PathElement override
+ public String toJCRName(NamespaceResolver resolver)
+ throws NoPrefixDeclaredException {
+ return LITERAL;
+ }
+
+ // Object override
+ public String toString() {
+ return LITERAL;
+ }
+ }
+
+ /**
+ * Object representation of a single JCR path element. A PathElement
+ * object contains the qualified name and optional index of a single
+ * JCR path element.
+ * <p/>
+ * Once created, a PathElement object is immutable.
+ */
+ public static class PathElement {
+
+ /**
+ * Qualified name of the path element.
+ */
+ private final QName name;
+
+ /**
+ * Optional index of the path element. Set to zero, if not
+ * explicitly specified, otherwise contains the 1-based index.
+ */
+ private final int index;
+
+ /**
+ * Creates a path element with the given qualified name.
+ * The created path element does not contain an explicit index.
+ *
+ * @param namespaceURI namespace URI
+ * @param localName local name
+ */
+ private PathElement(String namespaceURI, String localName) {
+ this(new QName(namespaceURI, localName));
+ }
+
+ /**
+ * Creates a path element with the given qualified name and index.
+ *
+ * @param namespaceURI namespace URI
+ * @param localName local name
+ * @param index index
+ */
+ private PathElement(String namespaceURI, String localName, int index) {
+ this(new QName(namespaceURI, localName), index);
+ }
+
+ /**
+ * Creates a path element with the given qualified name.
+ * The created path element does not contain an explicit index.
+ *
+ * @param name qualified name
+ * @throws IllegalArgumentException if the name is <code>null</code>
+ */
+ private PathElement(QName name) throws IllegalArgumentException {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+ this.name = name;
+ this.index = 0;
+ }
+
+ /**
+ * Creates a path element with the given qualified name and index.
+ *
+ * @param name qualified name
+ * @param index index
+ * @throws IllegalArgumentException if the name is <code>null</code>
+ */
+ private PathElement(QName name, int index) throws IllegalArgumentException {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+ if (index < 1) {
+ throw new IllegalArgumentException("index is 1-based");
+ }
+ this.index = index;
+ this.name = name;
+ }
+
+ /**
+ * Returns the qualified name of this path element.
+ *
+ * @return qualified name
+ */
+ public QName getName() {
+ return name;
+ }
+
+ /**
+ * Returns the 1-based index or 0 if no index was specified (which is
+ * equivalent to specifying 1).
+ *
+ * @return Returns the 1-based index or 0 if no index was specified.
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Returns <code>true</code> if this element denotes the <i>root</i> element,
+ * otherwise returns <code>false</code>.
+ *
+ * @return <code>true</code> if this element denotes the <i>root</i>
+ * element; otherwise <code>false</code>
+ */
+ public boolean denotesRoot() {
+ return equals(ROOT_ELEMENT);
+ }
+
+ /**
+ * Returns <code>true</code> if this element denotes the <i>parent</i>
+ * ('..') element, otherwise returns <code>false</code>.
+ *
+ * @return <code>true</code> if this element denotes the <i>parent</i>
+ * element; otherwise <code>false</code>
+ */
+ public boolean denotesParent() {
+ return equals(PARENT_ELEMENT);
+ }
+
+ /**
+ * Returns <code>true</code> if this element denotes the <i>current</i>
+ * ('.') element, otherwise returns <code>false</code>.
+ *
+ * @return <code>true</code> if this element denotes the <i>current</i>
+ * element; otherwise <code>false</code>
+ */
+ public boolean denotesCurrent() {
+ return equals(CURRENT_ELEMENT);
+ }
+
+ /**
+ * Returns <code>true</code> if this element represents a regular name
+ * (i.e. neither root, '.' nor '..'), otherwise returns <code>false</code>.
+ *
+ * @return <code>true</code> if this element represents a regular name;
+ * otherwise <code>false</code>
+ */
+ public boolean denotesName() {
+ return !denotesRoot() && !denotesParent() && !denotesCurrent();
+ }
+
+ /**
+ * Returns the JCR name representation of this path element.
+ * Note that strictly speaking the returned value is in fact
+ * a JCR relative path instead of a JCR name, as it contains
+ * the index value if the index is greater than one.
+ *
+ * @param resolver namespace resolver
+ * @return JCR name representation of the path element
+ * @throws NoPrefixDeclaredException if the namespace of the path
+ * element name can not be resolved
+ */
+ public String toJCRName(NamespaceResolver resolver)
+ throws NoPrefixDeclaredException {
+ StringBuffer sb = new StringBuffer();
+ toJCRName(resolver, sb);
+ return sb.toString();
+ }
+
+ /**
+ * Appends the JCR name representation of this path element to the
+ * given string buffer.
+ *
+ * @param resolver namespace resolver
+ * @param buf string buffer where the JCR name representation
+ * should be appended to
+ * @throws NoPrefixDeclaredException if the namespace of the path
+ * element name can not be resolved
+ * @see #toJCRName(NamespaceResolver)
+ */
+ public void toJCRName(NamespaceResolver resolver, StringBuffer buf)
+ throws NoPrefixDeclaredException {
+ // name
+ name.toJCRName(resolver, buf);
+ // index
+ int index = getIndex();
+ /**
+ * FIXME the [1] subscript should only be suppressed if the item
+ * in question can't have same-name siblings.
+ */
+ //if (index > 0) {
+ if (index > 1) {
+ buf.append('[');
+ buf.append(index);
+ buf.append(']');
+ }
+ }
+
+ /**
+ * Returns a string representation of this path element. Note that
+ * the path element name is expressed using the <code>{uri}name</code>
+ * syntax. Use the {@link #toJCRName(NamespaceResolver) toJCRName}
+ * method to get the prefixed string representation of the path element.
+ *
+ * @return string representation of the path element
+ */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ // name
+ sb.append(name.toString());
+ // index
+ int index = getIndex();
+ if (index > 0) {
+ sb.append('[');
+ sb.append(index);
+ sb.append(']');
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Parses the given path element string into a path element object.
+ *
+ * @param s path element string
+ * @return path element object
+ * @throws IllegalArgumentException if the given path element string
+ * is <code>null</code> or if its
+ * format is invalid
+ */
+ public static PathElement fromString(String s) throws IllegalArgumentException {
+ if (s == null) {
+ throw new IllegalArgumentException("null PathElement literal");
+ }
+ if (s.equals(RootElement.LITERAL)) {
+ return ROOT_ELEMENT;
+ } else if (s.equals(CurrentElement.LITERAL)) {
+ return CURRENT_ELEMENT;
+ } else if (s.equals(ParentElement.LITERAL)) {
+ return PARENT_ELEMENT;
+ }
+
+ int pos = s.indexOf('[');
+ if (pos == -1) {
+ QName name = QName.valueOf(s);
+ return new PathElement(name.getNamespaceURI(), name.getLocalName());
+ }
+ QName name = QName.valueOf(s.substring(0, pos));
+ int pos1 = s.indexOf(']');
+ if (pos1 == -1) {
+ throw new IllegalArgumentException("invalid PathElement literal: " + s + " (missing ']')");
+ }
+ try {
+ int index = Integer.valueOf(s.substring(pos + 1, pos1)).intValue();
+ if (index < 1) {
+ throw new IllegalArgumentException("invalid PathElement literal: " + s + " (index is 1-based)");
+ }
+ return new PathElement(name.getNamespaceURI(), name.getLocalName(), index);
+ } catch (Throwable t) {
+ throw new IllegalArgumentException("invalid PathElement literal: " + s + " (" + t.getMessage() + ")");
+ }
+ }
+
+ /**
+ * Computes a hash code for this path element.
+ *
+ * @return hash code
+ */
+ public int hashCode() {
+ // @todo treat index==0 as index==1?
+ int h = 17;
+ h = 37 * h + index;
+ h = 37 * h + name.hashCode();
+ return h;
+ }
+
+ /**
+ * Check for path element equality. Returns true if the given
+ * object is a PathElement and contains the same name and index
+ * as this one.
+ *
+ * @param obj the object to compare with
+ * @return <code>true</code> if the path elements are equal
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof PathElement) {
+ PathElement other = (PathElement) obj;
+ return name.equals(other.name)
+ // @todo treat index==0 as index==1?
+ && index == other.index;
+ }
+ return false;
+ }
+ }
+}
Propchange: incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/Path.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/QName.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/QName.java?rev=209089&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/QName.java (added)
+++ incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/QName.java Mon Jul 4 07:52:19 2005
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ * as applicable.
+ *
+ * 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.apache.jackrabbit.name;
+
+import org.apache.xerces.util.XMLChar;
+
+import javax.jcr.NamespaceException;
+import java.io.Serializable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Qualified name. A qualified name is a combination of a namespace URI
+ * and a local part. Instances of this class are used by Jackrabbit to
+ * represent the names of JCR content items and other objects.
+ * <p/>
+ * A qualified name is immutable once created, although the prefixed JCR
+ * name representation of the qualified name can change depending on the
+ * namespace mappings in effect.
+ *
+ * <h2>String representations</h2>
+ * <p>
+ * The prefixed JCR name format of a qualified name is specified by
+ * JSR 170 as follows:
+ * <pre>
+ *
+ * name ::= simplename | prefixedname
+ * simplename ::= onecharsimplename |
+ * twocharsimplename |
+ * threeormorecharname
+ * prefixedname ::= prefix ':' localname
+ * localname ::= onecharlocalname |
+ * twocharlocalname |
+ * threeormorecharname
+ * onecharsimplename ::= (* Any Unicode character except:
+ * '.', '/', ':', '[', ']', '*',
+ * ''', '"', '|' or any whitespace
+ * character *)
+ * twocharsimplename ::= '.' onecharsimplename |
+ * onecharsimplename '.' |
+ * onecharsimplename onecharsimplename
+ * onecharlocalname ::= nonspace
+ * twocharlocalname ::= nonspace nonspace
+ * threeormorecharname ::= nonspace string nonspace
+ * prefix ::= (* Any valid XML Name *)
+ * string ::= char | string char
+ * char ::= nonspace | ' '
+ * nonspace ::= (* Any Unicode character except:
+ * '/', ':', '[', ']', '*',
+ * ''', '"', '|' or any whitespace
+ * character *)
+ * </pre>
+ * <p>
+ * In addition to the prefixed JCR name format, a qualified name can also
+ * be represented using the format "<code>{namespaceURI}localPart</code>".
+ */
+public class QName implements Cloneable, Comparable, Serializable {
+
+ /** Serialization UID of this class. */
+ static final long serialVersionUID = -2712313010017755368L;
+
+ public static final QName[] EMPTY_ARRAY = new QName[0];
+
+ /**
+ * The reqular expression pattern used to validate and parse
+ * qualified names.
+ * <p>
+ * The pattern contains the following groups:
+ * <ul>
+ * <li>group 1 is namespace prefix incl. delimiter (colon)
+ * <li>group 2 is namespace prefix excl. delimiter (colon)
+ * <li>group 3 is localName
+ * </ul>
+ */
+ private static final Pattern NAME_PATTERN = Pattern.compile(
+ "(([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?):)?"
+ + "([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?)");
+
+ /** The memorized hash code of this qualified name. */
+ private transient int hash;
+
+ /** The memorized string representation of this qualified name. */
+ private transient String string;
+
+ /** The internalized namespace URI of this qualified name. */
+ protected final String namespaceURI;
+
+ /** The internalized local part of this qualified name. */
+ protected final String localName;
+
+ /**
+ * Creates a new qualified name with the given namespace URI and
+ * local part.
+ * <p/>
+ * Note that the format of the local part is not validated. The format
+ * can be checked by calling {@link #checkFormat(String)}.
+ *
+ * @param namespaceURI namespace uri
+ * @param localName local part
+ */
+ public QName(String namespaceURI, String localName) {
+ if (namespaceURI == null) {
+ throw new IllegalArgumentException("invalid namespaceURI specified");
+ }
+ // an empty localName is valid though (e.g. the root node name)
+ if (localName == null) {
+ throw new IllegalArgumentException("invalid localName specified");
+ }
+ // internalize both namespaceURI and localName to improve performance
+ // of QName comparisons
+ this.namespaceURI = namespaceURI.intern();
+ this.localName = localName.intern();
+ hash = 0;
+ }
+
+ //------------------------------------------------------< factory methods >
+
+ /**
+ * Parses the given prefixed JCR name into a qualified name using the
+ * given namespace resolver.
+ *
+ * @param rawName prefixed JCR name
+ * @param resolver namespace resolver
+ * @return qualified name
+ * @throws IllegalNameException if the given name is not a valid JCR name
+ * @throws UnknownPrefixException if the JCR name prefix does not resolve
+ */
+ public static QName fromJCRName(String rawName, NamespaceResolver resolver)
+ throws IllegalNameException, UnknownPrefixException {
+ if (resolver == null) {
+ throw new NullPointerException("resolver must not be null");
+ }
+
+ if (rawName == null || rawName.length() == 0) {
+ throw new IllegalNameException("empty name");
+ }
+
+ // parts[0]: prefix
+ // parts[1]: localName
+ String[] parts = parse(rawName);
+
+ String uri;
+ try {
+ uri = resolver.getURI(parts[0]);
+ } catch (NamespaceException nse) {
+ throw new UnknownPrefixException(parts[0]);
+ }
+
+ return new QName(uri, parts[1]);
+ }
+
+ /**
+ * Returns a <code>QName</code> holding the value of the specified
+ * string. The string must be in the format returned by the
+ * <code>QName.toString()</code> method, i.e.
+ * <p/>
+ * <code><b>{</b>namespaceURI<b>}</b>localName</code>
+ *
+ * @param s a <code>String</code> containing the <code>QName</code>
+ * representation to be parsed.
+ * @return the <code>QName</code> represented by the argument
+ * @throws IllegalArgumentException if the specified string can not be parsed
+ * as a <code>QName</code>.
+ * @see #toString()
+ */
+ public static QName valueOf(String s) throws IllegalArgumentException {
+ if ("".equals(s) || s == null) {
+ throw new IllegalArgumentException("invalid QName literal");
+ }
+
+ if (s.charAt(0) == '{') {
+ int i = s.indexOf('}');
+
+ if (i == -1) {
+ throw new IllegalArgumentException("invalid QName literal");
+ }
+
+ if (i == s.length() - 1) {
+ throw new IllegalArgumentException("invalid QName literal");
+ } else {
+ return new QName(s.substring(1, i), s.substring(i + 1));
+ }
+ } else {
+ throw new IllegalArgumentException("invalid QName literal");
+ }
+ }
+
+ //------------------------------------------------------< utility methods >
+ /**
+ * Checks if <code>jcrName</code> is a valid JCR-style name.
+ *
+ * @param jcrName the name to be checked
+ * @throws IllegalNameException If <code>jcrName</code> is not a valid
+ * JCR-style name.
+ */
+ public static void checkFormat(String jcrName) throws IllegalNameException {
+ parse(jcrName);
+ }
+
+ /**
+ * Parses the <code>jcrName</code> and returns an array of two strings:
+ * the first array element contains the prefix (or empty string),
+ * the second the local name.
+ *
+ * @param jcrName the name to be parsed
+ * @return An array holding two strings: the first array element contains
+ * the prefix (or empty string), the second the local name.
+ * @throws IllegalNameException If <code>jcrName</code> is not a valid
+ * JCR-style name.
+ */
+ public static String[] parse(String jcrName) throws IllegalNameException {
+ if (jcrName == null || jcrName.length() == 0) {
+ throw new IllegalNameException("empty name");
+ }
+
+ if (".".equals(jcrName) || "..".equals(jcrName)) {
+ // illegal syntax for name
+ throw new IllegalNameException("'" + jcrName + "' is not a valid name");
+ }
+
+ String prefix;
+ String localName;
+
+ Matcher matcher = NAME_PATTERN.matcher(jcrName);
+ if (matcher.matches()) {
+ // check for prefix (group 1)
+ if (matcher.group(1) != null) {
+ // prefix specified
+ // group 2 is namespace prefix excl. delimiter (colon)
+ prefix = matcher.group(2);
+ // check if the prefix is a valid XML prefix
+ if (!XMLChar.isValidNCName(prefix)) {
+ // illegal syntax for prefix
+ throw new IllegalNameException("'" + jcrName
+ + "' is not a valid name: illegal prefix");
+ }
+ } else {
+ // no prefix specified
+ prefix = "";
+ }
+
+ // group 3 is localName
+ localName = matcher.group(3);
+ } else {
+ // illegal syntax for name
+ throw new IllegalNameException("'" + jcrName + "' is not a valid name");
+ }
+
+ return new String[]{prefix, localName};
+ }
+
+ //-------------------------------------------------------< public methods >
+ /**
+ * Returns the local part of the qualified name.
+ *
+ * @return local name
+ */
+ public String getLocalName() {
+ return localName;
+ }
+
+ /**
+ * Returns the namespace URI of the qualified name.
+ *
+ * @return namespace URI
+ */
+ public String getNamespaceURI() {
+ return namespaceURI;
+ }
+
+ /**
+ * Returns the qualified name in the prefixed JCR name format.
+ * The namespace URI is mapped to a prefix using the given
+ * namespace resolver.
+ *
+ * @param resolver namespace resolver
+ * @return prefixed name
+ * @throws NoPrefixDeclaredException if the namespace can not be resolved
+ */
+ public String toJCRName(NamespaceResolver resolver)
+ throws NoPrefixDeclaredException {
+ StringBuffer sb = new StringBuffer();
+ toJCRName(resolver, sb);
+ return sb.toString();
+ }
+
+ /**
+ * Appends the qualified name in the prefixed JCR name format to the given
+ * string buffer. The namespace URI is mapped to a prefix using the given
+ * namespace resolver.
+ *
+ * @param resolver namespace resolver
+ * @param buf string buffer where the prefixed JCR name should be
+ * appended to
+ * @throws NoPrefixDeclaredException if the namespace can not be resolved
+ * @see #toJCRName(NamespaceResolver)
+ */
+ public void toJCRName(NamespaceResolver resolver, StringBuffer buf)
+ throws NoPrefixDeclaredException {
+ // prefix
+ String prefix;
+ try {
+ prefix = resolver.getPrefix(namespaceURI);
+ } catch (NamespaceException nse) {
+ throw new NoPrefixDeclaredException("no prefix declared for URI: "
+ + namespaceURI);
+ }
+ if (prefix.length() == 0) {
+ // default prefix (empty string)
+ } else {
+ buf.append(prefix);
+ buf.append(':');
+ }
+ // name
+ buf.append(localName);
+ }
+
+ /**
+ * Returns the string representation of this <code>QName</code> in the
+ * following format:
+ * <p/>
+ * <code><b>{</b>namespaceURI<b>}</b>localName</code>
+ *
+ * @return the string representation of this <code>QName</code>.
+ * @see #valueOf(String)
+ */
+ public String toString() {
+ // QName is immutable, we can store the string representation
+ if (string == null) {
+ string = '{' + namespaceURI + '}' + localName;
+ }
+ return string;
+ }
+
+ /**
+ * Compares two qualified names for equality. Returns <code>true</code>
+ * if the given object is a qualified name and has the same namespace URI
+ * and local part as this qualified name.
+ *
+ * @param obj the object to compare this qualified name with
+ * @return <code>true</code> if the object is equal to this qualified name,
+ * <code>false</code> otherwise
+ * @see Object#equals(Object)
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof QName) {
+ QName other = (QName) obj;
+ // localName & namespaceURI are internalized,
+ // we only have to compare their references
+ return localName == other.localName
+ && namespaceURI == other.namespaceURI;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the hash code of this qualified name. The hash code is
+ * computed from the namespace URI and local part of the qualified
+ * name and memorized for better performance.
+ *
+ * @return hash code
+ * @see Object#hashCode()
+ */
+ public int hashCode() {
+ // QName is immutable, we can store the computed hash code value
+ int h = hash;
+ if (h == 0) {
+ h = 17;
+ h = 37 * h + namespaceURI.hashCode();
+ h = 37 * h + localName.hashCode();
+ hash = h;
+ }
+ return h;
+ }
+
+ /**
+ * Creates a clone of this qualified name.
+ * Overriden in order to make <code>clone()</code> public.
+ *
+ * @return a clone of this instance
+ * @throws CloneNotSupportedException never thrown
+ * @see Object#clone()
+ */
+ public Object clone() throws CloneNotSupportedException {
+ // QName is immutable, no special handling required
+ return super.clone();
+ }
+
+ /**
+ * Compares two qualified names.
+ *
+ * @param o the object to compare this qualified name with
+ * @return comparison result
+ * @throws ClassCastException if the given object is not a qualified name
+ * @see Comparable#compareTo(Object)
+ */
+ public int compareTo(Object o) throws ClassCastException {
+ return toString().compareTo(((QName) o).toString());
+ }
+}
Propchange: incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/QName.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/UnknownPrefixException.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/UnknownPrefixException.java?rev=209089&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/UnknownPrefixException.java (added)
+++ incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/UnknownPrefixException.java Mon Jul 4 07:52:19 2005
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ * as applicable.
+ *
+ * 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.apache.jackrabbit.name;
+
+import org.apache.jackrabbit.BaseException;
+
+/**
+ * The <code>UnknownPrefixException</code> ...
+ */
+public class UnknownPrefixException extends BaseException {
+ /**
+ * Constructs a new instance of this class with <code>null</code> as its
+ * detail message.
+ */
+ public UnknownPrefixException() {
+ super();
+ }
+
+ /**
+ * Constructs a new instance of this class with the specified detail
+ * message.
+ *
+ * @param message the detail message. The detail message is saved for
+ * later retrieval by the {@link #getMessage()} method.
+ */
+ public UnknownPrefixException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new instance of this class with the specified detail
+ * message and root cause.
+ *
+ * @param message the detail message. The detail message is saved for
+ * later retrieval by the {@link #getMessage()} method.
+ * @param rootCause root failure cause
+ */
+ public UnknownPrefixException(String message, Throwable rootCause) {
+ super(message, rootCause);
+ }
+
+ /**
+ * Constructs a new instance of this class with the specified root cause.
+ *
+ * @param rootCause root failure cause
+ */
+ public UnknownPrefixException(Throwable rootCause) {
+ super(rootCause);
+ }
+}
Propchange: incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/name/UnknownPrefixException.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/util/Base64.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/util/Base64.java?rev=209089&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/util/Base64.java (added)
+++ incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/util/Base64.java Mon Jul 4 07:52:19 2005
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ * as applicable.
+ *
+ * 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.apache.jackrabbit.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+
+/**
+ * <code>Base64</code> provides Base64 encoding/decoding of strings and streams.
+ */
+public class Base64 {
+ // charset used for base64 encoded data (7-bit ASCII)
+ private static final String CHARSET = "US-ASCII";
+
+ // encoding table (the 64 valid base64 characters)
+ private static final char[] BASE64CHARS =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+
+ // decoding table (used to lookup original 6-bit with base64 character
+ // as table index)
+ private static final byte[] DECODETABLE = new byte[128];
+
+ static {
+ // initialize decoding table
+ for (int i = 0; i < DECODETABLE.length; i++) {
+ DECODETABLE[i] = 0x7f;
+ }
+ // build decoding table
+ for (int i = 0; i < BASE64CHARS.length; i++) {
+ DECODETABLE[BASE64CHARS[i]] = (byte) i;
+ }
+ }
+
+ // pad character
+ private static final char BASE64PAD = '=';
+
+ /**
+ * empty private constructor
+ */
+ private Base64() {
+ }
+
+ /**
+ * Calculates the size (i.e. number of bytes) of the base64 encoded output
+ * given the length (i.e. number of bytes) of the data to be encoded.
+ *
+ * @param dataLength length (i.e. number of bytes) of the data to be encoded
+ * @return size (i.e. number of bytes) of the base64 encoded output
+ */
+ public static long calcEncodedLength(long dataLength) {
+ long encLen = dataLength * 4 / 3;
+ encLen += (encLen + 4) % 4;
+ return encLen;
+ }
+
+ /**
+ * Pessimistically guesses the size (i.e. number of bytes) of the decoded
+ * output given the length (i.e. number of bytes) of the base64 encoded
+ * data.
+ *
+ * @param encLength length (i.e. number of bytes) of the base64 encoded data
+ * @return size (i.e. number of bytes) of the decoded output
+ */
+ public static long guessDecodedLength(long encLength) {
+ long decLen = encLength * 3 / 4;
+ return decLen + 3;
+ }
+
+ /**
+ * Outputs base64 representation of the specified stream data to a
+ * <code>Writer</code>.
+ *
+ * @param in stream data to be encoded
+ * @param writer writer to output the encoded data
+ * @throws java.io.IOException if an i/o error occurs
+ */
+ public static void encode(InputStream in, Writer writer)
+ throws IOException {
+ // encode stream data in chunks;
+ // chunksize must be a multiple of 3 in order
+ // to avoid padding within output
+ byte[] buffer = new byte[9 * 1024];
+ int read;
+ while ((read = in.read(buffer)) > 0) {
+ encode(buffer, 0, read, writer);
+ }
+ }
+
+ /**
+ * Outputs base64 representation of the specified stream data to an
+ * <code>OutputStream</code>.
+ *
+ * @param in stream data to be encoded
+ * @param out stream where the encoded data should be written to
+ * @throws java.io.IOException if an i/o error occurs
+ */
+ public static void encode(InputStream in, OutputStream out)
+ throws IOException {
+ Writer writer = new OutputStreamWriter(out, CHARSET);
+ encode(in, writer);
+ }
+
+ /**
+ * Outputs base64 representation of the specified data to a
+ * <code>Writer</code>.
+ *
+ * @param data data to be encoded
+ * @param off offset within data at which to start encoding
+ * @param len length of data to encode
+ * @param writer writer to output the encoded data
+ * @throws java.io.IOException if an i/o error occurs
+ */
+ public static void encode(byte[] data, int off, int len, Writer writer)
+ throws IOException {
+ if (len == 0) {
+ return;
+ }
+ if (len < 0 || off >= data.length
+ || len + off > data.length) {
+ throw new IllegalArgumentException();
+ }
+ char[] enc = new char[4];
+ while (len >= 3) {
+ int i = ((data[off] & 0xff) << 16)
+ + ((data[off + 1] & 0xff) << 8)
+ + (data[off + 2] & 0xff);
+ enc[0] = BASE64CHARS[i >> 18];
+ enc[1] = BASE64CHARS[(i >> 12) & 0x3f];
+ enc[2] = BASE64CHARS[(i >> 6) & 0x3f];
+ enc[3] = BASE64CHARS[i & 0x3f];
+ writer.write(enc, 0, 4);
+ off += 3;
+ len -= 3;
+ }
+ // add padding if necessary
+ if (len == 1) {
+ int i = data[off] & 0xff;
+ enc[0] = BASE64CHARS[i >> 2];
+ enc[1] = BASE64CHARS[(i << 4) & 0x3f];
+ enc[2] = BASE64PAD;
+ enc[3] = BASE64PAD;
+ writer.write(enc, 0, 4);
+ } else if (len == 2) {
+ int i = ((data[off] & 0xff) << 8) + (data[off + 1] & 0xff);
+ enc[0] = BASE64CHARS[i >> 10];
+ enc[1] = BASE64CHARS[(i >> 4) & 0x3f];
+ enc[2] = BASE64CHARS[(i << 2) & 0x3f];
+ enc[3] = BASE64PAD;
+ writer.write(enc, 0, 4);
+ }
+ }
+
+ /**
+ * Decode base64 encoded data.
+ *
+ * @param reader reader for the base64 encoded data to be decoded
+ * @param out stream where the decoded data should be written to
+ * @throws java.io.IOException if an i/o error occurs
+ */
+ public static void decode(Reader reader, OutputStream out)
+ throws IOException {
+ char[] chunk = new char[8192];
+ int read;
+ while ((read = reader.read(chunk)) > -1) {
+ decode(chunk, 0, read, out);
+ }
+ }
+
+ /**
+ * Decode base64 encoded data. The data read from the inputstream is
+ * assumed to be of charset "US-ASCII".
+ *
+ * @param in inputstream of the base64 encoded data to be decoded
+ * @param out stream where the decoded data should be written to
+ * @throws java.io.IOException if an i/o error occurs
+ */
+ public static void decode(InputStream in, OutputStream out)
+ throws IOException {
+ decode(new InputStreamReader(in, CHARSET), out);
+ }
+
+ /**
+ * Decode base64 encoded data.
+ *
+ * @param data the base64 encoded data to be decoded
+ * @param out stream where the decoded data should be written to
+ * @throws java.io.IOException if an i/o error occurs
+ */
+ public static void decode(String data, OutputStream out)
+ throws IOException {
+ char[] chars = data.toCharArray();
+ decode(chars, 0, chars.length, out);
+ }
+
+ /**
+ * Decode base64 encoded data.
+ *
+ * @param chars the base64 encoded data to be decoded
+ * @param out stream where the decoded data should be written to
+ * @throws java.io.IOException if an i/o error occurs
+ */
+ public static void decode(char[] chars, OutputStream out)
+ throws IOException {
+ decode(chars, 0, chars.length, out);
+ }
+
+ /**
+ * Decode base64 encoded data.
+ *
+ * @param chars the base64 encoded data to be decoded
+ * @param off offset within data at which to start decoding
+ * @param len length of data to decode
+ * @param out stream where the decoded data should be written to
+ * @throws java.io.IOException if an i/o error occurs
+ */
+ public static void decode(char[] chars, int off, int len, OutputStream out)
+ throws IOException {
+ if (len == 0) {
+ return;
+ }
+ if (len < 0 || off >= chars.length
+ || len + off > chars.length) {
+ throw new IllegalArgumentException();
+ }
+ char[] chunk = new char[4];
+ byte[] dec = new byte[3];
+ int posChunk = 0;
+ // decode in chunks of 4 characters
+ for (int i = off; i < (off + len); i++) {
+ char c = chars[i];
+ if (c < DECODETABLE.length && DECODETABLE[c] != 0x7f
+ || c == BASE64PAD) {
+ chunk[posChunk++] = c;
+ if (posChunk == chunk.length) {
+ int b0 = DECODETABLE[chunk[0]];
+ int b1 = DECODETABLE[chunk[1]];
+ int b2 = DECODETABLE[chunk[2]];
+ int b3 = DECODETABLE[chunk[3]];
+ if (chunk[3] == BASE64PAD && chunk[2] == BASE64PAD) {
+ dec[0] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+ out.write(dec, 0, 1);
+ } else if (chunk[3] == BASE64PAD) {
+ dec[0] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+ dec[1] = (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
+ out.write(dec, 0, 2);
+ } else {
+ dec[0] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+ dec[1] = (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
+ dec[2] = (byte) (b2 << 6 & 0xc0 | b3 & 0x3f);
+ out.write(dec, 0, 3);
+ }
+ posChunk = 0;
+ }
+ } else {
+ throw new IllegalArgumentException("specified data is not base64 encoded");
+ }
+ }
+ }
+}
Propchange: incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/util/Base64.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/util/ChildrenCollector.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/util/ChildrenCollector.java?rev=209089&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/util/ChildrenCollector.java (added)
+++ incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/util/ChildrenCollector.java Mon Jul 4 07:52:19 2005
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ * as applicable.
+ *
+ * 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.apache.jackrabbit.util;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.util.TraversingItemVisitor;
+import java.util.Collection;
+
+/**
+ * <code>ChildrenCollector</code> is a utility class
+ * which can be used to 'collect' child elements of a
+ * node. It implements the <code>ItemVisitor</code>
+ * interface.
+ */
+public class ChildrenCollector extends TraversingItemVisitor.Default {
+
+ private final Collection children;
+ private final boolean collectNodes;
+ private final boolean collectProperties;
+
+ /**
+ * Constructs a <code>ChildrenCollector</code>
+ *
+ * @param children where the matching children should be added
+ * @param collectNodes true, if child nodes should be collected; otherwise false
+ * @param collectProperties true, if child properties should be collected; otherwise false
+ * @param maxLevel number of hierarchy levels to traverse
+ * (e.g. 1 for direct children only, 2 for children and their children, and so on)
+ */
+ public ChildrenCollector(Collection children, boolean collectNodes, boolean collectProperties, int maxLevel) {
+ super(false, maxLevel);
+ this.children = children;
+ this.collectNodes = collectNodes;
+ this.collectProperties = collectProperties;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void entering(Node node, int level)
+ throws RepositoryException {
+ if (level > 0 && collectNodes) {
+ children.add(node);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void entering(Property property, int level)
+ throws RepositoryException {
+ if (level > 0 && collectProperties) {
+ children.add(property);
+ }
+ }
+}
Propchange: incubator/jackrabbit/trunk/commons/src/java/org/apache/jackrabbit/util/ChildrenCollector.java
------------------------------------------------------------------------------
svn:eol-style = native