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 2007/10/18 20:35:01 UTC

svn commit: r586058 [2/4] - in /jackrabbit/trunk/jackrabbit-spi-commons: ./ src/main/java/org/apache/jackrabbit/conversion/ src/main/java/org/apache/jackrabbit/identifier/ src/main/java/org/apache/jackrabbit/lock/ src/main/java/org/apache/jackrabbit/na...

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/lock/Locked.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/lock/Locked.java?rev=586058&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/lock/Locked.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/lock/Locked.java Thu Oct 18 11:34:57 2007
@@ -0,0 +1,270 @@
+/*
+ * 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.lock;
+
+import org.apache.jackrabbit.namespace.SessionNamespaceResolver;
+import org.apache.jackrabbit.name.NameConstants;
+import org.apache.jackrabbit.conversion.NamePathResolver;
+import org.apache.jackrabbit.conversion.DefaultNamePathResolver;
+import org.apache.jackrabbit.conversion.NameException;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Session;
+import javax.jcr.Repository;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.Event;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+
+/**
+ * <code>Locked</code> is a utility to synchronize modifications on a lockable
+ * node. The modification is applied while the lock on the node is held, thus
+ * ensuring that the modification will never fail with an {@link
+ * javax.jcr.InvalidItemStateException}. This utility can be used with any
+ * JCR Repository, not just Jackrabbit.
+ * <p/>
+ * The following example shows how this utility can be used to implement
+ * a persistent counter:
+ * <pre>
+ * Node counter = ...;
+ * long nextValue = ((Long) new Locked() {
+ *     protected Object run(Node counter) throws RepositoryException {
+ *         Property seqProp = counter.getProperty("value");
+ *         long value = seqProp.getLong();
+ *         seqProp.setValue(++value);
+ *         seqProp.save();
+ *         return new Long(value);
+ *     }
+ * }.with(counter, false)).longValue();
+ * </pre>
+ * If you specify a <code>timeout</code> you need to check the return value
+ * whether the <code>run</code> method could be executed within the timeout
+ * period:
+ * <pre>
+ * Node counter = ...;
+ * Object ret = new Locked() {
+ *     protected Object run(Node counter) throws RepositoryException {
+ *         Property seqProp = counter.getProperty("value");
+ *         long value = seqProp.getLong();
+ *         seqProp.setValue(++value);
+ *         seqProp.save();
+ *         return new Long(value);
+ *     }
+ * }.with(counter, false);
+ * if (ret == Locked.TIMED_OUT) {
+ *     // do whatever you think is appropriate in this case
+ * } else {
+ *     // get the value
+ *     long nextValue = ((Long) ret).longValue();
+ * }
+ * </pre>
+ */
+public abstract class Locked {
+
+    /**
+     * Object returned when timeout is reached without being able to call
+     * {@link #run} while holding the lock.
+     */
+    public static final Object TIMED_OUT = new Object();
+
+    /**
+     * Executes {@link #run} while the lock on <code>lockable</code> is held.
+     * This method will block until {@link #run} is executed while holding the
+     * lock on node <code>lockable</code>.
+     *
+     * @param lockable a lockable node.
+     * @param isDeep   <code>true</code> if <code>lockable</code> will be locked
+     *                 deep.
+     * @return the object returned by {@link #run}.
+     * @throws IllegalArgumentException if <code>lockable</code> is not
+     *      <i>mix:lockable</i>.
+     * @throws RepositoryException  if {@link #run} throws an exception.
+     * @throws InterruptedException if this thread is interrupted while waiting
+     *                              for the lock on node <code>lockable</code>.
+     */
+    public Object with(Node lockable, boolean isDeep)
+            throws RepositoryException, InterruptedException {
+        return with(lockable, isDeep, Long.MAX_VALUE);
+    }
+
+    /**
+     * Executes the method {@link #run} within the scope of a lock held on
+     * <code>lockable</code>.
+     *
+     * @param lockable the node where the lock is obtained from.
+     * @param isDeep   <code>true</code> if <code>lockable</code> will be locked
+     *                 deep.
+     * @param timeout  time in milliseconds to wait at most to aquire the lock.
+     * @return the object returned by {@link #run} or {@link #TIMED_OUT} if the
+     *         lock on <code>lockable</code> could not be aquired within the
+     *         specified timeout.
+     * @throws IllegalArgumentException if <code>timeout</code> is negative or
+     *                                  <code>lockable</code> is not
+     *                                  <i>mix:lockable</i>.
+     * @throws RepositoryException      if {@link #run} throws an exception.
+     * @throws UnsupportedRepositoryOperationException
+     *                                  if this repository does not support
+     *                                  locking.
+     * @throws InterruptedException     if this thread is interrupted while
+     *                                  waiting for the lock on node
+     *                                  <code>lockable</code>.
+     */
+    public Object with(Node lockable, boolean isDeep, long timeout)
+            throws UnsupportedRepositoryOperationException, RepositoryException, InterruptedException {
+        if (timeout < 0) {
+            throw new IllegalArgumentException("timeout must be >= 0");
+        }
+
+        Session session = lockable.getSession();
+        NamePathResolver resolver = new DefaultNamePathResolver(new SessionNamespaceResolver(session));
+
+        Lock lock;
+        EventListener listener = null;
+        try {
+            // check whether the lockable can be locked at all
+            if (!lockable.isNodeType(resolver.getJCRName(NameConstants.MIX_LOCKABLE))) {
+                throw new IllegalArgumentException("Node is not lockable");
+            }
+
+            lock = tryLock(lockable, isDeep);
+            if (lock != null) {
+                return runAndUnlock(lock);
+            }
+
+            if (timeout == 0) {
+                return TIMED_OUT;
+            }
+
+            long timelimit;
+            if (timeout == Long.MAX_VALUE) {
+                timelimit = Long.MAX_VALUE;
+            } else {
+                timelimit = System.currentTimeMillis() + timeout;
+            }
+
+            // node is locked by other session -> register event listener if possible
+            if (isObservationSupported(session)) {
+                ObservationManager om = session.getWorkspace().getObservationManager();
+                listener = new EventListener() {
+                    public void onEvent(EventIterator events) {
+                        synchronized (this) {
+                            this.notify();
+                        }
+                    }
+                };
+                om.addEventListener(listener, Event.PROPERTY_REMOVED,
+                        lockable.getPath(), false, null, null, true);
+            }
+
+            // now keep trying to aquire the lock
+            // using 'this' as a monitor allows the event listener to notify
+            // the current thread when the lockable node is possibly unlocked
+            for (; ;) {
+                synchronized (this) {
+                    lock = tryLock(lockable, isDeep);
+                    if (lock != null) {
+                        return runAndUnlock(lock);
+                    } else {
+                        // check timeout
+                        if (System.currentTimeMillis() > timelimit) {
+                            return TIMED_OUT;
+                        }
+                        if (listener != null) {
+                            // event listener *should* wake us up, however
+                            // there is a chance that removal of the lockOwner
+                            // property is notified before the node is acutally
+                            // unlocked. therefore we use a safety net to wait
+                            // at most 1000 millis.
+                            this.wait(Math.min(1000, timeout));
+                        } else {
+                            // repository does not support observation
+                            // wait at most 50 millis then retry
+                            this.wait(Math.min(50, timeout));
+                        }
+                    }
+                }
+            }
+        } catch (NameException e) {
+            throw new RepositoryException(e);
+        } finally {
+            if (listener != null) {
+                session.getWorkspace().getObservationManager().removeEventListener(listener);
+            }
+        }
+    }
+
+    /**
+     * This method is executed while holding the lock.
+     * @param node The <code>Node</code> on which the lock is placed.
+     * @return an object which is then returned by {@link #with with()}.
+     * @throws RepositoryException if an error occurs.
+     */
+    protected abstract Object run(Node node) throws RepositoryException;
+
+    /**
+     * Executes {@link #run} and unlocks the lockable node in any case, even
+     * when an exception is thrown.
+     *
+     * @param lock The <code>Lock</code> to unlock in any case before returning.
+     *
+     * @return the object returned by {@link #run}.
+     * @throws RepositoryException if an error occurs.
+     */
+    private Object runAndUnlock(Lock lock) throws RepositoryException {
+        try {
+            return run(lock.getNode());
+        } finally {
+            lock.getNode().unlock();
+        }
+    }
+
+    /**
+     * Tries to aquire a session scoped lock on <code>lockable</code>.
+     *
+     * @param lockable the lockable node
+     * @param isDeep   <code>true</code> if the lock should be deep
+     * @return The <code>Lock</code> or <code>null</code> if the
+     *         <code>lockable</code> cannot be locked.
+     * @throws UnsupportedRepositoryOperationException
+     *                             if this repository does not support locking.
+     * @throws RepositoryException if an error occurs
+     */
+    private static Lock tryLock(Node lockable, boolean isDeep)
+            throws UnsupportedRepositoryOperationException, RepositoryException {
+        try {
+            return lockable.lock(isDeep, true);
+        } catch (LockException e) {
+            // locked by some other session
+        }
+        return null;
+    }
+
+    /**
+     * Returns <code>true</code> if the repository supports observation.
+     *
+     * @param s a session of the repository.
+     * @return <code>true</code> if the repository supports observation.
+     */
+    private static boolean isObservationSupported(Session s) {
+        return "true".equalsIgnoreCase(s.getRepository().getDescriptor(Repository.OPTION_OBSERVATION_SUPPORTED));
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/lock/Locked.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/lock/Locked.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/NameConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/NameConstants.java?rev=586058&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/NameConstants.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/NameConstants.java Thu Oct 18 11:34:57 2007
@@ -0,0 +1,445 @@
+/*
+ * 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.spi.Name;
+import org.apache.jackrabbit.spi.NameFactory;
+
+/**
+ * <code>NameConstants</code>...
+ */
+public class NameConstants {
+
+    private static final NameFactory FACTORY = NameFactoryImpl.getInstance();
+
+    /**
+     * Extra Name for the root node
+     */
+    public static final Name ROOT = FACTORY.create(Name.NS_DEFAULT_URI,"");
+
+    /**
+     * jcr:system
+     */
+    public static final Name JCR_SYSTEM = FACTORY.create(Name.NS_JCR_URI, "system");
+
+    /**
+     * jcr:nodeTypes
+     */
+    public static final Name JCR_NODETYPES = FACTORY.create(Name.NS_JCR_URI, "nodeTypes");
+
+    /**
+     * jcr:uuid
+     */
+    public static final Name JCR_UUID = FACTORY.create(Name.NS_JCR_URI, "uuid");
+
+    /**
+     * jcr:primaryType
+     */
+    public static final Name JCR_PRIMARYTYPE = FACTORY.create(Name.NS_JCR_URI, "primaryType");
+
+    /**
+     * jcr:mixinTypes
+     */
+    public static final Name JCR_MIXINTYPES = FACTORY.create(Name.NS_JCR_URI, "mixinTypes");
+
+    /**
+     * jcr:created
+     */
+    public static final Name JCR_CREATED = FACTORY.create(Name.NS_JCR_URI, "created");
+
+    /**
+     * jcr:lastModified
+     */
+    public static final Name JCR_LASTMODIFIED = FACTORY.create(Name.NS_JCR_URI, "lastModified");
+
+    /**
+     * jcr:encoding
+     */
+    public static final Name JCR_ENCODING = FACTORY.create(Name.NS_JCR_URI, "encoding");
+
+    /**
+     * jcr:mimeType
+     */
+    public static final Name JCR_MIMETYPE = FACTORY.create(Name.NS_JCR_URI, "mimeType");
+
+    /**
+     * jcr:data
+     */
+    public static final Name JCR_DATA = FACTORY.create(Name.NS_JCR_URI, "data");
+
+    /**
+     * jcr:content
+     */
+    public static final Name JCR_CONTENT = FACTORY.create(Name.NS_JCR_URI, "content");
+
+    //--------------------------------------< xml related item name constants >
+
+    /**
+     * jcr:root (dummy name for root node used in XML serialization)
+     */
+    public static final Name JCR_ROOT = FACTORY.create(Name.NS_JCR_URI, "root");
+
+    /**
+     * jcr:xmltext
+     */
+    public static final Name JCR_XMLTEXT = FACTORY.create(Name.NS_JCR_URI, "xmltext");
+
+    /**
+     * jcr:xmlcharacters
+     */
+    public static final Name JCR_XMLCHARACTERS = FACTORY.create(Name.NS_JCR_URI, "xmlcharacters");
+
+    //-----------------------------------------< query related name constants >
+
+    /**
+     * jcr:score
+     */
+    public static final Name JCR_SCORE = FACTORY.create(Name.NS_JCR_URI, "score");
+
+    /**
+     * jcr:path
+     */
+    public static final Name JCR_PATH = FACTORY.create(Name.NS_JCR_URI, "path");
+
+    /**
+     * jcr:statement
+     */
+    public static final Name JCR_STATEMENT = FACTORY.create(Name.NS_JCR_URI, "statement");
+
+    /**
+     * jcr:language
+     */
+    public static final Name JCR_LANGUAGE = FACTORY.create(Name.NS_JCR_URI, "language");
+
+    //----------------------------------< locking related item name constants >
+
+    /**
+     * jcr:lockOwner
+     */
+    public static final Name JCR_LOCKOWNER = FACTORY.create(Name.NS_JCR_URI, "lockOwner");
+
+    /**
+     * jcr:lockIsDeep
+     */
+    public static final Name JCR_LOCKISDEEP = FACTORY.create(Name.NS_JCR_URI, "lockIsDeep");
+
+    //-------------------------------< versioning related item name constants >
+
+    /**
+     * jcr:versionStorage
+     */
+    public static final Name JCR_VERSIONSTORAGE = FACTORY.create(Name.NS_JCR_URI, "versionStorage");
+
+    /**
+     * jcr:mergeFailed
+     */
+    public static final Name JCR_MERGEFAILED = FACTORY.create(Name.NS_JCR_URI, "mergeFailed");
+
+    /**
+     * jcr:frozenNode
+     */
+    public static final Name JCR_FROZENNODE = FACTORY.create(Name.NS_JCR_URI, "frozenNode");
+
+    /**
+     * jcr:frozenUuid
+     */
+    public static final Name JCR_FROZENUUID = FACTORY.create(Name.NS_JCR_URI, "frozenUuid");
+
+    /**
+     * jcr:frozenPrimaryType
+     */
+    public static final Name JCR_FROZENPRIMARYTYPE = FACTORY.create(Name.NS_JCR_URI, "frozenPrimaryType");
+
+    /**
+     * jcr:frozenMixinTypes
+     */
+    public static final Name JCR_FROZENMIXINTYPES = FACTORY.create(Name.NS_JCR_URI, "frozenMixinTypes");
+
+    /**
+     * jcr:predecessors
+     */
+    public static final Name JCR_PREDECESSORS = FACTORY.create(Name.NS_JCR_URI, "predecessors");
+
+    /**
+     * jcr:versionLabels
+     */
+    public static final Name JCR_VERSIONLABELS = FACTORY.create(Name.NS_JCR_URI, "versionLabels");
+
+    /**
+     * jcr:successors
+     */
+    public static final Name JCR_SUCCESSORS = FACTORY.create(Name.NS_JCR_URI, "successors");
+
+    /**
+     * jcr:isCheckedOut
+     */
+    public static final Name JCR_ISCHECKEDOUT = FACTORY.create(Name.NS_JCR_URI, "isCheckedOut");
+
+    /**
+     * jcr:versionHistory
+     */
+    public static final Name JCR_VERSIONHISTORY = FACTORY.create(Name.NS_JCR_URI, "versionHistory");
+
+    /**
+     * jcr:baseVersion
+     */
+    public static final Name JCR_BASEVERSION = FACTORY.create(Name.NS_JCR_URI, "baseVersion");
+
+    /**
+     * jcr:childVersionHistory
+     */
+    public static final Name JCR_CHILDVERSIONHISTORY = FACTORY.create(Name.NS_JCR_URI, "childVersionHistory");
+
+    /**
+     * jcr:rootVersion
+     */
+    public static final Name JCR_ROOTVERSION = FACTORY.create(Name.NS_JCR_URI, "rootVersion");
+
+    /**
+     * jcr:versionableUuid
+     */
+    public static final Name JCR_VERSIONABLEUUID = FACTORY.create(Name.NS_JCR_URI, "versionableUuid");
+
+    //--------------------------------< node type related item name constants >
+
+    /**
+     * jcr:nodeTypeName
+     */
+    public static final Name JCR_NODETYPENAME = FACTORY.create(Name.NS_JCR_URI, "nodeTypeName");
+
+    /**
+     * jcr:hasOrderableChildNodes
+     */
+    public static final Name JCR_HASORDERABLECHILDNODES = FACTORY.create(Name.NS_JCR_URI, "hasOrderableChildNodes");
+
+    /**
+     * jcr:isMixin
+     */
+    public static final Name JCR_ISMIXIN = FACTORY.create(Name.NS_JCR_URI, "isMixin");
+
+    /**
+     * jcr:supertypes
+     */
+    public static final Name JCR_SUPERTYPES = FACTORY.create(Name.NS_JCR_URI, "supertypes");
+
+    /**
+     * jcr:propertyDefinition
+     */
+    public static final Name JCR_PROPERTYDEFINITION = FACTORY.create(Name.NS_JCR_URI, "propertyDefinition");
+
+    /**
+     * jcr:name
+     */
+    public static final Name JCR_NAME = FACTORY.create(Name.NS_JCR_URI, "name");
+
+    /**
+     * jcr:mandatory
+     */
+    public static final Name JCR_MANDATORY = FACTORY.create(Name.NS_JCR_URI, "mandatory");
+
+    /**
+     * jcr:protected
+     */
+    public static final Name JCR_PROTECTED = FACTORY.create(Name.NS_JCR_URI, "protected");
+
+    /**
+     * jcr:requiredType
+     */
+    public static final Name JCR_REQUIREDTYPE = FACTORY.create(Name.NS_JCR_URI, "requiredType");
+
+    /**
+     * jcr:onParentVersion
+     */
+    public static final Name JCR_ONPARENTVERSION = FACTORY.create(Name.NS_JCR_URI, "onParentVersion");
+
+    /**
+     * jcr:primaryItemName
+     */
+    public static final Name JCR_PRIMARYITEMNAME = FACTORY.create(Name.NS_JCR_URI, "primaryItemName");
+
+    /**
+     * jcr:multiple
+     */
+    public static final Name JCR_MULTIPLE = FACTORY.create(Name.NS_JCR_URI, "multiple");
+
+    /**
+     * jcr:valueConstraints
+     */
+    public static final Name JCR_VALUECONSTRAINTS = FACTORY.create(Name.NS_JCR_URI, "valueConstraints");
+
+    /**
+     * jcr:defaultValues
+     */
+    public static final Name JCR_DEFAULTVALUES = FACTORY.create(Name.NS_JCR_URI, "defaultValues");
+
+    /**
+     * jcr:autoCreated
+     */
+    public static final Name JCR_AUTOCREATED = FACTORY.create(Name.NS_JCR_URI, "autoCreated");
+
+    /**
+     * jcr:childNodeDefinition
+     */
+    public static final Name JCR_CHILDNODEDEFINITION = FACTORY.create(Name.NS_JCR_URI, "childNodeDefinition");
+
+    /**
+     * jcr:sameNameSiblings
+     */
+    public static final Name JCR_SAMENAMESIBLINGS = FACTORY.create(Name.NS_JCR_URI, "sameNameSiblings");
+
+    /**
+     * jcr:defaultPrimaryType
+     */
+    public static final Name JCR_DEFAULTPRIMARYTYPE = FACTORY.create(Name.NS_JCR_URI, "defaultPrimaryType");
+
+    /**
+     * jcr:requiredPrimaryTypes
+     */
+    public static final Name JCR_REQUIREDPRIMARYTYPES = FACTORY.create(Name.NS_JCR_URI, "requiredPrimaryTypes");
+
+    //-------------------------------------------< node type name constants >---
+    /**
+     * nt:unstructured
+     */
+    public static final Name NT_UNSTRUCTURED = FACTORY.create(Name.NS_NT_URI, "unstructured");
+
+    /**
+     * nt:base
+     */
+    public static final Name NT_BASE = FACTORY.create(Name.NS_NT_URI, "base");
+
+    /**
+     * nt:hierarchyNode
+     */
+    public static final Name NT_HIERARCHYNODE = FACTORY.create(Name.NS_NT_URI, "hierarchyNode");
+
+    /**
+     * nt:resource
+     */
+    public static final Name NT_RESOURCE = FACTORY.create(Name.NS_NT_URI, "resource");
+
+    /**
+     * nt:file
+     */
+    public static final Name NT_FILE = FACTORY.create(Name.NS_NT_URI, "file");
+
+    /**
+     * nt:folder
+     */
+    public static final Name NT_FOLDER = FACTORY.create(Name.NS_NT_URI, "folder");
+
+    /**
+     * nt:query
+     */
+    public static final Name NT_QUERY = FACTORY.create(Name.NS_NT_URI, "query");
+
+    /**
+     * mix:referenceable
+     */
+    public static final Name MIX_REFERENCEABLE = FACTORY.create(Name.NS_MIX_URI, "referenceable");
+    /**
+     * mix:referenceable
+     */
+    public static final Name MIX_LOCKABLE = FACTORY.create(Name.NS_MIX_URI, "lockable");
+    /**
+     * mix:versionable
+     */
+    public static final Name MIX_VERSIONABLE = FACTORY.create(Name.NS_MIX_URI, "versionable");
+    /**
+     * nt:versionHistory
+     */
+    public static final Name NT_VERSIONHISTORY = FACTORY.create(Name.NS_NT_URI, "versionHistory");
+    /**
+     * nt:version
+     */
+    public static final Name NT_VERSION = FACTORY.create(Name.NS_NT_URI, "version");
+    /**
+     * nt:versionLabels
+     */
+    public static final Name NT_VERSIONLABELS = FACTORY.create(Name.NS_NT_URI, "versionLabels");
+    /**
+     * nt:versionedChild
+     */
+    public static final Name NT_VERSIONEDCHILD = FACTORY.create(Name.NS_NT_URI, "versionedChild");
+    /**
+     * nt:frozenNode
+     */
+    public static final Name NT_FROZENNODE = FACTORY.create(Name.NS_NT_URI, "frozenNode");
+    /**
+     * nt:nodeType
+     */
+    public static final Name NT_NODETYPE = FACTORY.create(Name.NS_NT_URI, "nodeType");
+    /**
+     * nt:propertyDefinition
+     */
+    public static final Name NT_PROPERTYDEFINITION = FACTORY.create(Name.NS_NT_URI, "propertyDefinition");
+    /**
+     * nt:childNodeDefinition
+     */
+    public static final Name NT_CHILDNODEDEFINITION = FACTORY.create(Name.NS_NT_URI, "childNodeDefinition");
+
+
+    //--------------------------------------------------------------------------
+    /**
+     * rep:root
+     */
+    public static final Name REP_ROOT = FACTORY.create(Name.NS_REP_URI, "root");
+
+    /**
+     * rep:system
+     */
+    public static final Name REP_SYSTEM = FACTORY.create(Name.NS_REP_URI, "system");
+
+    /**
+     * rep:versionStorage
+     */
+    public static final Name REP_VERSIONSTORAGE = FACTORY.create(Name.NS_REP_URI, "versionStorage");
+
+    /**
+     * rep:versionStorage
+     */
+    public static final Name REP_NODETYPES = FACTORY.create(Name.NS_REP_URI, "nodeTypes");
+
+    /**
+     * The special wildcard name used as the name of residual item definitions.
+     */
+    public static final Name ANY_NAME = FACTORY.create("", "*");
+
+    //------------------------------------------< system view name constants >
+    /**
+     * sv:node
+     */
+    public static final Name SV_NODE = FACTORY.create(Name.NS_SV_URI, "node");
+    /**
+     * sv:property
+     */
+    public static final Name SV_PROPERTY = FACTORY.create(Name.NS_SV_URI, "property");
+    /**
+     * sv:value
+     */
+    public static final Name SV_VALUE = FACTORY.create(Name.NS_SV_URI, "value");
+    /**
+     * sv:type
+     */
+    public static final Name SV_TYPE = FACTORY.create(Name.NS_SV_URI, "type");
+    /**
+     * sv:name
+     */
+    public static final Name SV_NAME = FACTORY.create(Name.NS_SV_URI, "name");
+
+
+}
\ No newline at end of file

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

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

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/NameFactoryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/NameFactoryImpl.java?rev=586058&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/NameFactoryImpl.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/NameFactoryImpl.java Thu Oct 18 11:34:57 2007
@@ -0,0 +1,227 @@
+/*
+ * 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.spi.NameFactory;
+import org.apache.jackrabbit.spi.Name;
+
+/**
+ * <code>NameFactoryImpl</code>...
+ */
+public class NameFactoryImpl implements NameFactory {
+
+    private static NameFactory FACTORY;
+
+    private NameFactoryImpl() {};
+
+    public static NameFactory getInstance() {
+        if (FACTORY == null) {
+            FACTORY = new NameFactoryImpl();
+        }
+        return FACTORY;
+    }
+
+    //--------------------------------------------------------< NameFactory >---
+    /**
+     * @see NameFactory#create(String, String)
+     */
+    public Name create(String namespaceURI, String localName) throws IllegalArgumentException {
+        // NOTE: an empty localName and/or URI is valid (e.g. the root node name)
+        if (namespaceURI == null) {
+            throw new IllegalArgumentException("invalid namespaceURI specified");
+        }
+        if (localName == null) {
+            throw new IllegalArgumentException("invalid localName specified");
+        }
+        return new NameImpl(namespaceURI, localName);
+    }
+
+    /**
+     * @see NameFactory#create(String)
+     */
+    public Name create(String nameString) throws IllegalArgumentException {
+        if (nameString == null || "".equals(nameString)) {
+            throw new IllegalArgumentException("Invalid Name literal.");
+        }
+        if (nameString.charAt(0) != '{') {
+            throw new IllegalArgumentException("Invalid Name literal");
+        }
+        int i = nameString.indexOf('}');
+        if (i == -1) {
+            throw new IllegalArgumentException("Invalid Name literal");
+        }
+        if (i == nameString.length() - 1) {
+            throw new IllegalArgumentException("Invalid Name literal");
+        } else {
+            return new NameImpl(nameString.substring(1, i), nameString.substring(i + 1));
+        }
+    }
+
+    //--------------------------------------------------------< inner class >---
+    /**
+     * Inner class implementing the <code>Name</code> interface.
+     */
+    private static class NameImpl implements Name {
+
+        /** The memorized hash code of this qualified name. */
+        private transient int hash;
+
+        /** The memorized string representation of this qualified name. */
+        private transient String string;
+
+        /** The internalized namespace URI of this qualified name. */
+        private final String namespaceURI;
+
+        /** The local part of this qualified name. */
+        private final String localName;
+
+        private NameImpl(String namespaceURI, String localName) {
+            // internalize namespaceURI to improve performance of comparisons.            
+            this.namespaceURI = namespaceURI.intern();
+            // localName is not internalized in order not to risk huge perm
+            // space for large repositories
+            this.localName = localName;
+            hash = 0;
+        }
+
+        //-----------------------------------------------------------< Name >---
+        /**
+         * @see Name#getLocalName()
+         */
+        public String getLocalName() {
+            return localName;
+        }
+
+        /**
+         * @see Name#getNamespaceURI()
+         */
+        public String getNamespaceURI() {
+            return namespaceURI;
+        }
+
+        //---------------------------------------------------------< Object >---
+        /**
+         * Returns the string representation of this <code>Name</code> in the
+         * following format:
+         * <p/>
+         * <code><b>{</b>namespaceURI<b>}</b>localName</code>
+         *
+         * @return the string representation of this <code>Name</code>.
+         * @see NameFactory#create(String)
+         * @see Object#toString()
+         */
+        public String toString() {
+            // Name is immutable, we can store the string representation
+            if (string == null) {
+                string = '{' + namespaceURI + '}' + localName;
+            }
+            return string;
+        }
+
+        /**
+         * Compares two qualified names for equality. Returns <code>true</code>
+         * if the given object is a qualified name and has the same namespace URI
+         * and local part as this qualified name.
+         *
+         * @param obj the object to compare this qualified name with
+         * @return <code>true</code> if the object is equal to this qualified name,
+         *         <code>false</code> otherwise
+         * @see Object#equals(Object)
+         */
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof NameImpl) {
+                NameImpl other = (NameImpl) obj;
+                // we can use == operator for namespaceURI since it is internalized
+                return namespaceURI == other.namespaceURI && localName.equals(other.localName);
+            }
+            // some other Name implementation
+            if (obj instanceof Name) {
+                Name other = (Name) obj;
+                return namespaceURI.equals(other.getNamespaceURI()) && localName.equals(other.getLocalName());
+            }
+            return false;
+        }
+
+        /**
+         * Returns the hash code of this qualified name. The hash code is
+         * computed from the namespace URI and local part of the qualified
+         * name and memorized for better performance.
+         *
+         * @return hash code
+         * @see Object#hashCode()
+         */
+        public int hashCode() {
+            // Name is immutable, we can store the computed hash code value
+            int h = hash;
+            if (h == 0) {
+                h = 17;
+                h = 37 * h + namespaceURI.hashCode();
+                h = 37 * h + localName.hashCode();
+                hash = h;
+            }
+            return h;
+        }
+
+        //------------------------------------------------------< Cloneable >---
+        /**
+         * Creates a clone of this qualified name.
+         * Overriden in order to make <code>clone()</code> public.
+         *
+         * @return a clone of this instance
+         * @throws CloneNotSupportedException never thrown
+         * @see Object#clone()
+         */
+        public Object clone() throws CloneNotSupportedException {
+            // Name is immutable, no special handling required
+            return super.clone();
+        }
+
+        //-----------------------------------------------------< Comparable >---
+        /**
+         * Compares two qualified names.
+         *
+         * @param o the object to compare this qualified name with
+         * @return comparison result
+         * @throws ClassCastException if the given object is not a qualified name
+         * @see Comparable#compareTo(Object)
+         */
+        public int compareTo(Object o) {
+            if (this == o) {
+                return 0;
+            }
+            Name other = (Name) o;
+            if (namespaceURI.equals(other.getNamespaceURI())) {
+                return localName.compareTo(other.getLocalName());
+            } else {
+                return namespaceURI.compareTo(other.getNamespaceURI());
+            }
+        }
+
+        //---------------------------------------------------< Serializable >---
+        /**
+         * Creates a new <code>Name</code> instance using the proper constructor
+         * during deserialization in order to make sure that internalized strings
+         * are used where appropriate.
+         */
+        private Object readResolve() {
+            return new NameImpl(namespaceURI, localName);
+        }
+    }
+}
\ No newline at end of file

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

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

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/PathBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/PathBuilder.java?rev=586058&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/PathBuilder.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/PathBuilder.java Thu Oct 18 11:34:57 2007
@@ -0,0 +1,196 @@
+/*
+ * 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.spi.Path;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.conversion.MalformedPathException;
+
+import java.util.LinkedList;
+
+/**
+ * Helper class used to build a path from pre-parsed path elements.
+ * <p/>
+ * Note that this class does neither validate the format of the path elements nor
+ * does it validate the format of the entire path.
+ * This class should therefore only be used in situations, where the elements
+ * and the resulting path are known to be valid. The regular way of creating
+ * a <code>Path</code> object is by calling any of the
+ * <code>PathFactory.create()</code>methods.
+ */
+public final class PathBuilder {
+
+    /**
+     * The path factory
+     */
+    private final PathFactory factory;
+
+    /**
+     * 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 to create a Path using the
+     * {@link PathFactoryImpl default PathFactory}. See
+     * {@link PathBuilder#PathBuilder(PathFactory)} for a constructor explicitely
+     * specifying the factory to use.
+     */
+    public PathBuilder() {
+        this(PathFactoryImpl.getInstance());
+    }
+
+    /**
+     * Creates a new PathBuilder.
+     *
+     * @param factory The PathFactory used to create the elements and the final path.
+     */
+    public PathBuilder(PathFactory factory) {
+        this.factory = (factory != null) ? factory : PathFactoryImpl.getInstance();
+        queue = new LinkedList();
+    }
+
+    /**
+     * Creates a new PathBuilder and initialized it with the given path
+     * elements.
+     *
+     * @param elements
+     */
+    public PathBuilder(Path.Element[] elements) {
+        this();
+        addAll(elements);
+    }
+
+    /**
+     * 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 org.apache.jackrabbit.spi.PathFactory#getRootElement()}.
+     */
+    public void addRoot() {
+        addFirst(factory.getRootElement());
+    }
+
+    /**
+     * Adds the given elemenets
+     *
+     * @param elements
+     */
+    public void addAll(Path.Element[] elements) {
+        for (int i = 0; i < elements.length; i++) {
+            addLast(elements[i]);
+        }
+    }
+
+    /**
+     * Inserts the element at the beginning of the path to be built.
+     *
+     * @param elem
+     */
+    public void addFirst(Path.Element elem) {
+        if (queue.isEmpty()) {
+            isNormalized &= !elem.denotesCurrent();
+            leadingParent = elem.denotesParent();
+        } else {
+            isNormalized &= !elem.denotesCurrent() && (!leadingParent || elem.denotesParent());
+            leadingParent |= elem.denotesParent();
+        }
+        queue.addFirst(elem);
+    }
+
+    /**
+     * Inserts the element at the beginning of the path to be built.
+     *
+     * @param name
+     */
+    public void addFirst(Name name) {
+        addFirst(factory.createElement(name));
+    }
+
+    /**
+     * Inserts the element at the beginning of the path to be built.
+     *
+     * @param name
+     * @param index
+     */
+    public void addFirst(Name name, int index) {
+        addFirst(factory.createElement(name, index));
+    }
+
+    /**
+     * Inserts the element at the end of the path to be built.
+     *
+     * @param elem
+     */
+    public void addLast(Path.Element elem) {
+        queue.addLast(elem);
+        leadingParent &= elem.denotesParent();
+        isNormalized &= !elem.denotesCurrent() && (leadingParent || !elem.denotesParent());
+    }
+
+    /**
+     * Inserts the element at the end of the path to be built.
+     *
+     * @param name
+     */
+    public void addLast(Name name) {
+        addLast(factory.createElement(name));
+    }
+
+    /**
+     * Inserts the element at the end of the path to be built.
+     *
+     * @param name
+     * @param index
+     */
+    public void addLast(Name name, int index) {
+        addLast(factory.createElement(name, index));
+    }
+
+    /**
+     * Assembles the built path and returns a new {@link Path}.
+     *
+     * @return a new {@link Path}
+     * @throws MalformedPathException if the internal path element queue is empty.
+     */
+    public Path getPath() throws MalformedPathException {
+        if (queue.size() == 0) {
+            throw new MalformedPathException("empty path");
+        }
+        Path.Element[] elements = (Path.Element[]) queue.toArray(new Path.Element[queue.size()]);
+        return factory.create(elements);
+    }
+}
\ No newline at end of file

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

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

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/PathFactoryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/PathFactoryImpl.java?rev=586058&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/PathFactoryImpl.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/name/PathFactoryImpl.java Thu Oct 18 11:34:57 2007
@@ -0,0 +1,895 @@
+/*
+ * 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.spi.Path;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.spi.NameFactory;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.PathNotFoundException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * <code>PathFactoryImpl</code>...
+ */
+public class PathFactoryImpl implements PathFactory {
+
+    private static PathFactory FACTORY = new PathFactoryImpl();
+
+    private static final String CURRENT_LITERAL = ".";
+    private static final String PARENT_LITERAL = "..";
+
+    private static final NameFactory NAME_FACTORY = NameFactoryImpl.getInstance();
+    private final static Name CURRENT_NAME = NAME_FACTORY.create(Name.NS_DEFAULT_URI, CURRENT_LITERAL);
+    private final static Name PARENT_NAME = NAME_FACTORY.create(Name.NS_DEFAULT_URI, PARENT_LITERAL);
+    private final static Name ROOT_NAME = NAME_FACTORY.create(Name.NS_DEFAULT_URI, "");
+
+    private static final Path.Element CURRENT_ELEMENT = new SpecialElement(CURRENT_NAME);
+    private static final Path.Element PARENT_ELEMENT = new SpecialElement(PARENT_NAME);
+    private static final Path.Element ROOT_ELEMENT = new SpecialElement(ROOT_NAME);
+
+    /**
+     * the root path
+     */
+    private static final Path ROOT = new PathImpl(new Path.Element[]{ROOT_ELEMENT}, true);
+
+    private PathFactoryImpl() {}
+
+    public static PathFactory getInstance() {
+        return FACTORY;
+    }
+
+    //--------------------------------------------------------< PathFactory >---
+    /**
+     * @see PathFactory#create(Path, Path, boolean)
+     */
+    public Path create(Path parent, Path relPath, boolean normalize) throws IllegalArgumentException, RepositoryException {
+        if (relPath.isAbsolute()) {
+            throw new IllegalArgumentException("relPath is not a relative path");
+        }
+        List l = new ArrayList();
+        l.addAll(Arrays.asList(parent.getElements()));
+        l.addAll(Arrays.asList(relPath.getElements()));
+
+        Builder pb = new Builder(l);
+        Path path = pb.getPath();
+        if (normalize) {
+            return path.getNormalizedPath();
+        } else {
+            return path;
+        }
+    }
+
+    /**
+     * @see PathFactory#create(Path, Name, boolean)
+     */
+    public Path create(Path parent, Name name, boolean normalize) throws RepositoryException {
+        List elements = new ArrayList();
+        elements.addAll(Arrays.asList(parent.getElements()));
+        elements.add(createElement(name));
+
+        Builder pb = new Builder(elements);
+        Path path = pb.getPath();
+        if (normalize) {
+            return path.getNormalizedPath();
+        } else {
+            return path;
+        }
+    }
+
+    /**
+     * @see PathFactory#create(Path, Name, int, boolean)
+     */
+    public Path create(Path parent, Name name, int index, boolean normalize) throws IllegalArgumentException, RepositoryException {
+        List elements = new ArrayList();
+        elements.addAll(Arrays.asList(parent.getElements()));
+        elements.add(createElement(name, index));
+
+        Builder pb = new Builder(elements);
+        Path path = pb.getPath();
+        if (normalize) {
+            return path.getNormalizedPath();
+        } else {
+            return path;
+        }
+    }
+
+    /**
+     * @see PathFactory#create(Name)
+     */
+    public Path create(Name name) throws IllegalArgumentException {
+        Path.Element elem = createElement(name);
+        return new Builder(new Path.Element[]{elem}).getPath();
+    }
+
+    /**
+     * @see PathFactory#create(Name, int)
+     */
+    public Path create(Name name, int index) throws IllegalArgumentException {
+        if (index < Path.INDEX_UNDEFINED) {
+            throw new IllegalArgumentException("Index must not be negative: " + index);
+        }
+        Path.Element elem = createElement(name, index);
+        return new Builder(new Path.Element[]{elem}).getPath();
+    }
+
+    /**
+     * @see PathFactory#create(Path.Element[])
+     */
+    public Path create(Path.Element[] elements) throws IllegalArgumentException {
+        return new Builder(elements).getPath();
+    }
+
+    /**
+     * @see PathFactory#create(String)
+     */
+    public Path create(String pathString) throws IllegalArgumentException {
+        if (pathString == null || "".equals(pathString)) {
+            throw new IllegalArgumentException("Invalid Path literal");
+        }
+        // split into path elements
+        int lastPos = 0;
+        int pos = pathString.indexOf(Path.DELIMITER);
+        ArrayList list = new ArrayList();
+        while (lastPos >= 0) {
+            Path.Element elem;
+            if (pos >= 0) {
+                elem = createElement(pathString.substring(lastPos, pos));
+                lastPos = pos + 1;
+                pos = pathString.indexOf(Path.DELIMITER, lastPos);
+            } else {
+                elem = createElement(pathString.substring(lastPos));
+                lastPos = -1;
+            }
+            list.add(elem);
+        }
+        return new Builder(list).getPath();
+    }
+
+    /**
+     * @see PathFactory#createElement(Name)
+     */
+    public Path.Element createElement(Name name) throws IllegalArgumentException {
+        if (name == null) {
+            throw new IllegalArgumentException("name must not be null");
+        } else if (name.equals(PARENT_NAME)) {
+            return PARENT_ELEMENT;
+        } else if (name.equals(CURRENT_NAME)) {
+            return CURRENT_ELEMENT;
+        } else if (name.equals(ROOT_NAME)) {
+            return ROOT_ELEMENT;
+        } else {
+            return new Element(name, Path.INDEX_UNDEFINED);
+        }
+    }
+
+    /**
+     * @see PathFactory#createElement(Name, int)
+     */
+    public Path.Element createElement(Name name, int index) throws IllegalArgumentException {
+        if (index < Path.INDEX_UNDEFINED) {
+            throw new IllegalArgumentException("The index may not be negative.");
+        } else if (name == null) {
+            throw new IllegalArgumentException("The name must not be null");
+        } else if (name.equals(PARENT_NAME)
+                || name.equals(CURRENT_NAME)
+                || name.equals(ROOT_NAME)) {
+            throw new IllegalArgumentException(
+                    "Special path elements (root, '.' and '..') can not have an explicit index.");
+        } else {
+            return new Element(name, index);
+        }
+    }
+
+    /**
+     * Create an element from the element string
+     */
+    private Path.Element createElement(String elementString) {
+        if (elementString == null) {
+            throw new IllegalArgumentException("null PathElement literal");
+        }
+        if (elementString.equals(ROOT_NAME.toString())) {
+            return ROOT_ELEMENT;
+        } else if (elementString.equals(CURRENT_LITERAL)) {
+            return CURRENT_ELEMENT;
+        } else if (elementString.equals(PARENT_LITERAL)) {
+            return PARENT_ELEMENT;
+        }
+
+        int pos = elementString.indexOf('[');
+        if (pos == -1) {
+            Name name = NAME_FACTORY.create(elementString);
+            return new Element(name, Path.INDEX_UNDEFINED);
+        }
+        Name name = NAME_FACTORY.create(elementString.substring(0, pos));
+        int pos1 = elementString.indexOf(']');
+        if (pos1 == -1) {
+            throw new IllegalArgumentException("invalid PathElement literal: " + elementString + " (missing ']')");
+        }
+        try {
+            int index = Integer.valueOf(elementString.substring(pos + 1, pos1)).intValue();
+            if (index < 1) {
+                throw new IllegalArgumentException("invalid PathElement literal: " + elementString + " (index is 1-based)");
+            }
+            return new Element(name, index);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("invalid PathElement literal: " + elementString + " (" + e.getMessage() + ")");
+        }
+    }
+
+    /**
+     * @see PathFactory#getCurrentElement()
+     */
+    public Path.Element getCurrentElement() {
+        return CURRENT_ELEMENT;
+    }
+
+    /**
+     * @see PathFactory#getParentElement()
+     */
+    public Path.Element getParentElement() {
+        return PARENT_ELEMENT;
+    }
+
+    /**
+     * @see PathFactory#getRootElement()
+     */
+    public Path.Element getRootElement() {
+        return ROOT_ELEMENT;
+    }
+
+    /**
+     * @see PathFactory#getRootPath()
+     */
+    public Path getRootPath() {
+        return ROOT;
+    }
+
+    //--------------------------------------------------------------------------
+    private static final class PathImpl implements Path {
+
+        /**
+         * the elements of this path
+         */
+        private final Path.Element[] elements;
+
+        /**
+         * flag indicating if this path is normalized
+         */
+        private final boolean normalized;
+
+        /**
+         * flag indicating if this path is absolute
+         */
+        private final boolean absolute;
+
+        /**
+         * the cached hashcode of this path
+         */
+        private transient int hash = 0;
+
+        /**
+         * the cached 'toString' of this path
+         */
+        private transient String string;
+
+        private PathImpl(Path.Element[] elements, boolean isNormalized) {
+            if (elements == null || elements.length == 0) {
+                throw new IllegalArgumentException("Empty paths are not allowed");
+            }
+            this.elements = elements;
+            this.absolute = elements[0].denotesRoot();
+            this.normalized = isNormalized;
+        }
+
+        /**
+         * @see Path#denotesRoot()
+         */
+        public boolean denotesRoot() {
+            return absolute && elements.length == 1;
+        }
+
+        /**
+         * @see Path#isAbsolute()
+         */
+        public boolean isAbsolute() {
+            return absolute;
+        }
+
+        /**
+         * @see Path#isCanonical()
+         */
+        public boolean isCanonical() {
+            return absolute && normalized;
+        }
+
+        /**
+         * @see Path#isNormalized()
+         */
+        public boolean isNormalized() {
+            return normalized;
+        }
+
+        /**
+         * @see Path#getNormalizedPath()
+         */
+        public Path getNormalizedPath() throws RepositoryException {
+            if (isNormalized()) {
+                return this;
+            }
+            LinkedList queue = new LinkedList();
+            Path.Element last = PARENT_ELEMENT;
+            for (int i = 0; i < elements.length; i++) {
+                Path.Element elem = elements[i];
+                if (elem.denotesParent() && !last.denotesParent()) {
+                    if (last.denotesRoot()) {
+                        // the first element is the root element;
+                        // ".." would refer to the parent of root
+                        throw new RepositoryException("Path can not be canonicalized: unresolvable '..' element");
+                    }
+                    queue.removeLast();
+                    if (queue.isEmpty()) {
+                        last = PARENT_ELEMENT;
+                    } else {
+                        last = (Path.Element) queue.getLast();
+                    }
+                } else if (!elem.denotesCurrent()) {
+                    last = elem;
+                    queue.add(last);
+                }
+            }
+            if (queue.isEmpty()) {
+                throw new RepositoryException("Path can not be normalized: would result in an empty path.");
+            }
+            boolean isNormalized = true;
+            return new PathImpl((Path.Element[]) queue.toArray(new Element[queue.size()]), isNormalized);
+        }
+
+        /**
+         * @see Path#getCanonicalPath()
+         */
+        public Path getCanonicalPath() throws RepositoryException {
+            if (isCanonical()) {
+                return this;
+            }
+            if (!isAbsolute()) {
+                throw new RepositoryException("Only an absolute path can be canonicalized.");
+            }
+            return getNormalizedPath();
+        }
+
+        /**
+         * @see Path#computeRelativePath(Path)
+         */
+        public Path computeRelativePath(Path other) throws RepositoryException {
+            if (other == null) {
+                throw new IllegalArgumentException("null argument");
+            }
+
+            // make sure both paths are absolute
+            if (!isAbsolute() || !other.isAbsolute()) {
+                throw new RepositoryException("Cannot compute relative path from relative paths");
+            }
+
+            // make sure we're comparing canonical paths
+            Path p0 = getCanonicalPath();
+            Path p1 = other.getCanonicalPath();
+
+            if (p0.equals(p1)) {
+                // both paths are equal, the relative path is therefore '.'
+                Builder pb = new Builder(new Path.Element[] {CURRENT_ELEMENT});
+                return pb.getPath();
+            }
+
+            // determine length of common path fragment
+            int lengthCommon = 0;
+            Path.Element[] elems0 = p0.getElements();
+            Path.Element[] elems1 = p1.getElements();
+            for (int i = 0; i < elems0.length && i < elems1.length; i++) {
+                if (!elems0[i].equals(elems1[i])) {
+                    break;
+                }
+                lengthCommon++;
+            }
+            List l = new ArrayList();
+            if (lengthCommon < elems0.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 = elems0.length - lengthCommon;
+                while (tmp-- > 0) {
+                    l.add(0, PARENT_ELEMENT);
+                }
+            }
+            // add remainder of other path
+            for (int i = lengthCommon; i < elems1.length; i++) {
+                l.add(elems1[i]);
+            }
+            return new Builder(l).getPath();
+        }
+
+        /**
+         * @see Path#getAncestor(int)
+         */
+        public Path getAncestor(int degree) throws IllegalArgumentException, PathNotFoundException {
+            if (degree < 0) {
+                throw new IllegalArgumentException("degree must be >= 0");
+            } else if (degree == 0) {
+                return this;
+            }
+            int length = elements.length - degree;
+            if (length < 1) {
+                throw new PathNotFoundException("no such ancestor path of degree " + degree);
+            }
+            Path.Element[] elements = new Element[length];
+            System.arraycopy(this.elements, 0, elements, 0, length);
+            return new PathImpl(elements, normalized);
+        }
+
+        /**
+         * @see Path#getAncestorCount()
+         */
+        public int getAncestorCount() {
+            return getDepth() - 1;
+        }
+
+        /**
+         * @see Path#getLength()
+         */
+        public int getLength() {
+            return elements.length;
+        }
+
+        /**
+         * @see Path#getDepth()
+         */
+        public int getDepth() {
+            int depth = ROOT_DEPTH;
+            for (int i = 0; i < elements.length; i++) {
+                if (elements[i].denotesParent()) {
+                    depth--;
+                } else if (!elements[i].denotesCurrent()) {
+                    depth++;
+                }
+            }
+            return depth;
+        }
+
+        /**
+         * @see Path#isAncestorOf(Path)
+         */
+        public boolean isAncestorOf(Path other) throws IllegalArgumentException, RepositoryException {
+            if (other == null) {
+                throw new IllegalArgumentException("null argument");
+            }
+            // make sure both paths are either absolute or relative
+            if (isAbsolute() != other.isAbsolute()) {
+                throw new IllegalArgumentException("Cannot compare a relative path with an absolute path");
+            }
+            // make sure we're comparing normalized paths
+            Path p0 = getNormalizedPath();
+            Path p1 = other.getNormalizedPath();
+
+            if (p0.equals(p1)) {
+                return false;
+            }
+            // calculate depth of paths (might be negative)
+            if (p0.getDepth() >= p1.getDepth()) {
+                return false;
+            }
+            Path.Element[] elems0 = p0.getElements();
+            Path.Element[] elems1 = p1.getElements();
+            for (int i = 0; i < elems0.length; i++) {
+                if (!elems0[i].equals(elems1[i])) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * @see Path#isDescendantOf(Path)
+         */
+        public boolean isDescendantOf(Path other) throws IllegalArgumentException, RepositoryException {
+            if (other == null) {
+                throw new IllegalArgumentException("Null argument");
+            }
+            return other.isAncestorOf(this);
+        }
+
+        /**
+         * @see Path#subPath(int, int)
+         */
+        public Path subPath(int from, int to) throws IllegalArgumentException, RepositoryException {
+            if (from < 0 || to >= elements.length || from >= to) {
+                throw new IllegalArgumentException();
+            }
+            if (!isNormalized()) {
+                throw new RepositoryException("Cannot extract sub-Path from a non-normalized Path.");
+            }
+            Path.Element[] dest = new Path.Element[to-from];
+            System.arraycopy(elements, from, dest, 0, dest.length);
+            Builder pb = new Builder(dest);
+            return pb.getPath();
+        }
+
+        /**
+         * @see Path#getNameElement()
+         */
+        public Element getNameElement() {
+            return elements[elements.length - 1];
+        }
+
+        /**
+         * @see Path#getString()
+         */
+        public String getString() {
+            return toString();
+        }
+
+        /**
+         * @see Path#getElements()
+         */
+        public Element[] getElements() {
+            return elements;
+        }
+
+        //---------------------------------------------------------< Object >---
+        /**
+         * Returns the internal string representation of this <code>Path</code>.
+         * <p/>
+         * Note that the returned string is not a valid JCR path, i.e. the
+         * namespace URI's of the individual path elements are not replaced with
+         * their mapped prefixes.
+         *
+         * @return the internal string representation of this <code>Path</code>.
+         */
+        public String toString() {
+            // Path is immutable, we can store the string representation
+            if (string == null) {
+                StringBuffer sb = new StringBuffer();
+                for (int i = 0; i < elements.length; i++) {
+                    if (i > 0) {
+                        sb.append(Path.DELIMITER);
+                    }
+                    Path.Element element = elements[i];
+                    String elem = element.toString();
+                    sb.append(elem);
+                }
+                string = sb.toString();
+            }
+            return string;
+        }
+
+        /**
+         * Returns a hash code value for this path.
+         *
+         * @return a hash code value for this path.
+         * @see Object#hashCode()
+         */
+        public int hashCode() {
+            // Path is immutable, we can store the computed hash code value
+            int h = hash;
+            if (h == 0) {
+                h = 17;
+                for (int i = 0; i < elements.length; i++) {
+                    h = 37 * h + elements[i].hashCode();
+                }
+                hash = h;
+            }
+            return h;
+        }
+
+        /**
+         * Compares the specified object with this path for equality.
+         *
+         * @param obj the object to be compared for equality with this path.
+         * @return <tt>true</tt> if the specified object is equal to this path.
+         */
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof PathImpl) {
+                Path other = (Path) obj;
+                return Arrays.equals(elements, other.getElements());
+            }
+            return false;
+        }
+    }
+
+    //-------------------------------------------------------< Path.Element >---
+    /**
+     * Object representation of a single JCR path element.
+     *
+     * @see Path.Element
+     */
+    private static class Element implements Path.Element {
+
+        /**
+         * Qualified name of the path element.
+         */
+        private final Name name;
+
+        /**
+         * Optional index of the path element. Set to zero, if not
+         * explicitly specified, otherwise contains the 1-based index.
+         */
+        private final int index;
+
+        /**
+         * Private constructor for creating a path element with the given
+         * qualified name and index. Instead of using this constructor directly
+         * the factory methods {@link PathFactory#createElement(Name)} and
+         * {@link PathFactory#create(Name, int)} should be used.
+         *
+         * @param name  qualified name
+         * @param index index
+         */
+        private Element(Name name, int index) {
+            this.index = index;
+            this.name = name;
+        }
+
+        /**
+         * @see Path.Element#getName()
+         */
+        public Name getName() {
+            return name;
+        }
+
+        /**
+         * @see Path.Element#getIndex()
+         */
+        public int getIndex() {
+            return index;
+        }
+
+        /**
+         * @see Path.Element#getNormalizedIndex()
+         */
+        public int getNormalizedIndex() {
+            if (index == Path.INDEX_UNDEFINED) {
+                return Path.INDEX_DEFAULT;
+            } else {
+                return index;
+            }
+        }
+
+        /**
+         * @return always returns false.
+         * @see Path.Element#denotesRoot()
+         */
+        public boolean denotesRoot() {
+            return false;
+        }
+
+        /**
+         * @return always returns false.
+         * @see Path.Element#denotesParent()
+         */
+        public boolean denotesParent() {
+            return false;
+        }
+
+        /**
+         * @return always returns false.
+         * @see Path.Element#denotesCurrent()
+         */
+        public boolean denotesCurrent() {
+            return false;
+        }
+
+        /**
+         * @return always returns true.
+         * @see Path.Element#denotesName()
+         */
+        public boolean denotesName() {
+            return true;
+        }
+
+        /**
+         * @see Path.Element#getString()
+         */
+        public String getString() {
+            return toString();
+        }
+
+        /**
+         * Returns a string representation of this path element. Note that
+         * the path element name is expressed using the <code>{uri}name</code>
+         * syntax.
+         *
+         * @return string representation of the path element
+         * @see Object#toString()
+         */
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            // name
+            sb.append(name.toString());
+            // index
+            if (index > Path.INDEX_DEFAULT) {
+                sb.append('[');
+                sb.append(index);
+                sb.append(']');
+            }
+            return sb.toString();
+        }
+
+        /**
+         * Computes a hash code for this path element.
+         *
+         * @return hash code
+         * @see Object#hashCode()
+         */
+        public int hashCode() {
+            int h = 17;
+            h = 37 * h + getNormalizedIndex();
+            h = 37 * h + name.hashCode();
+            return h;
+        }
+
+        /**
+         * Check for path element equality. Returns true if the given
+         * object is a PathElement and contains the same name and index
+         * as this one.
+         *
+         * @param obj the object to compare with
+         * @return <code>true</code> if the path elements are equal
+         * @see Object#equals(Object)
+         */
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof Path.Element) {
+                Path.Element other = (Path.Element) obj;
+                return name.equals(other.getName())
+                        && getNormalizedIndex() == other.getNormalizedIndex();
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Object representation of a special JCR path element notably the root, the
+     * current and the parent element.
+     */
+    private static final class SpecialElement extends Element {
+
+        private final static int ROOT = 1;
+        private final static int CURRENT = 2;
+        private final static int PARENT = 4;
+
+        private final int type;
+
+        private SpecialElement(Name name) {
+            super(name, Path.INDEX_UNDEFINED);
+            if (ROOT_NAME.equals(name)) {
+                type = ROOT;
+            } else if (CURRENT_NAME.equals(name)) {
+                type = CURRENT;
+            } else if (PARENT_NAME.equals(name)) {
+                type = PARENT;
+            } else {
+                throw new IllegalArgumentException();
+            }
+        }
+
+        /**
+         * @return true if this is the {@link #ROOT root element}.
+         * @see Path.Element#denotesRoot()
+         */
+        public boolean denotesRoot() {
+            return type == ROOT;
+        }
+
+        /**
+         * @return true if this is the {@link #PARENT parent element}.
+         * @see Path.Element#denotesParent()
+         */
+        public boolean denotesParent() {
+            return type == PARENT;
+        }
+
+        /**
+         * @return true if this is the {@link #CURRENT current element}.
+         * @see Path.Element#denotesCurrent()
+         */
+        public boolean denotesCurrent() {
+            return type == CURRENT;
+        }
+
+        /**
+         * @return Always returns false.
+         * @see Path.Element#denotesParent()
+         */
+        public boolean denotesName() {
+            return false;
+        }
+    }
+
+    /**
+     * Builder internal class
+     */
+    private static final class Builder {
+
+        /**
+         * the lpath elements of the constructed path
+         */
+        private final Path.Element[] elements;
+
+        /**
+         * flag indicating if the current path is normalized
+         */
+        private boolean isNormalized = true;
+
+        /**
+         * flag indicating if the current path has leading parent '..' elements
+         */
+        private boolean leadingParent = true;
+
+        /**
+         * Creates a new Builder and initialized it with the given path
+         * elements.
+         *
+         * @param elemList
+         * @throws IllegalArgumentException if the given elements array is null
+         * or has a length less than 1;
+         */
+        private Builder(List elemList) {
+            this((Path.Element[]) elemList.toArray(new Path.Element[elemList.size()]));
+        }
+
+        /**
+         * Creates a new Builder and initialized it with the given path
+         * elements.
+         *
+         * @param elements
+         * @throws IllegalArgumentException if the given elements array is null
+         * or has a length less than 1;
+         */
+        private Builder(Path.Element[] elements) {
+            if (elements == null || elements.length == 0) {
+                throw new IllegalArgumentException("Cannot build path from null or 0 elements.");
+            }
+            this.elements = elements;
+            for (int i = 0; i < elements.length; i++) {
+                Path.Element elem = elements[i];
+                leadingParent &= elem.denotesParent();
+                isNormalized &= !elem.denotesCurrent() && (leadingParent || !elem.denotesParent());
+            }
+        }
+
+        /**
+         * Assembles the built path and returns a new {@link Path}.
+         *
+         * @return a new {@link Path}
+         */
+        private Path getPath() {
+            // no need to check the path format, assuming all names correct
+            return new PathImpl(elements, isNormalized);
+        }
+    }
+}
\ No newline at end of file

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

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