You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by tr...@apache.org on 2005/01/21 12:54:15 UTC

svn commit: r125923 - in incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core: . nodetype util

Author: tripod
Date: Fri Jan 21 03:54:11 2005
New Revision: 125923

URL: http://svn.apache.org/viewcvs?view=rev&rev=125923
Log:
- minor path/qname adjustments
Modified:
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Path.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/QName.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/nodetype/ValueConstraint.java
   incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/util/Text.java

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
Url: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java?view=diff&rev=125923&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java&r1=125922&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java&r2=125923
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java	Fri Jan 21 03:54:11 2005
@@ -268,14 +268,13 @@
                     // if the parent has more than one child node entries pointing
                     // to the same child node, always use the first one
                     NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry) entries.get(0);
-                    QName name = entry.getName();
                     // add to path
-                    builder.addFirst(name.getNamespaceURI(), name.getLocalName(), entry.getIndex());
+                    builder.addFirst(entry.getName(), entry.getIndex());
                 } else {
                     PropertyState propState = (PropertyState) state;
                     QName name = propState.getName();
                     // add to path
-                    builder.addFirst(name.getNamespaceURI(), name.getLocalName());
+                    builder.addFirst(name);
                 }
                 parentUUID = parent.getParentUUID();
                 if (parentUUID != null) {
@@ -368,7 +367,7 @@
                 PropertyState propState = (PropertyState) getItemState(id, includeZombies);
                 QName name = propState.getName();
                 // add to path
-                builder.addFirst(name.getNamespaceURI(), name.getLocalName());
+                builder.addFirst(name);
                 nodeId = new NodeId(propState.getParentUUID());
             } catch (NoSuchItemStateException nsise) {
                 String msg = "failed to build path of " + id;
@@ -503,12 +502,11 @@
                 }
                 for (int i = 0; i < entries.size(); i++) {
                     NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry) entries.get(i);
-                    QName name = entry.getName();
 
                     // get a path builder clone from the tail of the queue
                     Path.PathBuilder pb = (Path.PathBuilder) queue.removeLast();
                     // add entry to path
-                    pb.addFirst(name.getNamespaceURI(), name.getLocalName(), entry.getIndex());
+                    pb.addFirst(entry.getName(), entry.getIndex());
 
                     // recurse
                     recursiveBuildPaths(new NodeId(parentUUID), pb, builders, includeZombies);

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Path.java
Url: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Path.java?view=diff&rev=125923&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Path.java&r1=125922&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Path.java&r2=125923
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Path.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/Path.java	Fri Jan 21 03:54:11 2005
@@ -16,21 +16,87 @@
  */
 package org.apache.jackrabbit.core;
 
+import org.apache.jackrabbit.core.util.Text;
+
 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;
+import java.util.regex.Matcher;
 
 /**
- * The <code>Path</code> utility class provides
- * misc. methods to resolve and nornalize JCR-style item paths.
+ * The <code>Path</code> utility class provides misc. methods to resolve and
+ * nornalize JCR-style item paths. <br>
+ * Each path consistnes of path elements and is immutable. it has the following
+ * properties:<br>
+ * <code>isAbsolute()</code>:<br>
+ * A path is absolute, if the first path element denotes the root element '/'.
+ * <p>
+ * <code>isRelative()</code>:<br>
+ * A path is relative, if the first path element does not denote the root element.
+ * I.e. is always the opposite of <code>isAbsolute</code>.
+ * <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 path never have '.' elements.
+ * absolte normalilzed paths have no and relative normalized paths have no or
+ * only leading '..' elements.<br>
+ * <p>
+ * <code>isCanonical()</code>:<br>
+ * A path is canonical, if its absolute and normalized.
+ * <p>
+ *
+ * the external string representation of a path has the following format:
+ *
+ * <xmp>
+ *           path ::= properpath ['/']
+ *     properpath ::= abspath | relpath
+ *        abspath ::= '/' relpath
+ *        relpath ::= [relpath '/'] pathelement
+ *    pathelement ::= name ['[' number ']']
+ *         number ::= << An integer > 0 >>
+ *
+ *           name ::= [prefix ':'] simplename
+ *         prefix ::= << Any valid XML Name >>
+ *     simplename ::= nonspacestring [[string] nonspacestring]
+ *         string ::= [string] char
+ *           char ::= nonspace | space
+ * nonspacestring ::= [nonspacestring] nonspace
+ *          space ::= << ' ' (the space character) >>
+ *       nonspace ::= << Any Unicode character except
+ *                    '/', ':', '[', ']', '*',
+ *                    '''(the single quote),
+ *                    '"'(the double quote),
+ *                    any whitespace character >>
+ * </xmp>
  */
 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 .
@@ -44,127 +110,155 @@
      */
     private static final Pattern PATH_ELEMENT_PATTERN = Pattern.compile("(\\.)|(\\.\\.)|(([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?):)?([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?)(\\[([1-9]\\d*)\\])?");
 
-    private static final PathElement ROOT_ELEMENT = new RootElement();
-    // .
-    private static final PathElement CURRENT_ELEMENT = new CurrentElement();
-    // ..
-    private static final PathElement PARENT_ELEMENT = new ParentElement();
-
+    /**
+     * the elements of this path
+     */
     private final PathElement[] elements;
 
-    private int hash;
+    /**
+     * flag indicating if this path is normalized
+     */
+    private final boolean isNormalized;
+
+    /**
+     * flag indicating if this path is absolute
+     */
+    private final boolean isAbsolute;
+
+    /**
+     * 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) {
+    private Path(PathElement[] elements, boolean isNormalized) {
+        if (elements==null || elements.length==0) {
+            throw new IllegalArgumentException("Empty paths are not allowed");
+        }
         this.elements = elements;
-        hash = 0;
+        this.isAbsolute = elements[0].denotesRoot();
+        this.isNormalized = isNormalized;
     }
 
-    //------------------------------------------------------< factory methods >
+    //----------------------------------------------------< 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 canonicalize
+     * @param normalize
      * @return
      * @throws MalformedPathException
      */
-    public static Path create(String jcrPath, NamespaceResolver resolver, boolean canonicalize)
+    public static Path create(String jcrPath, NamespaceResolver resolver,
+                              boolean normalize)
             throws MalformedPathException {
-        PathElement[] elements = parse(jcrPath, null, resolver);
-        if (canonicalize) {
-            return new Path(elements).getCanonicalPath();
-        } else {
-            return new Path(elements);
-        }
+        return normalize
+            ? parse(jcrPath, null, resolver).getNormalizedPath()
+            : parse(jcrPath, null, resolver);
     }
 
     /**
-     * @param master
+     * 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 master, String relJCRPath, NamespaceResolver resolver, boolean canonicalize)
+    public static Path create(Path parent, String relJCRPath,
+                              NamespaceResolver resolver, boolean canonicalize)
             throws MalformedPathException {
-        if (relJCRPath.startsWith("/")) {
-            throw new MalformedPathException("'" + relJCRPath + "' is not a relative path");
-        }
-
-        PathElement[] elements = parse(relJCRPath, master.getElements(), resolver);
-        if (canonicalize) {
-            return new Path(elements).getCanonicalPath();
-        } else {
-            return new Path(elements);
-        }
+        return canonicalize
+                ? parse(relJCRPath, parent, resolver).getCanonicalPath()
+                : parse(relJCRPath, parent, resolver);
     }
 
     /**
-     * @param master
+     * 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 canonicalize
+     * @param normalize
      * @return
      * @throws MalformedPathException
      */
-    public static Path create(Path master, Path relPath, boolean canonicalize)
+    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(master.getElements());
+        PathBuilder pb = new PathBuilder(parent.getElements());
         pb.addAll(relPath.getElements());
 
-        if (canonicalize) {
-            return pb.getPath().getCanonicalPath();
-        } else {
-            return pb.getPath();
-        }
+        return normalize
+            ? pb.getPath().getNormalizedPath()
+            : pb.getPath();
     }
 
     /**
-     * @param master
+     * 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 canonicalize
+     * @param normalize
      * @return
      * @throws MalformedPathException
      */
-    public static Path create(Path master, QName name, boolean canonicalize)
+    public static Path create(Path parent, QName name, boolean normalize)
             throws MalformedPathException {
-        PathBuilder pb = new PathBuilder(master.getElements());
-        pb.addLast(name.getNamespaceURI(), name.getLocalName());
+        PathBuilder pb = new PathBuilder(parent.getElements());
+        pb.addLast(name);
 
-        if (canonicalize) {
-            return pb.getPath().getCanonicalPath();
-        } else {
-            return pb.getPath();
-        }
+        return normalize
+                ? pb.getPath().getNormalizedPath()
+                : pb.getPath();
     }
 
     /**
-     * @param master
+     * Creates a new <code>Path</code> out of the given <code>parent<code> path
+     * string 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 canonicalize
+     * @param normalize
      * @return
      * @throws MalformedPathException
      */
-    public static Path create(Path master, QName name, int index, boolean canonicalize)
+    public static Path create(Path parent, QName name, int index, boolean normalize)
             throws MalformedPathException {
-        PathBuilder pb = new PathBuilder(master.getElements());
-        pb.addLast(name.getNamespaceURI(), name.getLocalName(), index);
+        PathBuilder pb = new PathBuilder(parent.getElements());
+        pb.addLast(name, index);
 
-        if (canonicalize) {
-            return pb.getPath().getCanonicalPath();
-        } else {
-            return pb.getPath();
-        }
+        return normalize
+                ? pb.getPath().getNormalizedPath()
+                : pb.getPath();
     }
 
     /**
@@ -185,7 +279,7 @@
         } else {
             elem = new PathElement(name, index);
         }
-        return new Path(new PathElement[]{elem});
+        return new Path(new PathElement[]{elem}, !elem.equals(CURRENT_ELEMENT));
     }
 
     //------------------------------------------------------< utility methods >
@@ -198,25 +292,7 @@
      *                                JCR-style path.
      */
     public static void checkFormat(String jcrPath) throws MalformedPathException {
-        if (jcrPath == null || jcrPath.length() == 0) {
-            throw new MalformedPathException("empty path");
-        }
-        // shortcut
-        if (jcrPath.equals("/")) {
-            return;
-        }
-
-        // split path into path elements
-        String[] elems = jcrPath.split("/", -1);
-        for (int i = jcrPath.startsWith("/") ? 1 : 0; i < elems.length; i++) {
-            // validate path element
-            String elem = elems[i];
-            Matcher matcher = PATH_ELEMENT_PATTERN.matcher(elem);
-            if (!matcher.matches()) {
-                // illegal syntax for path element
-                throw new MalformedPathException("'" + jcrPath + "' is not a valid path: '" + elem + "' is not a legal path element");
-            }
-        }
+        parse(jcrPath, null, null);
     }
 
     //-------------------------------------------------------< public methods >
@@ -226,7 +302,7 @@
      * @return true if this path represents the root path; false otherwise.
      */
     public boolean denotesRoot() {
-        return elements.length == 1 && elements[0].denotesRoot();
+        return isAbsolute && elements.length == 1;
     }
 
     /**
@@ -235,7 +311,7 @@
      * @return true if this path is absolute; false otherwise.
      */
     public boolean isAbsolute() {
-        return elements.length > 0 && elements[0].denotesRoot();
+        return isAbsolute;
     }
 
     /**
@@ -246,17 +322,7 @@
      * @see #isAbsolute()
      */
     public boolean isCanonical() {
-        if (!isAbsolute()) {
-            return false;
-        }
-        // check path for any "." and ".." elements
-        for (int i = 0; i < elements.length; i++) {
-            if (elements[i].equals(CURRENT_ELEMENT)
-                    || elements[i].equals(PARENT_ELEMENT)) {
-                return false;
-            }
-        }
-        return true;
+        return isAbsolute && isNormalized;
     }
 
     /**
@@ -272,31 +338,7 @@
      * @see #getNormalizedPath()
      */
     public boolean isNormalized() {
-        if (isAbsolute()) {
-            /**
-             * a normalized absolute path has to be canonical, i.e. it
-             * cannot contain any "." and ".." elements
-             */
-            return isCanonical();
-        }
-
-        // check relative path for redundant "." and ".." elements only
-        for (int i = 0; i < elements.length; i++) {
-            if (elements[i].equals(CURRENT_ELEMENT)) {
-                // "." is always redundant
-                return false;
-            }
-            if (elements[i].equals(PARENT_ELEMENT)) {
-                /**
-                 * ".." is redundant only if there's a preceeding
-                 * non-".." element
-                 */
-                if (i > 0 && !elements[i - 1].equals(PARENT_ELEMENT)) {
-                    return false;
-                }
-            }
-        }
-        return true;
+        return isNormalized;
     }
 
     /**
@@ -304,32 +346,41 @@
      * 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 normailzed path representation of this path
      * @see #isNormalized()
+     * @throws MalformedPathException if the path cannot be normalized.
      */
-    public Path getNormalizedPath() {
+    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.equals(CURRENT_ELEMENT)) {
+            if (elem.denotesCurrent()) {
                 continue;
-            } else if (elem.equals(PARENT_ELEMENT)) {
-                if (queue.size() > 0 && !queue.getLast().equals(PARENT_ELEMENT)) {
-                    queue.removeLast();
-                } else {
-                    queue.add(elem);
+            } 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();
+                last = queue.isEmpty() ? null : (PathElement) queue.getLast();
             } else {
-                queue.add(elem);
+                queue.add(last=elem);
             }
         }
-        return new Path((PathElement[]) queue.toArray(new PathElement[queue.size()]));
+        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);
     }
 
     /**
@@ -348,25 +399,7 @@
         if (!isAbsolute()) {
             throw new MalformedPathException("only an absolute path can be canonicalized.");
         }
-
-        LinkedList queue = new LinkedList();
-        for (int i = 0; i < elements.length; i++) {
-            PathElement elem = elements[i];
-
-            if (elem.equals(CURRENT_ELEMENT)) {
-                continue;
-            } else if (elem.equals(PARENT_ELEMENT)) {
-                if (queue.size() <= 1) {
-                    // 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();
-            } else {
-                queue.add(elem);
-            }
-        }
-        return new Path((PathElement[]) queue.toArray(new PathElement[queue.size()]));
+        return getNormalizedPath();
     }
 
     /**
@@ -383,7 +416,7 @@
      * </ul>
      * <p/>
      * Note that there migth be an unexpected result if <i>this</i> path is not
-     * canonical, e.g. the ancestor of degree = 1 of the path "../.." would
+     * 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.
@@ -406,7 +439,7 @@
         for (int i = 0; i < length; i++) {
             elements[i] = this.elements[i];
         }
-        return new Path(elements);
+        return new Path(elements, isNormalized);
     }
 
     /**
@@ -447,7 +480,8 @@
     /**
      * 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.
+     * 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.
@@ -459,9 +493,9 @@
     public int getDepth() {
         int depth = 0;
         for (int i = 0; i < elements.length; i++) {
-            if (elements[i].equals(PARENT_ELEMENT)) {
+            if (elements[i].denotesParent()) {
                 depth--;
-            } else if (!elements[i].equals(CURRENT_ELEMENT)) {
+            } else if (!elements[i].denotesCurrent()) {
                 depth++;
             }
         }
@@ -490,6 +524,7 @@
         // make sure we're comparing normalized paths
         Path p0 = getNormalizedPath();
         Path p1 = other.getNormalizedPath();
+
         if (p0.equals(p1)) {
             return false;
         }
@@ -517,9 +552,6 @@
      * @see #getDepth()
      */
     public boolean isDescendantOf(Path other) throws MalformedPathException {
-        if (equals(other)) {
-            return false;
-        }
         if (other == null) {
             throw new IllegalArgumentException("null argument");
         }
@@ -615,14 +647,18 @@
         // split into path elements
 
         // @todo find safe path separator char that does not conflict with chars in serialized QName
-        String[] elements = s.split("\t", -1);
+        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++) {
-            String elem = elements[i];
-            list.add(PathElement.fromString(elem));
+            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()]));
+        return new Path((PathElement[]) list.toArray(new PathElement[list.size()]), isNormalized);
     }
 
     /**
@@ -663,75 +699,134 @@
 
     //--------------------------------------------------------< inner classes >
     /**
-     * package private inner class used to build a path from path elements;
+     * package protected inner class used to build a path from path elements;
      * this class does not validate the format of the path elements!
      */
     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.
+         */
         PathBuilder() {
             queue = new LinkedList();
         }
 
+        /**
+         * Creates a new PathBuilder and initialized it with the given path
+         * elements.
+         *
+         * @param elements
+         */
         PathBuilder(PathElement[] elements) {
             this();
             addAll(elements);
         }
 
+        /**
+         * Adds the {@link Path#ROOT_ELEMENT}.
+         */
         void addRoot() {
-            queue.addFirst(ROOT_ELEMENT);
+            addFirst(ROOT_ELEMENT);
         }
 
+        /**
+         * Adds the given elemenets
+         * @param elements
+         */
         void addAll(PathElement[] elements) {
             for (int i = 0; i < elements.length; i++) {
-                queue.add(elements[i]);
+                addLast(elements[i]);
             }
         }
 
-        void addFirst(String nameSpaceURI, String localName) {
-            queue.addFirst(new PathElement(nameSpaceURI, localName));
-        }
-
-        void addFirst(String nameSpaceURI, String localName, int index) {
-            queue.addFirst(new PathElement(nameSpaceURI, localName, index));
+        /**
+         * 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
+         */
         void addFirst(QName name) {
-            queue.addFirst(new PathElement(name));
+            addFirst(new PathElement(name));
         }
 
+        /**
+         * Inserts the element at the beginning of the path to be built.
+         * @param name
+         * @param index
+         */
         void addFirst(QName name, int index) {
-            queue.addFirst(new PathElement(name, index));
-        }
-
-        void addLast(String nameSpaceURI, String localName) {
-            queue.addLast(new PathElement(nameSpaceURI, localName));
+            addFirst(new PathElement(name, index));
         }
 
-        void addLast(String nameSpaceURI, String localName, int index) {
-            queue.addLast(new PathElement(nameSpaceURI, localName, 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
+         */
         void addLast(QName name) {
-            queue.addLast(new PathElement(name));
+            addLast(new PathElement(name));
         }
 
+        /**
+         * Inserts the element at the end of the path to be built.
+         * @param name
+         * @param index
+         */
         void addLast(QName name, int index) {
-            queue.addLast(new PathElement(name, index));
+            addLast(new PathElement(name, index));
         }
 
+        /**
+         * Assembles the built path and returns a new {@link Path}.
+         * @return
+         * @throws MalformedPathException if the internal path element queue is empty.
+         */
         Path getPath() throws MalformedPathException {
             PathElement[] elements = (PathElement[]) queue.toArray(new PathElement[queue.size()]);
             // validate path
             if (elements.length == 0) {
                 throw new MalformedPathException("empty path");
             }
-            for (int i = 1; i < elements.length; i++) {
-                if (elements[i].denotesRoot()) {
-                    throw new MalformedPathException("path contains invalid root element(s)");
-                }
-            }
-            return new Path(elements);
+
+            // no need to check the path format, assuming all names correct
+            return new Path(elements, isNormalized);
         }
 
         public Object clone() {
@@ -1044,26 +1139,42 @@
     }
 
     //-------------------------------------------------------< implementation >
-    private static PathElement[] parse(String jcrPath, PathElement[] master,
-                                       NamespaceResolver resolver)
+
+    /**
+     * 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 (jcrPath.equals("/")) {
-            return new PathElement[]{ROOT_ELEMENT};
+            return ROOT;
         }
 
         // split path into path elements
-        String[] elems = jcrPath.split("/", -1);
+        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.isNormalized;
             // a master path was specified; the 'path' argument is assumed
             // to be a relative path
-            for (int i = 0; i < master.length; i++) {
-                list.add(master[i]);
+            for (int i = 0; i < master.elements.length; i++) {
+                list.add(master.elements[i]);
+                leadingParent &= master.elements[i].denotesParent();
             }
         }
 
@@ -1080,16 +1191,25 @@
                     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
 
@@ -1131,12 +1251,16 @@
                         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");
             }
         }
-        return (PathElement[]) list.toArray(new PathElement[list.size()]);
+        return resolver == null
+                ? null
+                : new Path((PathElement[]) list.toArray(new PathElement[list.size()]), isNormalized);
     }
+
 }

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/QName.java
Url: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/QName.java?view=diff&rev=125923&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/QName.java&r1=125922&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/QName.java&r2=125923
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/QName.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/QName.java	Fri Jan 21 03:54:11 2005
@@ -18,12 +18,29 @@
 
 import javax.jcr.NamespaceException;
 import java.io.Serializable;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.regex.Matcher;
 
 /**
  * <code>QName</code> represents the qualified name of a repository item
  * (i.e. <code>Node</code> or <code>Property</code>) or a node type.
+ * <p>
+ * The external string representation is specified as follows:
+ * <xmp>
+ *           name ::= [prefix ':'] simplename
+ *         prefix ::= << Any valid XML Name >>
+ *     simplename ::= nonspacestring [[string] nonspacestring]
+ *         string ::= [string] char
+ *           char ::= nonspace | space
+ * nonspacestring ::= [nonspacestring] nonspace
+ *          space ::= << ' ' (the space character) >>
+ *       nonspace ::= << Any Unicode character except
+ *                    '/', ':', '[', ']', '*',
+ *                    '''(the single quote),
+ *                    '"'(the double quote),
+ *                    any whitespace character >>
+ * </xmp>
+ *
  */
 public class QName implements Cloneable, Comparable, Serializable {
 
@@ -45,6 +62,7 @@
     protected final String namespaceURI;
     protected final String localName;
 
+
     /**
      * Creates a new <code>QName</code> instance with the given <code>namespaceURI</code>
      * and <code>localName</code>.
@@ -75,40 +93,10 @@
      */
     public static QName fromJCRName(String rawName, NamespaceResolver resolver)
             throws IllegalNameException, UnknownPrefixException {
-        if (rawName == null || rawName.length() == 0) {
-            throw new IllegalNameException("empty name");
+        if (resolver==null) {
+            throw new NullPointerException("resolver must not be null");
         }
-
-        String prefix = null;
-        String localName = null;
-
-        Matcher matcher = NAME_PATTERN.matcher(rawName);
-        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);
-            } else {
-                // no prefix specified
-                prefix = "";
-            }
-
-            // group 3 is localName
-            localName = matcher.group(3);
-        } else {
-            // illegal syntax for name
-            throw new IllegalNameException("'" + rawName + "' is not a valid name");
-        }
-
-        String uri;
-        try {
-            uri = resolver.getURI(prefix);
-        } catch (NamespaceException nse) {
-            throw new UnknownPrefixException(prefix);
-        }
-
-        return new QName(uri, localName);
+        return internalFromJCRName(rawName, resolver);
     }
 
     /**
@@ -156,17 +144,69 @@
      *                              JCR-style name.
      */
     public static void checkFormat(String jcrName) throws IllegalNameException {
-        if (jcrName == null || jcrName.length() == 0) {
+        try {
+            internalFromJCRName(jcrName, null);
+        } catch (UnknownPrefixException e) {
+            // ignore, will never happen
+        }
+    }
+
+    /**
+     * Parses the <code>jcrName</code>, resolves the prefix using the namespace
+     * resolver and returns a new QName instance. this method is also used
+     * internally just to check the format of the given string by passing a
+     * <code>null</code> value as <code>resolver</code>
+     *
+     * @param rawName the jcr name to parse
+     * @param resolver the namespace resolver or <code>null</code>
+     * @return a new resolved QName
+     * @throws IllegalNameException
+     * @throws UnknownPrefixException
+     */
+    public static QName internalFromJCRName(String rawName, NamespaceResolver resolver)
+            throws IllegalNameException, UnknownPrefixException {
+
+        if (rawName == null || rawName.length() == 0) {
             throw new IllegalNameException("empty name");
         }
 
-        Matcher matcher = NAME_PATTERN.matcher(jcrName);
-        if (!matcher.matches()) {
+        String prefix = null;
+        String localName = null;
+
+        Matcher matcher = NAME_PATTERN.matcher(rawName);
+        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);
+            } 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");
+            throw new IllegalNameException("'" + rawName + "' is not a valid name");
+        }
+
+        if (resolver==null) {
+            return null;
+        } else {
+            String uri;
+            try {
+                uri = resolver.getURI(prefix);
+            } catch (NamespaceException nse) {
+                throw new UnknownPrefixException(prefix);
+            }
+
+            return new QName(uri, localName);
         }
     }
 
+
     //-------------------------------------------------------< public methods >
     /**
      * @return
@@ -230,8 +270,8 @@
         }
         if (obj instanceof QName) {
             QName other = (QName) obj;
-            return namespaceURI.equals(other.namespaceURI)
-                    && localName.equals(other.localName);
+            return localName.equals(other.localName)
+                    && namespaceURI.equals(other.namespaceURI);
         }
         return false;
     }
@@ -263,4 +303,5 @@
     public int compareTo(Object o) {
         return toString().compareTo(((QName) o).toString());
     }
+
 }

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/nodetype/ValueConstraint.java
Url: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/nodetype/ValueConstraint.java?view=diff&rev=125923&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/nodetype/ValueConstraint.java&r1=125922&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/nodetype/ValueConstraint.java&r2=125923
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/nodetype/ValueConstraint.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/nodetype/ValueConstraint.java	Fri Jan 21 03:54:11 2005
@@ -499,8 +499,14 @@
             case PropertyType.PATH:
                 Path p = (Path) value.internalValue();
                 // normalize paths before comparing them
-                Path p0 = path.getNormalizedPath();
-                Path p1 = p.getNormalizedPath();
+                Path p0 = null;
+                Path p1 = null;
+                try {
+                    p0 = path.getNormalizedPath();
+                    p1 = p.getNormalizedPath();
+                } catch (MalformedPathException e) {
+                    throw new ConstraintViolationException("path not valid: " + e);
+                }
                 if (deep) {
                     try {
                         if (!p0.isAncestorOf(p1)) {

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/util/Text.java
Url: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/util/Text.java?view=diff&rev=125923&p1=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/util/Text.java&r1=125922&p2=incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/util/Text.java&r2=125923
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/util/Text.java	(original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/util/Text.java	Fri Jan 21 03:54:11 2005
@@ -19,6 +19,7 @@
 import java.io.UnsupportedEncodingException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
 
 /**
  * This Class provides some text related utilities
@@ -100,6 +101,53 @@
             res.append(hexTable[b & 15]);
         }
         return res.toString();
+    }
+
+    /**
+     * returns an array of strings decomposed of the original string, split at
+     * every occurance of 'ch'. if 2 'ch' follow each other with no intermediate
+     * characters, empty "" entries are avoided.
+     *
+     * @param str the string to decompose
+     * @param ch the character to use a split pattern
+     * @return an array of strings
+     */
+    public static String[] explode(String str, int ch) {
+        return explode(str,ch,false);
+    }
+
+    /**
+     * returns an array of strings decomposed of the original string, split at
+     * every occurance of 'ch'.
+     * @param str the string to decompose
+     * @param ch the character to use a split pattern
+     * @param respectEmpty if <code>true</code>, empty elements are generated
+     * @return an array of strings
+     */
+    public static String[] explode(String str, int ch, boolean respectEmpty) {
+        if (str == null || str.length()==0) {
+            return new String[0];
+        }
+
+        ArrayList strings = new ArrayList();
+        int pos     = 0;
+        int lastpos = 0;
+
+        // add snipples
+        while ((pos = str.indexOf(ch, lastpos)) >= 0) {
+            if (pos-lastpos>0 || respectEmpty)
+                strings.add(str.substring(lastpos, pos));
+            lastpos = pos+1;
+        }
+        // add rest
+        if (lastpos < str.length()) {
+            strings.add(str.substring(lastpos));
+        } else if (respectEmpty && lastpos==str.length()) {
+            strings.add("");
+        }
+
+        // return stringarray
+        return (String[]) strings.toArray(new String[strings.size()]);
     }
 
 }