You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2006/07/10 09:08:39 UTC

svn commit: r420449 [1/2] - in /jackrabbit/trunk/jackrabbit/src: main/java/org/apache/jackrabbit/core/ main/java/org/apache/jackrabbit/core/lock/ main/java/org/apache/jackrabbit/core/state/ main/java/org/apache/jackrabbit/core/xml/ main/java/org/apache...

Author: angela
Date: Mon Jul 10 00:08:37 2006
New Revision: 420449

URL: http://svn.apache.org/viewvc?rev=420449&view=rev
Log:
JCR-473: Some enhancements to jackrabbit commons

Added:
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NameFormat.java   (with props)
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/PathFormat.java   (with props)
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/util/PathMap.java   (with props)
Removed:
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PathMap.java
Modified:
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/LocalNamespaceMappings.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/xml/NamespaceContext.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/AbstractNamespaceResolver.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NamespaceListener.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NamespaceResolver.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/Path.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/QName.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/util/Text.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/value/NameValue.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/value/PathValue.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/value/ValueFactoryImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/value/ValueHelper.java
    jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/name/PathTest.java

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java Mon Jul 10 00:08:37 2006
@@ -26,6 +26,7 @@
 import org.apache.jackrabbit.name.NamespaceResolver;
 import org.apache.jackrabbit.name.Path;
 import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.util.PathMap;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java Mon Jul 10 00:08:37 2006
@@ -23,6 +23,9 @@
 import org.apache.jackrabbit.name.NamespaceResolver;
 import org.apache.jackrabbit.name.NamespaceListener;
 import org.apache.jackrabbit.name.AbstractNamespaceResolver;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.name.PathFormat;
 import org.apache.commons.collections.map.LRUMap;
 
 import javax.jcr.NamespaceException;
@@ -106,6 +109,24 @@
     }
 
     /**
+     * @inheritDoc
+     * As currently paths are not cached, the call is delegated to
+     * {@link PathFormat#parse(String, NamespaceResolver)}.
+     */
+    public Path getQPath(String jcrPath) throws MalformedPathException {
+        return PathFormat.parse(jcrPath, this);
+    }
+
+    /**
+     * @inheritDoc
+     * As currently paths are not cached, the call is delegated to
+     * {@link PathFormat#format(Path, NamespaceResolver)}.
+     */
+    public String getJCRPath(Path qPath) throws NoPrefixDeclaredException {
+        return PathFormat.format(qPath, this);
+    }
+
+    /**
      * Disposes this <code>CachingNamespaceResolver</code>.
      */
     public void dispose() {
@@ -126,6 +147,15 @@
      * Invalidates all cached mappings.
      */
     public void namespaceRemapped(String oldPrefix, String newPrefix, String uri) {
+        qnameToJCRName.clear();
+        jcrNameToQName.clear();
+    }
+
+    /**
+     * @inheritDoc
+     * Invalidates all cached mappings.
+     */
+    public void namespaceRemoved(String uri) {
         qnameToJCRName.clear();
         jcrNameToQName.clear();
     }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/LocalNamespaceMappings.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/LocalNamespaceMappings.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/LocalNamespaceMappings.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/LocalNamespaceMappings.java Mon Jul 10 00:08:37 2006
@@ -296,4 +296,17 @@
             uriToPrefix.put(uri, uniquePrefix);
         }
     }
+
+    /**
+     * @inheritDoc
+     * This method gets called when an existing namespace is removed
+     * in the global NamespaceRegistry. Overridden in order to check
+     * for/resolve collision of new global prefix with existing local prefix.
+     */
+    public void namespaceRemoved(String uri) {
+        if (uriToPrefix.containsKey(uri)) {
+            String prefix = (String)uriToPrefix.remove(uri);
+            prefixToURI.remove(prefix);
+        }
+    }
 }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java Mon Jul 10 00:08:37 2006
@@ -1635,7 +1635,7 @@
         try {
             if (prop.getDefinition().getRequiredType() == PropertyType.UNDEFINED
                     && type != PropertyType.UNDEFINED) {
-                prop.setValue(ValueHelper.convert(values, type));
+                prop.setValue(ValueHelper.convert(values, type, session.getValueFactory()));
             } else {
                 prop.setValue(values);
             }
@@ -1998,7 +1998,7 @@
         try {
             if (prop.getDefinition().getRequiredType() == PropertyType.UNDEFINED
                     && type != PropertyType.UNDEFINED) {
-                prop.setValue(ValueHelper.convert(values, type));
+                prop.setValue(ValueHelper.convert(values, type, session.getValueFactory()));
             } else {
                 prop.setValue(values);
             }
@@ -2030,7 +2030,7 @@
         try {
             if (prop.getDefinition().getRequiredType() == PropertyType.UNDEFINED
                     && type != PropertyType.UNDEFINED) {
-                prop.setValue(ValueHelper.convert(values, type));
+                prop.setValue(ValueHelper.convert(values, type, session.getValueFactory()));
             } else {
                 prop.setValue(values);
             }
@@ -2094,7 +2094,7 @@
         try {
             if (prop.getDefinition().getRequiredType() == PropertyType.UNDEFINED
                     && type != PropertyType.UNDEFINED) {
-                prop.setValue(ValueHelper.convert(values, type));
+                prop.setValue(ValueHelper.convert(values, type, session.getValueFactory()));
             } else {
                 prop.setValue(values);
             }
@@ -2154,7 +2154,7 @@
         try {
             if (prop.getDefinition().getRequiredType() == PropertyType.UNDEFINED
                     && type != PropertyType.UNDEFINED) {
-                prop.setValue(ValueHelper.convert(value, type));
+                prop.setValue(ValueHelper.convert(value, type, session.getValueFactory()));
             } else {
                 prop.setValue(value);
             }
@@ -2186,7 +2186,7 @@
         try {
             if (prop.getDefinition().getRequiredType() == PropertyType.UNDEFINED
                     && type != PropertyType.UNDEFINED) {
-                prop.setValue(ValueHelper.convert(value, type));
+                prop.setValue(ValueHelper.convert(value, type, session.getValueFactory()));
             } else {
                 prop.setValue(value);
             }
@@ -2223,7 +2223,7 @@
         try {
             if (prop.getDefinition().getRequiredType() == PropertyType.UNDEFINED
                     && type != PropertyType.UNDEFINED) {
-                prop.setValue(ValueHelper.convert(value, type));
+                prop.setValue(ValueHelper.convert(value, type, session.getValueFactory()));
             } else {
                 prop.setValue(value);
             }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java Mon Jul 10 00:08:37 2006
@@ -1244,7 +1244,7 @@
     public ValueFactory getValueFactory()
             throws UnsupportedRepositoryOperationException, RepositoryException {
         if (valueFactory == null) {
-            valueFactory = new ValueFactoryImpl();
+            valueFactory = ValueFactoryImpl.getInstance();
         }
         return valueFactory;
     }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java Mon Jul 10 00:08:37 2006
@@ -21,7 +21,7 @@
 import org.apache.jackrabbit.core.ItemId;
 import org.apache.jackrabbit.core.NodeId;
 import org.apache.jackrabbit.core.NodeImpl;
-import org.apache.jackrabbit.core.PathMap;
+import org.apache.jackrabbit.util.PathMap;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.SessionListener;
 import org.apache.jackrabbit.core.fs.FileSystem;
@@ -852,7 +852,7 @@
 
     /**
      * Contains information about a lock and gets placed inside the child
-     * information of a {@link org.apache.jackrabbit.core.PathMap}.
+     * information of a {@link org.apache.jackrabbit.util.PathMap}.
      */
     class LockInfo extends AbstractLockInfo implements SessionListener {
 

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java Mon Jul 10 00:08:37 2006
@@ -18,7 +18,6 @@
 
 import org.apache.jackrabbit.core.PropertyId;
 import org.apache.jackrabbit.core.NodeId;
-import org.apache.jackrabbit.uuid.UUID;
 
 import java.io.Serializable;
 import java.util.ArrayList;

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/xml/NamespaceContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/xml/NamespaceContext.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/xml/NamespaceContext.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/xml/NamespaceContext.java Mon Jul 10 00:08:37 2006
@@ -27,6 +27,9 @@
 import org.apache.jackrabbit.name.NoPrefixDeclaredException;
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.name.UnknownPrefixException;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.name.PathFormat;
 
 /**
  * Hierarchically scoped namespace resolver. Each NamespaceContext instance
@@ -126,6 +129,14 @@
     /** {@inheritDoc} */
     public String getJCRName(QName name) throws NoPrefixDeclaredException {
         return name.toJCRName(this);
+    }
+
+    public Path getQPath(String jcrPath) throws MalformedPathException {
+        return PathFormat.parse(jcrPath, this);
+    }
+
+    public String getJCRPath(Path qPath) throws NoPrefixDeclaredException {
+        return PathFormat.format(qPath, this);
     }
 
     /** {@inheritDoc} */

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/AbstractNamespaceResolver.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/AbstractNamespaceResolver.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/AbstractNamespaceResolver.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/AbstractNamespaceResolver.java Mon Jul 10 00:08:37 2006
@@ -40,16 +40,30 @@
     /**
      * @inheritDoc
      */
-    public QName getQName(String name)
+    public QName getQName(String jcrName)
             throws IllegalNameException, UnknownPrefixException {
-        return QName.fromJCRName(name, this);
+        return NameFormat.parse(jcrName, this);
     }
 
     /**
      * @inheritDoc
      */
-    public String getJCRName(QName name) throws NoPrefixDeclaredException {
-        return name.toJCRName(this);
+    public String getJCRName(QName qName) throws NoPrefixDeclaredException {
+        return NameFormat.format(qName, this);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public Path getQPath(String jcrPath) throws MalformedPathException {
+        return PathFormat.parse(jcrPath, this);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public String getJCRPath(Path qPath) throws NoPrefixDeclaredException {
+        return PathFormat.format(qPath, this);
     }
 
     /**
@@ -162,6 +176,32 @@
         }
         for (int i = 0; i < currentListeners.length; i++) {
             currentListeners[i].namespaceRemapped(oldPrefix, newPrefix, uri);
+        }
+    }
+
+    /**
+     * Notifies the listeners that the namespace with the given <code>uri</code>
+     * has been removed from the mapping.
+     *
+     * @param uri the namespace uri.
+     * @see NamespaceListener#namespaceRemoved(String)
+     */
+    protected void notifyNamespaceRemoved(String uri) {
+        if (listeners == null) {
+            throw new UnsupportedOperationException("notifyNamespaceRemapped");
+        }
+        // removal is infrequent compared to listener registration
+        // -> use copy-on-read
+        NamespaceListener[] currentListeners;
+        synchronized (listeners) {
+            int i = 0;
+            currentListeners = new NamespaceListener[listeners.size()];
+            for (Iterator it = listeners.iterator(); it.hasNext();) {
+                currentListeners[i++] = (NamespaceListener) it.next();
+            }
+        }
+        for (int i = 0; i < currentListeners.length; i++) {
+            currentListeners[i].namespaceRemoved(uri);
         }
     }
 }

Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NameFormat.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NameFormat.java?rev=420449&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NameFormat.java (added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NameFormat.java Mon Jul 10 00:08:37 2006
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.XMLChar;
+
+import javax.jcr.NamespaceException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <code>NameFormat</code> formats a {@link QName} using a
+ * {@link NamespaceResolver}.
+ */
+public class NameFormat {
+
+    /**
+     * 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(
+            "(([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?):)?"
+            + "([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?)");
+
+    /**
+     * Matcher instance as thread-local.
+     */
+    private static final ThreadLocal NAME_MATCHER = new ThreadLocal() {
+        protected Object initialValue() {
+            return NAME_PATTERN.matcher("dummy");
+        }
+    };
+
+    /**
+     * 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 QName parse(String jcrName, NamespaceResolver nsResolver) throws IllegalNameException, UnknownPrefixException {
+        String[] parts = parse(jcrName);
+        String uri;
+        try {
+            uri = nsResolver.getURI(parts[0]);
+        } catch (NamespaceException nse) {
+            throw new UnknownPrefixException(parts[0]);
+        }
+
+        return new QName(uri, parts[1]);
+    }
+
+    /**
+     * 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 qName
+     * @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 = (Matcher) NAME_MATCHER.get();
+        matcher.reset(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};
+
+    }
+    /**
+     * 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);
+    }
+
+    /**
+     * Returns a string representation of the qualified <code>name</code> in the
+     * JCR name format.
+     *
+     * @param qName the qualified name to resolve.
+     * @param resolver the namespace resolver.
+     * @return JCR the formatted path.
+     * @throws NoPrefixDeclaredException if a namespace can not be resolved
+     * @see #format(QName, NamespaceResolver, StringBuffer)
+     */
+    public static String format(QName qName, NamespaceResolver resolver)
+            throws NoPrefixDeclaredException {
+        StringBuffer buf = new StringBuffer();
+        format(qName, resolver, buf);
+        return buf.toString();
+    }
+
+    /**
+     * Returns a string representation of the qualified <code>name</code> in the
+     * JCR name format.
+     *
+     * @param qName the qualified name to resolve.
+     * @param resolver the namespace resolver.
+     * @param buffer StringBuffer where the prefixed JCR name should be appended to.
+     * @return JCR the formatted path.
+     * @throws NoPrefixDeclaredException if a namespace can not be resolved
+     * @see #format(QName, NamespaceResolver)
+     */
+    public static void format(QName qName, NamespaceResolver resolver, StringBuffer buffer)
+            throws NoPrefixDeclaredException {
+        // prefix
+        String prefix;
+        try {
+            prefix = resolver.getPrefix(qName.getNamespaceURI());
+        } catch (NamespaceException nse) {
+            throw new NoPrefixDeclaredException("no prefix declared for URI: "
+                + qName.getNamespaceURI());
+        }
+        if (prefix.length() == 0) {
+            // default prefix (empty string)
+        } else {
+            buffer.append(prefix);
+            buffer.append(':');
+        }
+        // name
+        buffer.append(qName.getLocalName());
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NameFormat.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NameFormat.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NamespaceListener.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NamespaceListener.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NamespaceListener.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NamespaceListener.java Mon Jul 10 00:08:37 2006
@@ -39,4 +39,12 @@
      * @param uri    the namespace uri.
      */
     public void namespaceAdded(String prefix, String uri);
+
+    /**
+     * Notifies the listeners that the namespace with the given uri has been
+     * unregistered.
+     *
+     * @param uri    the namespace uri.
+     */
+    public void namespaceRemoved(String uri);
 }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NamespaceResolver.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NamespaceResolver.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NamespaceResolver.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/NamespaceResolver.java Mon Jul 10 00:08:37 2006
@@ -49,20 +49,37 @@
     /**
      * Parses the given prefixed JCR name into a qualified name.
      *
-     * @param name the raw name, potentially prefixed.
+     * @param jcrName the raw name, potentially prefixed.
      * @return the QName instance for the raw name.
      * @throws IllegalNameException   if the given name is not a valid JCR name
      * @throws UnknownPrefixException if the JCR name prefix does not resolve
      */
-    public QName getQName(String name)
+    public QName getQName(String jcrName)
             throws IllegalNameException, UnknownPrefixException;
 
     /**
      * Returns the qualified name in the prefixed JCR name format.
      *
-     * @param name a qualified name
+     * @param qName a qualified name
      * @return the raw JCR name
      * @throws NoPrefixDeclaredException if the namespace can not be resolved
      */
-    public String getJCRName(QName name) throws NoPrefixDeclaredException;
+    public String getJCRName(QName qName) throws NoPrefixDeclaredException;
+
+    /**
+     * Parses the given prefixed JCR Path into a qualified path.
+     *
+     * @param jcrPath the raw path, with potentially prefixed path elements.
+     * @return the Path instance for the raw path.
+     */
+    public Path getQPath(String jcrPath) throws MalformedPathException;
+
+    /**
+     * Returns the qualified path in the prefixed JCR path format.
+     *
+     * @param qPath a qualified path
+     * @return the corresponding JCR path, eventually containing prefixed elements.
+     * @throws NoPrefixDeclaredException if a namespace can not be resolved
+     */
+    public String getJCRPath(Path qPath) throws NoPrefixDeclaredException;
 }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/Path.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/Path.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/Path.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/Path.java Mon Jul 10 00:08:37 2006
@@ -17,15 +17,11 @@
 package org.apache.jackrabbit.name;
 
 import org.apache.jackrabbit.util.Text;
-import org.apache.jackrabbit.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;
+import javax.jcr.PathNotFoundException;
 
 /**
  * The <code>Path</code> utility class provides misc. methods to resolve and
@@ -90,17 +86,17 @@
     /**
      * the 'root' element. i.e. '/'
      */
-    private static final PathElement ROOT_ELEMENT = new RootElement();
+    public static final PathElement ROOT_ELEMENT = new RootElement();
 
     /**
      * the 'current' element. i.e. '.'
      */
-    private static final PathElement CURRENT_ELEMENT = new CurrentElement();
+    public static final PathElement CURRENT_ELEMENT = new CurrentElement();
 
     /**
      * the 'parent' element. i.e. '..'
      */
-    private static final PathElement PARENT_ELEMENT = new ParentElement();
+    public static final PathElement PARENT_ELEMENT = new ParentElement();
 
     /**
      * the root path
@@ -108,32 +104,19 @@
     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>
+     * Constant representing an undefined index value
      */
-    private static final Pattern PATH_ELEMENT_PATTERN =
-            Pattern.compile("(\\.)|"
-            + "(\\.\\.)|"
-            + "(([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?):)?"
-            + "([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?)"
-            + "(\\[([1-9]\\d*)\\])?");
+    public static final int INDEX_UNDEFINED = 0;
 
     /**
-     * Matcher instance as thread-local.
+     * Constant representing the default (initial) index value.
      */
-    private static final ThreadLocal PATH_ELEMENT_MATCHER = new ThreadLocal() {
-        protected Object initialValue() {
-            return PATH_ELEMENT_PATTERN.matcher("dummy");
-        }
-    };
+    public static final int INDEX_DEFAULT = 1;
+
+    /**
+     * Constant defining the depth of the root path
+     */
+    public static final int ROOT_DEPTH = 0;
 
     /**
      * the elements of this path
@@ -177,6 +160,18 @@
 
     //------------------------------------------------------< factory methods >
     /**
+     * @param elements
+     * @return
+     */
+    public static Path create(PathElement[] elements) {
+        // Path constructor uses elements array as is
+        // need to copy here because Path.create() is public
+        PathElement[] tmp = new PathElement[elements.length];
+        System.arraycopy(elements, 0, tmp, 0, elements.length);
+        return new Path(tmp, true);
+    }
+
+    /**
      * 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).
@@ -186,11 +181,12 @@
      * @param normalize
      * @return
      * @throws MalformedPathException
+     * @deprecated Use PathFormat#parse(String, NamespaceResolver)} instead.
      */
     public static Path create(String jcrPath, NamespaceResolver resolver,
                               boolean normalize)
             throws MalformedPathException {
-        Path path = parse(jcrPath, null, resolver);
+        Path path = PathFormat.parse(jcrPath, resolver);
         if (normalize) {
             return path.getNormalizedPath();
         } else {
@@ -209,11 +205,12 @@
      * @param canonicalize
      * @return
      * @throws MalformedPathException
+     * @deprecated Use {@link PathFormat#create(Path, String, NamespaceResolver)} instead.
      */
     public static Path create(Path parent, String relJCRPath,
                               NamespaceResolver resolver, boolean canonicalize)
             throws MalformedPathException {
-        Path path = parse(relJCRPath, parent, resolver);
+        Path path = PathFormat.parse(parent, relJCRPath, resolver);
         if (canonicalize) {
             return path.getCanonicalPath();
         } else {
@@ -231,7 +228,7 @@
      * @param relPath
      * @param normalize
      * @return
-     * @throws MalformedPathException
+     * @throws MalformedPathException if <code>relPath</code> is absolute
      */
     public static Path create(Path parent, Path relPath, boolean normalize)
             throws MalformedPathException {
@@ -239,7 +236,7 @@
             throw new MalformedPathException("relPath is not a relative path");
         }
 
-        PathBuilder pb = new PathBuilder(parent.getElements());
+        PathBuilder pb = new PathBuilder(parent);
         pb.addAll(relPath.getElements());
 
         Path path = pb.getPath();
@@ -256,15 +253,13 @@
      * the returned path will be normalized (or canonicalized, if the parent
      * path is absolute).
      *
-     * @param parent
-     * @param name
+     * @param parent the parent path
+     * @param name the name of the new path element.
      * @param normalize
-     * @return
-     * @throws MalformedPathException
+     * @return the new path.
      */
-    public static Path create(Path parent, QName name, boolean normalize)
-            throws MalformedPathException {
-        PathBuilder pb = new PathBuilder(parent.getElements());
+    public static Path create(Path parent, QName name, boolean normalize) throws MalformedPathException {
+        PathBuilder pb = new PathBuilder(parent);
         pb.addLast(name);
 
         Path path = pb.getPath();
@@ -277,21 +272,17 @@
 
     /**
      * 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).
+     * and the give name and index.
      *
-     * @param parent
-     * @param name
-     * @param index
+     * @param parent the paren tpath.
+     * @param name the name of the new path element.
+     * @param index the index of the new path element.
      * @param normalize
-     * @return
-     * @throws MalformedPathException
+     * @return the new path.
      */
-    public static Path create(Path parent, QName name, int index,
-                              boolean normalize)
+    public static Path create(Path parent, QName name, int index, boolean normalize)
             throws MalformedPathException {
-        PathBuilder pb = new PathBuilder(parent.getElements());
+        PathBuilder pb = new PathBuilder(parent);
         pb.addLast(name, index);
 
         Path path = pb.getPath();
@@ -312,11 +303,11 @@
      */
     public static Path create(QName name, int index)
             throws IllegalArgumentException {
-        if (index < 0) {
+        if (index < INDEX_UNDEFINED) {
             throw new IllegalArgumentException("index must not be negative: " + index);
         }
         PathElement elem;
-        if (index < 1) {
+        if (index < INDEX_DEFAULT) {
             elem = new PathElement(name);
         } else {
             elem = new PathElement(name, index);
@@ -336,132 +327,11 @@
      * @param resolver
      * @return
      * @throws MalformedPathException
+     * @deprecated use {@link PathFormat#parse(Path, String, NamespaceResolver)} instead.
      */
     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;
-            }
-            if (elem.length() == 0 && i == elems.length - 1) {
-                // ignore trailing '/'
-                break;
-            }
-            Matcher matcher = (Matcher) PATH_ELEMENT_MATCHER.get();
-            matcher.reset(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;
-        }
+        return PathFormat.parse(master, jcrPath, resolver);
     }
 
     //------------------------------------------------------< utility methods >
@@ -472,9 +342,10 @@
      * @param jcrPath the path to be checked
      * @throws MalformedPathException If <code>jcrPath</code> is not a valid
      *                                JCR-style path.
+     * @deprecated Use {@link PathFormat#checkFormat(String)} instead.
      */
     public static void checkFormat(String jcrPath) throws MalformedPathException {
-        parse(jcrPath, null, null);
+        PathFormat.checkFormat(jcrPath);
     }
 
     //-------------------------------------------------------< public methods >
@@ -622,28 +493,28 @@
 
         // 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])) {
+        for (int i = 0; i < p0.getElements().length && i < p1.getElements().length; i++) {
+            if (!p0.getElement(i).equals(p1.getElement(i))) {
                 break;
             }
             lengthCommon++;
         }
 
         PathBuilder pb = new PathBuilder();
-        if (lengthCommon < p0.elements.length) {
+        if (lengthCommon < p0.getElements().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;
+            int tmp = p0.getElements().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]);
+        for (int i = lengthCommon; i < p1.getElements().length; i++) {
+            pb.addLast(p1.getElement(i));
         }
         // we're done
         return pb.getPath();
@@ -739,7 +610,7 @@
      * @see #getAncestorCount()
      */
     public int getDepth() {
-        int depth = 0;
+        int depth = ROOT_DEPTH;
         for (int i = 0; i < elements.length; i++) {
             if (elements[i].denotesParent()) {
                 depth--;
@@ -780,8 +651,8 @@
         if (p0.getDepth() >= p1.getDepth()) {
             return false;
         }
-        for (int i = 0; i < p0.elements.length; i++) {
-            if (!p0.elements[i].equals(p1.elements[i])) {
+        for (int i = 0; i < p0.getElements().length; i++) {
+            if (!p0.getElement(i).equals(p1.getElement(i))) {
                 return false;
             }
         }
@@ -825,31 +696,31 @@
     }
 
     /**
+     * Returns the <code>i</code><sup>th</sup> element of this path.
+     *
+     * @param i element index.
+     * @return the <code>i</code><sup>th</sup> element of this path.
+     * @throws ArrayIndexOutOfBoundsException if this path does not have an
+     * element at index <code>i</code>.
+     */
+    public PathElement getElement(int i) {
+        return elements[i];
+    }
+
+    /**
      * 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
+     * @deprecated Use {@link PathFormat#format(Path, NamespaceResolver} instead.
      */
-    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();
+    public String toJCRPath(NamespaceResolver resolver) throws NoPrefixDeclaredException {
+        return PathFormat.format(this, resolver);
     }
 
+    //---------------------------------------------------------------< Object >
     /**
      * Returns the internal string representation of this <code>Path</code>.
      * <p/>
@@ -944,7 +815,7 @@
         }
         if (obj instanceof Path) {
             Path other = (Path) obj;
-            return Arrays.equals(elements, other.elements);
+            return Arrays.equals(elements, other.getElements());
         }
         return false;
     }
@@ -995,6 +866,17 @@
         }
 
         /**
+         * Creates a new PathBuilder and initialized it with elements of the
+         * given path.
+         *
+         * @param parent
+         */
+        public PathBuilder(Path parent) {
+            this();
+            addAll(parent.getElements());
+        }
+
+        /**
          * Adds the {@link Path#ROOT_ELEMENT}.
          */
         public void addRoot() {
@@ -1106,36 +988,49 @@
         static final String LITERAL = "*";
 
         private RootElement() {
-            super(QName.NS_DEFAULT_URI, "");
+            super(QName.ROOT);
         }
 
-        // PathElement override
+        /**
+         * Returns true.
+         * @return true
+         * @see PathElement#denotesRoot()
+         */
         public boolean denotesRoot() {
             return true;
         }
 
-        // PathElement override
+        /**
+         * Returns false.
+         * @return false
+         * @see PathElement#denotesCurrent()
+         */
         public boolean denotesCurrent() {
             return false;
         }
 
-        // PathElement override
+        /**
+         * Returns false.
+         * @return false
+         * @see PathElement#denotesParent()
+         */
         public boolean denotesParent() {
             return false;
         }
 
-        // PathElement override
+        /**
+         * Returns false.
+         * @return false
+         * @see PathElement#denotesName()
+         */
         public boolean denotesName() {
             return false;
         }
 
-        // PathElement override
-        public String toJCRName(NamespaceResolver resolver)
-                throws NoPrefixDeclaredException {
-            return "";
-        }
-
-        // Object override
+        /**
+         * @return {@link #LITERAL}
+         * @see Object#toString()
+         */
         public String toString() {
             return LITERAL;
         }
@@ -1148,72 +1043,120 @@
             super(QName.NS_DEFAULT_URI, LITERAL);
         }
 
-        // PathElement override
+        /**
+         * Returns false.
+         * @return false
+         * @see PathElement#denotesRoot()
+         */
         public boolean denotesRoot() {
             return false;
         }
 
-        // PathElement override
+        /**
+         * Returns true.
+         * @return true
+         */
         public boolean denotesCurrent() {
             return true;
         }
 
-        // PathElement override
+        /**
+         * Returns false.
+         * @return false
+         * @see PathElement#denotesParent()
+         */
         public boolean denotesParent() {
             return false;
         }
 
-        // PathElement override
+        /**
+         * Returns false.
+         * @return false
+         * @see PathElement#denotesName()
+         */
         public boolean denotesName() {
             return false;
         }
 
-        // PathElement override
+        /**
+         * Returns the JCR name of this path element.
+         *
+         * @param resolver
+         * @return {@link #LITERAL}
+         */
         public String toJCRName(NamespaceResolver resolver)
                 throws NoPrefixDeclaredException {
             return LITERAL;
         }
 
-        // Object override
+
+        /**
+         * @return {@link #LITERAL}
+         * @see Object#toString()
+         */
         public String toString() {
             return LITERAL;
         }
     }
 
-    public static final class ParentElement extends PathElement {
+    private static final class ParentElement extends PathElement {
         static final String LITERAL = "..";
 
         private ParentElement() {
             super(QName.NS_DEFAULT_URI, LITERAL);
         }
 
-        // PathElement override
+        /**
+         * Returns false.
+         * @return false
+         * @see PathElement#denotesRoot()
+         */
         public boolean denotesRoot() {
             return false;
         }
 
-        // PathElement override
+        /**
+         * Returns false.
+         * @return false
+         * @see PathElement#denotesCurrent()
+         */
         public boolean denotesCurrent() {
             return false;
         }
 
-        // PathElement override
+        /**
+         * Returns true.
+         * @return true
+         * @see PathElement#denotesParent()
+         */
         public boolean denotesParent() {
             return true;
         }
 
-        // PathElement override
+        /**
+         * Returns false.
+         * @return false
+         * @see PathElement#denotesName()
+         */
         public boolean denotesName() {
             return false;
         }
 
-        // PathElement override
+        /**
+         * Returns the JCR name of this path element.
+         *
+         * @param resolver
+         * @return {@link #LITERAL}
+         */
         public String toJCRName(NamespaceResolver resolver)
                 throws NoPrefixDeclaredException {
             return LITERAL;
         }
 
-        // Object override
+        /**
+         * @return {@link #LITERAL}
+         * @see Object#toString()
+         */
         public String toString() {
             return LITERAL;
         }
@@ -1273,7 +1216,7 @@
                 throw new IllegalArgumentException("name must not be null");
             }
             this.name = name;
-            this.index = 0;
+            this.index = INDEX_UNDEFINED;
         }
 
         /**
@@ -1287,7 +1230,7 @@
             if (name == null) {
                 throw new IllegalArgumentException("name must not be null");
             }
-            if (index < 1) {
+            if (index < INDEX_DEFAULT) {
                 throw new IllegalArgumentException("index is 1-based");
             }
             this.index = index;
@@ -1314,6 +1257,18 @@
         }
 
         /**
+         * Returns the normalized index of this path element, i.e. the index
+         * is always equals or greater that {@link #INDEX_DEFAULT}.
+         */
+        public int getNormalizedIndex() {
+            if (index == INDEX_UNDEFINED) {
+                return INDEX_DEFAULT;
+            } else {
+                return index;
+            }
+        }
+
+        /**
          * Returns <code>true</code> if this element denotes the <i>root</i> element,
          * otherwise returns <code>false</code>.
          *
@@ -1389,7 +1344,7 @@
         public void toJCRName(NamespaceResolver resolver, StringBuffer buf)
                 throws NoPrefixDeclaredException {
             // name
-            name.toJCRName(resolver, buf);
+            NameFormat.format(name, resolver, buf);
             // index
             int index = getIndex();
             /**
@@ -1411,6 +1366,7 @@
          * method to get the prefixed string representation of the path element.
          *
          * @return string representation of the path element
+         * @see Object#toString()
          */
         public String toString() {
             StringBuffer sb = new StringBuffer();
@@ -1418,7 +1374,7 @@
             sb.append(name.toString());
             // index
             int index = getIndex();
-            if (index > 0) {
+            if (index > INDEX_UNDEFINED) {
                 sb.append('[');
                 sb.append(index);
                 sb.append(']');

Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/PathFormat.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/PathFormat.java?rev=420449&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/PathFormat.java (added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/PathFormat.java Mon Jul 10 00:08:37 2006
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.Text;
+import org.apache.jackrabbit.util.XMLChar;
+
+import javax.jcr.NamespaceException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <code>PathFormat</code> formats a {@link Path} using a
+ * {@link NamespaceResolver}.
+ */
+public class PathFormat {
+
+    /**
+     * 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*)\\])?");
+
+    /**
+     * Matcher instance as thread-local.
+     */
+    private static final ThreadLocal PATH_ELEMENT_MATCHER = new ThreadLocal() {
+        protected Object initialValue() {
+            return PATH_ELEMENT_PATTERN.matcher("dummy");
+        }
+    };
+
+    /**
+     * Parses <code>jcrPath</code> into a qualified path using
+     * <code>resolver</code> to convert prefixes into namespace URI's.
+     *
+     * @param jcrPath the jcr path.
+     * @param resolver the namespace resolver.
+     * @return qualified path.
+     * @throws MalformedPathException if <code>jcrPath</code> is malformed.
+     */
+    public static Path parse(String jcrPath, NamespaceResolver resolver)
+            throws MalformedPathException {
+        return parse(null, jcrPath, resolver);
+    }
+
+    /**
+     * Parses the give <code>jcrPath</code> and returns a <code>Path</code>. If
+     * <code>parent</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 parent   the parent path
+     * @param jcrPath  the JCR path
+     * @param resolver the namespace resolver to get prefixes for namespace
+     *                 URI's.
+     * @return the fully qualified Path.
+     * @throws MalformedPathException if <code>jcrPath</code> is malformed.
+     */
+    public static Path parse(Path parent, String jcrPath, NamespaceResolver resolver)
+            throws MalformedPathException {
+        // shortcut
+        if ("/".equals(jcrPath)) {
+            return Path.ROOT;
+        }
+
+        // split path into path elements
+        String[] elems = Text.explode(jcrPath, '/', true);
+        if (elems.length == 0) {
+            throw new MalformedPathException("empty path");
+        }
+
+        Path.PathBuilder pathBuilder;
+        if (parent != null) {
+            // a parent path was specified; the 'jcrPath' argument is assumed
+            // to be a relative path
+            pathBuilder = new Path.PathBuilder(parent);
+        } else {
+            pathBuilder = new Path.PathBuilder();
+        }
+
+        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 (parent != null) {
+                    throw new MalformedPathException("'" + jcrPath + "' is not a relative path");
+                }
+                pathBuilder.addLast(Path.ROOT_ELEMENT);
+                continue;
+            }
+            if (elem.length() == 0 && i == elems.length - 1) {
+                // ignore trailing '/'
+                break;
+            }
+            Matcher matcher = (Matcher) PATH_ELEMENT_MATCHER.get();
+            matcher.reset(elem);
+            if (matcher.matches()) {
+                if (resolver == null) {
+                    // check only
+                    continue;
+                }
+
+                if (matcher.group(1) != null) {
+                    // group 1 is .
+                    pathBuilder.addLast(Path.CURRENT_ELEMENT);
+                } else if (matcher.group(2) != null) {
+                    // group 2 is ..
+                    pathBuilder.addLast(Path.PARENT_ELEMENT);
+                } 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 = org.apache.jackrabbit.name.Path.INDEX_UNDEFINED;
+                    }
+
+                    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");
+                    }
+
+                    if (index == org.apache.jackrabbit.name.Path.INDEX_UNDEFINED) {
+                        pathBuilder.addLast(new QName(nsURI, localName));
+                    } else {
+                        pathBuilder.addLast(new QName(nsURI, localName), index);
+                    }
+                }
+            } 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 pathBuilder.getPath();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 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(null, jcrPath, null);
+    }
+
+    /**
+     * Returns a string representation of the qualified <code>path</code> in the
+     * JCR path format.
+     *
+     * @param path the qualified path to resolve.
+     * @param resolver the namespace resolver.
+     * @return JCR the formatted path.
+     * @throws NoPrefixDeclaredException if a namespace can not be resolved
+     */
+    public static String format(Path path, NamespaceResolver resolver)
+            throws NoPrefixDeclaredException {
+        if (path.denotesRoot()) {
+            // shortcut
+            return "/";
+        }
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < path.getLength(); i++) {
+            if (i > 0) {
+                sb.append('/');
+            }
+            // name
+            format(path.getElement(i), resolver, sb);
+        }
+        return sb.toString();
+    }
+
+    //-----------------------------------------------------------< internal >---
+
+    /**
+     * Appends the JCR name representation of this path element to the given
+     * string buffer.
+     *
+     * @param element  the path element to format.
+     * @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
+     */
+    private static void format(Path.PathElement element, NamespaceResolver resolver, StringBuffer buf)
+            throws NoPrefixDeclaredException {
+        // name
+        buf.append(resolver.getJCRName(element.getName()));
+        // index
+        int index = element.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 > org.apache.jackrabbit.name.Path.INDEX_DEFAULT) {
+            buf.append('[');
+            buf.append(index);
+            buf.append(']');
+        }
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/PathFormat.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/PathFormat.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/QName.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/QName.java?rev=420449&r1=420448&r2=420449&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/QName.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/name/QName.java Mon Jul 10 00:08:37 2006
@@ -16,13 +16,7 @@
  */
 package org.apache.jackrabbit.name;
 
-import javax.jcr.NamespaceException;
-
-import org.apache.jackrabbit.util.XMLChar;
-
 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
@@ -108,6 +102,11 @@
     //------------------------------------------< general item name constants >
 
     /**
+     * Extra QName for the root node
+     */
+    public static final QName ROOT = new QName(NS_DEFAULT_URI,"");
+
+    /**
      * jcr:system
      */
     public static final QName JCR_SYSTEM = new QName(NS_JCR_URI, "system");
@@ -504,30 +503,6 @@
 
     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(
-            "(([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?):)?"
-            + "([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?)");
-
-    /**
-     * Matcher instance as thread-local.
-     */
-    private static final ThreadLocal NAME_MATCHER = new ThreadLocal() {
-        protected Object initialValue() {
-            return NAME_PATTERN.matcher("dummy");
-        }
-    };
-
     /** The memorized hash code of this qualified name. */
     private transient int hash;
 
@@ -545,10 +520,11 @@
      * local part.
      * <p/>
      * Note that the format of the local part is not validated. The format
-     * can be checked by calling {@link #checkFormat(String)}.
+     * can be checked by calling {@link NameFormat#checkFormat(String)}.
      *
      * @param namespaceURI namespace uri
      * @param localName local part
+     * @throws IllegalArgumentException if <code>localName</code> is invalid.
      */
     public QName(String namespaceURI, String localName) {
         if (namespaceURI == null) {
@@ -576,29 +552,11 @@
      * @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
+     * @deprecated Use {@link NameFormat#parse(String, NamespaceResolver)} instead
      */
     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]);
+        return NameFormat.parse(rawName, resolver);
     }
 
     /**
@@ -659,48 +617,10 @@
      *         the prefix (or empty string), the second the local name.
      * @throws IllegalNameException If <code>jcrName</code> is not a valid
      *                              JCR-style name.
+     * @deprecated Use {@link NameFormat#parse(String)} instead.
      */
     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 = (Matcher) NAME_MATCHER.get();
-        matcher.reset(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};
+        return NameFormat.parse(jcrName);
     }
 
     //-------------------------------------------------------< public methods >
@@ -730,12 +650,12 @@
      * @param resolver namespace resolver
      * @return prefixed name
      * @throws NoPrefixDeclaredException if the namespace can not be resolved
+     * @deprecated Use {@link NameFormat#format(QName, NamespaceResolver)}
+     * instead.
      */
     public String toJCRName(NamespaceResolver resolver)
             throws NoPrefixDeclaredException {
-        StringBuffer sb = new StringBuffer();
-        toJCRName(resolver, sb);
-        return sb.toString();
+        return NameFormat.format(this, resolver);
     }
 
     /**
@@ -748,27 +668,15 @@
      *                 appended to
      * @throws NoPrefixDeclaredException if the namespace can not be resolved
      * @see #toJCRName(NamespaceResolver)
+     * @deprecated Use {@link NameFormat#format(QName, NamespaceResolver, StringBuffer)}
+     * instead.
      */
     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);
+        NameFormat.format(this, resolver, buf);
     }
 
+    //---------------------------------------------------------------< Object >
     /**
      * Returns the string representation of this <code>QName</code> in the
      * following format:
@@ -777,6 +685,7 @@
      *
      * @return the string representation of this <code>QName</code>.
      * @see #valueOf(String)
+     * @see Object#toString()
      */
     public String toString() {
         // QName is immutable, we can store the string representation
@@ -830,6 +739,7 @@
         return h;
     }
 
+    //------------------------------------------------------------< Cloneable >
     /**
      * Creates a clone of this qualified name.
      * Overriden in order to make <code>clone()</code> public.
@@ -843,6 +753,7 @@
         return super.clone();
     }
 
+    //-----------------------------------------------------------< Comparable >
     /**
      * Compares two qualified names.
      *