Return-Path: Delivered-To: apmail-incubator-jackrabbit-commits-archive@www.apache.org Received: (qmail 73408 invoked from network); 21 Jan 2005 11:54:19 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur-2.apache.org with SMTP; 21 Jan 2005 11:54:19 -0000 Received: (qmail 60599 invoked by uid 500); 21 Jan 2005 11:54:18 -0000 Mailing-List: contact jackrabbit-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: jackrabbit-dev@incubator.apache.org Delivered-To: mailing list jackrabbit-commits@incubator.apache.org Received: (qmail 60586 invoked by uid 500); 21 Jan 2005 11:54:18 -0000 Delivered-To: apmail-incubator-jackrabbit-cvs@incubator.apache.org Received: (qmail 60574 invoked by uid 99); 21 Jan 2005 11:54:17 -0000 X-ASF-Spam-Status: No, hits=-9.8 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from minotaur.apache.org (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.28) with SMTP; Fri, 21 Jan 2005 03:54:16 -0800 Received: (qmail 73366 invoked by uid 65534); 21 Jan 2005 11:54:15 -0000 Date: 21 Jan 2005 11:54:15 -0000 Message-ID: <20050121115415.73358.qmail@minotaur.apache.org> From: tripod@apache.org To: jackrabbit-cvs@incubator.apache.org Subject: svn commit: r125923 - in incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core: . nodetype util MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 X-Virus-Checked: Checked X-Spam-Rating: minotaur-2.apache.org 1.6.2 0/1000/N 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 Path utility class provides - * misc. methods to resolve and nornalize JCR-style item paths. + * The Path utility class provides misc. methods to resolve and + * nornalize JCR-style item paths.
+ * Each path consistnes of path elements and is immutable. it has the following + * properties:
+ * isAbsolute():
+ * A path is absolute, if the first path element denotes the root element '/'. + *

+ * isRelative():
+ * A path is relative, if the first path element does not denote the root element. + * I.e. is always the opposite of isAbsolute. + *

+ * isNormalized():
+ * 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.
+ *

+ * isCanonical():
+ * A path is canonical, if its absolute and normalized. + *

+ * + * the external string representation of a path has the following format: + * + *

+ * 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 >> + * */ 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:

*

    *
  • 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 Path from the given jcrPath + * string. If normalize is true, 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 Path out of the given parent path + * and a relative path string. If canonicalize is + * true, 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 Path out of the given parent path + * string and the given relative path string. If normalize is + * true, 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 Path out of the given parent path + * string and the give name. If normalize is true, + * 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 Path out of the given parent path + * string and the give name and index. If normalize is + * true, 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. + *

    + * 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 @@ *

*

* Note that there migth be an unexpected result if this 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. *

* 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 + * master is not null, it is prepended to the + * returned list. If resolver is null, this + * method only checks the format of the string and returns null. + * + * @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; /** * QName represents the qualified name of a repository item * (i.e. Node or Property) or a node type. + *

+ * The external string representation is specified as follows: + *

+ * 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 >> + * + * */ public class QName implements Cloneable, Comparable, Serializable { @@ -45,6 +62,7 @@ protected final String namespaceURI; protected final String localName; + /** * Creates a new QName instance with the given namespaceURI * and localName. @@ -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 jcrName, 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 + * null value as resolver + * + * @param rawName the jcr name to parse + * @param resolver the namespace resolver or null + * @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 true, 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()]); } }