Index: src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java (working copy)
@@ -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;
Index: src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java (working copy)
@@ -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 CachingNamespaceResolver.
*/
public void dispose() {
@@ -129,4 +150,13 @@
qnameToJCRName.clear();
jcrNameToQName.clear();
}
+
+ /**
+ * @inheritDoc
+ * Invalidates all cached mappings.
+ */
+ public void namespaceRemoved(String uri) {
+ qnameToJCRName.clear();
+ jcrNameToQName.clear();
+ }
}
Index: src/main/java/org/apache/jackrabbit/core/LocalNamespaceMappings.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/LocalNamespaceMappings.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/LocalNamespaceMappings.java (working copy)
@@ -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);
+ }
+ }
}
Index: src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java (working copy)
@@ -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 {
Index: src/main/java/org/apache/jackrabbit/core/NodeImpl.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/NodeImpl.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/NodeImpl.java (working copy)
@@ -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);
}
Index: src/main/java/org/apache/jackrabbit/core/SessionImpl.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/SessionImpl.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/SessionImpl.java (working copy)
@@ -1225,7 +1225,7 @@
public ValueFactory getValueFactory()
throws UnsupportedRepositoryOperationException, RepositoryException {
if (valueFactory == null) {
- valueFactory = new ValueFactoryImpl();
+ valueFactory = ValueFactoryImpl.getInstance();
}
return valueFactory;
}
Index: src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java (working copy)
@@ -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;
Index: src/main/java/org/apache/jackrabbit/core/xml/DocViewImportHandler.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/xml/DocViewImportHandler.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/xml/DocViewImportHandler.java (working copy)
@@ -27,7 +27,6 @@
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
-import javax.jcr.Value;
import java.io.IOException;
import java.io.Reader;
Index: src/main/java/org/apache/jackrabbit/core/xml/Importer.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/xml/Importer.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/xml/Importer.java (working copy)
@@ -16,8 +16,6 @@
*/
package org.apache.jackrabbit.core.xml;
-import org.apache.jackrabbit.name.NamespaceResolver;
-
import javax.jcr.RepositoryException;
import java.util.List;
Index: src/main/java/org/apache/jackrabbit/core/xml/ImportHandler.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/xml/ImportHandler.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/xml/ImportHandler.java (working copy)
@@ -17,7 +17,6 @@
package org.apache.jackrabbit.core.xml;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.Map;
import org.apache.jackrabbit.core.NamespaceRegistryImpl;
@@ -26,13 +25,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
-import javax.jcr.NamespaceException;
import javax.jcr.RepositoryException;
/**
Index: src/main/java/org/apache/jackrabbit/core/xml/NamespaceContext.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/xml/NamespaceContext.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/xml/NamespaceContext.java (working copy)
@@ -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
@@ -128,6 +131,14 @@
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} */
public QName getQName(String name)
throws IllegalNameException, UnknownPrefixException {
Index: src/main/java/org/apache/jackrabbit/core/xml/StringValue.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/xml/StringValue.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/core/xml/StringValue.java (working copy)
@@ -18,8 +18,6 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.Reader;
-import java.io.StringReader;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
Index: src/main/java/org/apache/jackrabbit/name/AbstractNamespaceResolver.java
===================================================================
--- src/main/java/org/apache/jackrabbit/name/AbstractNamespaceResolver.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/name/AbstractNamespaceResolver.java (working copy)
@@ -40,19 +40,33 @@
/**
* @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);
+ }
+
+ /**
* Creates a AbstractNamespaceResolver without listener
* support.
*/
@@ -164,4 +178,30 @@
currentListeners[i].namespaceRemapped(oldPrefix, newPrefix, uri);
}
}
+
+ /**
+ * Notifies the listeners that the namespace with the given uri
+ * 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);
+ }
+ }
}
Index: src/main/java/org/apache/jackrabbit/name/NameFormat.java
===================================================================
--- src/main/java/org/apache/jackrabbit/name/NameFormat.java (revision 0)
+++ src/main/java/org/apache/jackrabbit/name/NameFormat.java (revision 0)
@@ -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.xerces.util.XMLChar;
+
+import javax.jcr.NamespaceException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * NameFormat formats a {@link QName} using a
+ * {@link NamespaceResolver}.
+ */
+public class NameFormat {
+
+ /**
+ * The reqular expression pattern used to validate and parse
+ * qualified names.
+ *
+ * The pattern contains the following groups: + *
jcrName 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 jcrName 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 jcrName 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 jcrName 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 jcrName is a valid JCR-style name.
+ *
+ * @param jcrName the name to be checked
+ * @throws IllegalNameException If jcrName is not a valid
+ * JCR-style name.
+ */
+ public static void checkFormat(String jcrName) throws IllegalNameException {
+ parse(jcrName);
+ }
+
+ /**
+ * Returns a string representation of the qualified name 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 name 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());
+ }
+}
Property changes on: src\main\java\org\apache\jackrabbit\name\NameFormat.java
___________________________________________________________________
Name: svn:keywords
+ author date id revision url
Name: svn:eol-style
+ native
Index: src/main/java/org/apache/jackrabbit/name/NamespaceListener.java
===================================================================
--- src/main/java/org/apache/jackrabbit/name/NamespaceListener.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/name/NamespaceListener.java (working copy)
@@ -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);
}
Index: src/main/java/org/apache/jackrabbit/name/NamespaceResolver.java
===================================================================
--- src/main/java/org/apache/jackrabbit/name/NamespaceResolver.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/name/NamespaceResolver.java (working copy)
@@ -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;
}
Index: src/main/java/org/apache/jackrabbit/name/Path.java
===================================================================
--- src/main/java/org/apache/jackrabbit/name/Path.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/name/Path.java (working copy)
@@ -17,15 +17,11 @@
package org.apache.jackrabbit.name;
import org.apache.jackrabbit.util.Text;
-import org.apache.xerces.util.XMLChar;
-import javax.jcr.NamespaceException;
-import javax.jcr.PathNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import javax.jcr.PathNotFoundException;
/**
* The Path 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,34 +104,21 @@
public static final Path ROOT = new Path(new PathElement[]{ROOT_ELEMENT}, true);
/**
- * Pattern used to validate and parse path elements:- *
Path from the given jcrPath
* string. If normalize is true, 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 relPath 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 Path out of the given parent path
- * and the give name and index. If normalize is
- * true, 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 jcrPath 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 ith element of this path.
+ *
+ * @param i element index.
+ * @return the ith element of this path.
+ * @throws ArrayIndexOutOfBoundsException if this path does not have an
+ * element at index i.
+ */
+ public PathElement getElement(int i) {
+ return elements[i];
+ }
+
+ /**
* Returns a string representation of this Path 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 Path.
*
@@ -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 true if this element denotes the root element,
* otherwise returns false.
*
@@ -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(']');
Index: src/main/java/org/apache/jackrabbit/name/PathFormat.java
===================================================================
--- src/main/java/org/apache/jackrabbit/name/PathFormat.java (revision 0)
+++ src/main/java/org/apache/jackrabbit/name/PathFormat.java (revision 0)
@@ -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.xerces.util.XMLChar;
+
+import javax.jcr.NamespaceException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * PathFormat formats a {@link Path} using a
+ * {@link NamespaceResolver}.
+ */
+public class PathFormat {
+
+ /**
+ * Pattern used to validate and parse path elements:
+ *
+ * - group 1 is .
+ *
- group 2 is ..
+ *
- group 3 is namespace prefix incl. delimiter (colon)
+ *
- group 4 is namespace prefix excl. delimiter (colon)
+ *
- group 5 is localName
+ *
- group 6 is index incl. brackets
+ *
- group 7 is index excl. brackets
+ *
+ */
+ 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 jcrPath into a qualified path using
+ * resolver to convert prefixes into namespace URI's.
+ *
+ * @param jcrPath the jcr path.
+ * @param resolver the namespace resolver.
+ * @return qualified path.
+ * @throws MalformedPathException if jcrPath is malformed.
+ */
+ public static Path parse(String jcrPath, NamespaceResolver resolver)
+ throws MalformedPathException {
+ return parse(null, jcrPath, resolver);
+ }
+
+ /**
+ * Parses the give jcrPath and returns a Path. If
+ * parent is not null, it is prepended to the
+ * returned list. If resolver is null, this method
+ * only checks the format of the string and returns null.
+ *
+ * @param 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 jcrPath 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 jcrPath is a valid JCR-style absolute or relative
+ * path.
+ *
+ * @param jcrPath the path to be checked
+ * @throws MalformedPathException If jcrPath 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 path 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(']');
+ }
+ }
+
+}
Property changes on: src\main\java\org\apache\jackrabbit\name\PathFormat.java
___________________________________________________________________
Name: svn:keywords
+ author date id revision url
Name: svn:eol-style
+ native
Index: src/main/java/org/apache/jackrabbit/name/QName.java
===================================================================
--- src/main/java/org/apache/jackrabbit/name/QName.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/name/QName.java (working copy)
@@ -16,12 +16,7 @@
*/
package org.apache.jackrabbit.name;
-import org.apache.xerces.util.XMLChar;
-
-import javax.jcr.NamespaceException;
import java.io.Serializable;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Qualified name. A qualified name is a combination of a namespace URI
@@ -107,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");
@@ -503,30 +503,6 @@
public static final QName[] EMPTY_ARRAY = new QName[0];
- /**
- * The reqular expression pattern used to validate and parse
- * qualified names.
- *
- * The pattern contains the following groups:
- *
- * - group 1 is namespace prefix incl. delimiter (colon)
- *
- group 2 is namespace prefix excl. delimiter (colon)
- *
- group 3 is localName
- *
- */
- 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;
@@ -544,10 +520,11 @@
* local part.
*
* 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 localName is invalid.
*/
public QName(String namespaceURI, String localName) {
if (namespaceURI == null) {
@@ -575,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);
}
/**
@@ -658,48 +617,10 @@
* the prefix (or empty string), the second the local name.
* @throws IllegalNameException If jcrName 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 >
@@ -729,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);
}
/**
@@ -747,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 QName in the
* following format:
@@ -776,6 +685,7 @@
*
* @return the string representation of this QName.
* @see #valueOf(String)
+ * @see Object#toString()
*/
public String toString() {
// QName is immutable, we can store the string representation
@@ -829,6 +739,7 @@
return h;
}
+ //------------------------------------------------------------< Cloneable >
/**
* Creates a clone of this qualified name.
* Overriden in order to make clone() public.
@@ -842,6 +753,7 @@
return super.clone();
}
+ //-----------------------------------------------------------< Comparable >
/**
* Compares two qualified names.
*
Index: src/main/java/org/apache/jackrabbit/util/PathMap.java
===================================================================
--- src/main/java/org/apache/jackrabbit/util/PathMap.java (revision 0)
+++ src/main/java/org/apache/jackrabbit/util/PathMap.java (revision 0)
@@ -0,0 +1,577 @@
+/*
+ * 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.util;
+
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.QName;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Generic path map that associates information with the individual path elements
+ * of a path.
+ */
+public class PathMap {
+
+ /**
+ * Root element
+ */
+ private final Element root = new Element(Path.ROOT.getNameElement());
+
+ /**
+ * Map a path to a child. If exact is false,
+ * returns the last available item along the path that is stored in the map.
+ * @param path path to map
+ * @param exact flag indicating whether an exact match is required
+ * @return child, maybe null if exact is
+ * true
+ */
+ public Element map(Path path, boolean exact) {
+ Path.PathElement[] elements = path.getElements();
+ Element current = root;
+
+ for (int i = 1; i < elements.length; i++) {
+ Element next = current.getChild(elements[i]);
+ if (next == null) {
+ if (exact) {
+ return null;
+ }
+ break;
+ }
+ current = next;
+ }
+ return current;
+ }
+
+ /**
+ * Create an element given by its path. The path map will create any necessary
+ * intermediate elements.
+ * @param path path to child
+ * @param obj object to store at destination
+ */
+ public Element put(Path path, Object obj) {
+ Element element = put(path);
+ element.obj = obj;
+ return element;
+ }
+
+ /**
+ * Put an element given by its path. The path map will create any necessary
+ * intermediate elements.
+ * @param path path to child
+ * @param element element to store at destination
+ */
+ public void put(Path path, Element element) {
+ Path.PathElement[] elements = path.getElements();
+ Element current = root;
+
+ for (int i = 1; i < elements.length - 1; i++) {
+ Element next = current.getChild(elements[i]);
+ if (next == null) {
+ next = current.createChild(elements[i]);
+ }
+ current = next;
+ }
+ current.put(path.getNameElement(), element);
+ }
+
+ /**
+ * Create an empty child given by its path.
+ * @param path path to child
+ */
+ public Element put(Path path) {
+ Path.PathElement[] elements = path.getElements();
+ Element current = root;
+
+ for (int i = 1; i < elements.length; i++) {
+ Element next = current.getChild(elements[i]);
+ if (next == null) {
+ next = current.createChild(elements[i]);
+ }
+ current = next;
+ }
+ return current;
+ }
+
+ /**
+ * Traverse the path map and call back requester.
+ * @param includeEmpty if true invoke call back on every child
+ * regardless, whether the associated object is empty
+ * or not; otherwise call back on non-empty children
+ * only
+ */
+ public void traverse(ElementVisitor visitor, boolean includeEmpty) {
+ root.traverse(visitor, includeEmpty);
+ }
+
+ /**
+ * Internal class holding the object associated with a certain
+ * path element.
+ */
+ public static class Element {
+
+ /**
+ * Parent element
+ */
+ private Element parent;
+
+ /**
+ * Map of immediate children
+ */
+ private Map children;
+
+ /**
+ * Number of non-empty children
+ */
+ private int childrenCount;
+
+ /**
+ * Object associated with this element
+ */
+ private Object obj;
+
+ /**
+ * QName associated with this element
+ */
+ private QName name;
+
+ /**
+ * 1-based index associated with this element where index=0 is
+ * equivalent to index=1.
+ */
+ private int index;
+
+ /**
+ * Create a new instance of this class with a path element.
+ * @param nameIndex path element of this child
+ */
+ private Element(Path.PathElement nameIndex) {
+ this.name = nameIndex.getName();
+ this.index = nameIndex.getIndex();
+ }
+
+ /**
+ * Create a child of this node inside the path map.
+ * @param nameIndex position where child is created
+ * @return child
+ */
+ private Element createChild(Path.PathElement nameIndex) {
+ Element element = new Element(nameIndex);
+ put(nameIndex, element);
+ return element;
+ }
+
+ /**
+ * Insert an empty child. Will shift all children having an index
+ * greater than or equal to the child inserted to the right.
+ * @param nameIndex position where child is inserted
+ */
+ public void insert(Path.PathElement nameIndex) {
+ // convert 1-based index value to 0-base value
+ int index = getZeroBasedIndex(nameIndex);
+ if (children != null) {
+ ArrayList list = (ArrayList) children.get(nameIndex.getName());
+ if (list != null && list.size() > index) {
+ for (int i = index; i < list.size(); i++) {
+ Element element = (Element) list.get(i);
+ if (element != null) {
+ element.index = element.getNormalizedIndex() + 1;
+ }
+ }
+ list.add(index, null);
+ }
+ }
+ }
+
+ /**
+ * Return an element matching a name and index.
+ * @param nameIndex position where child is located
+ * @return element matching nameIndex or null if
+ * none exists.
+ */
+ private Element getChild(Path.PathElement nameIndex) {
+ // convert 1-based index value to 0-base value
+ int index = getZeroBasedIndex(nameIndex);
+ Element element = null;
+
+ if (children != null) {
+ ArrayList list = (ArrayList) children.get(nameIndex.getName());
+ if (list != null && list.size() > index) {
+ element = (Element) list.get(index);
+ }
+ }
+ return element;
+ }
+
+ /**
+ * Link a child of this node. Position is given by nameIndex.
+ * @param nameIndex position where child should be located
+ * @param element element to add
+ */
+ public void put(Path.PathElement nameIndex, Element element) {
+ // convert 1-based index value to 0-base value
+ int index = getZeroBasedIndex(nameIndex);
+ if (children == null) {
+ children = new HashMap();
+ }
+ ArrayList list = (ArrayList) children.get(nameIndex.getName());
+ if (list == null) {
+ list = new ArrayList();
+ children.put(nameIndex.getName(), list);
+ }
+ while (list.size() < index) {
+ list.add(null);
+ }
+ if (list.size() == index) {
+ list.add(element);
+ } else {
+ list.set(index, element);
+ }
+
+ element.parent = this;
+ element.name = nameIndex.getName();
+ element.index = nameIndex.getIndex();
+
+ childrenCount++;
+ }
+
+ /**
+ * Remove a child. Will shift all children having an index greater than
+ * the child removed to the left. If there are no more children left in
+ * this element and no object is associated with this element, the
+ * element itself gets removed.
+ *
+ * @param nameIndex child's path element
+ * @return removed child, may be null
+ */
+ public Element remove(Path.PathElement nameIndex) {
+ return remove(nameIndex, true);
+ }
+
+ /**
+ * Remove a child. If shift is set to true,
+ * will shift all children having an index greater than the child
+ * removed to the left. If there are no more children left in
+ * this element and no object is associated with this element, the
+ * element itself gets removed.
+ *
+ * @param nameIndex child's path element
+ * @param shift whether to shift same name siblings having a greater
+ * index to the left
+ * @return removed child, may be null
+ */
+ private Element remove(Path.PathElement nameIndex, boolean shift) {
+ // convert 1-based index value to 0-base value
+ int index = getZeroBasedIndex(nameIndex);
+ if (children == null) {
+ return null;
+ }
+ ArrayList list = (ArrayList) children.get(nameIndex.getName());
+ if (list == null || list.size() <= index) {
+ return null;
+ }
+ Element element = (Element) list.set(index, null);
+ if (shift) {
+ for (int i = index + 1; i < list.size(); i++) {
+ Element sibling = (Element) list.get(i);
+ if (sibling != null) {
+ sibling.index--;
+ }
+ }
+ list.remove(index);
+ }
+ if (element != null) {
+ element.parent = null;
+ childrenCount--;
+ }
+ if (childrenCount == 0 && obj == null && parent != null) {
+ parent.remove(getPathElement(), shift);
+ }
+ return element;
+ }
+
+ /**
+ * Remove this element. Delegates the call to the parent item.
+ * Index of same name siblings will be shifted!
+ */
+ public void remove() {
+ remove(true);
+ }
+
+ /**
+ * Remove this element. Delegates the call to the parent item.
+ * @param shift if index of same name siblings will be shifted.
+ */
+ public void remove(boolean shift) {
+ if (parent != null) {
+ parent.remove(getPathElement(), shift);
+ }
+ }
+
+ /**
+ * Remove all children of this element. Removes this element itself
+ * if this element does not contain associated information.
+ */
+ public void removeAll() {
+ children = null;
+ childrenCount = 0;
+
+ if (obj == null && parent != null) {
+ parent.remove(getPathElement(), false);
+ }
+ }
+
+ /**
+ * Return the object associated with this element
+ * @return object associated with this element
+ */
+ public Object get() {
+ return obj;
+ }
+
+ /**
+ * Set the object associated with this element
+ * @param obj object associated with this element
+ */
+ public void set(Object obj) {
+ this.obj = obj;
+
+ if (obj == null && childrenCount == 0 && parent != null) {
+ parent.remove(getPathElement(), false);
+ }
+ }
+
+ /**
+ * Return the name of this element
+ * @return name
+ */
+ public QName getName() {
+ return name;
+ }
+
+ /**
+ * Return the non-normalized 1-based index of this element. Note that
+ * this method can return a value of 0 which should be treated as 1.
+ * @return index
+ * @see #getNormalizedIndex()
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Return the 1-based index of this element.
+ * Same as {@link #getIndex()} except that an {@link Path#INDEX_UNDEFINED
+ * undefined index} value is automatically converted to the
+ * {@link Path#INDEX_DEFAULT default index} value.
+ * @return 1-based index
+ */
+ public int getNormalizedIndex() {
+ if (index == Path.INDEX_UNDEFINED) {
+ return Path.INDEX_DEFAULT;
+ } else {
+ return index;
+ }
+ }
+
+ /**
+ * Return a path element pointing to this element
+ * @return path element
+ */
+ public Path.PathElement getPathElement() {
+ return Path.create(name, index).getNameElement();
+ }
+
+ /**
+ * Return the path of this element.
+ * @return path
+ * @throws MalformedPathException if building the path fails
+ */
+ public Path getPath() throws MalformedPathException {
+ if (parent == null) {
+ return Path.ROOT;
+ }
+
+ Path.PathBuilder builder = new Path.PathBuilder();
+ getPath(builder);
+ return builder.getPath();
+ }
+
+ /**
+ * Internal implementation of {@link #getPath()} that populates entries
+ * in a builder. On exit, builder contains the path
+ * of this element
+ */
+ private void getPath(Path.PathBuilder builder) {
+ if (parent == null) {
+ builder.addRoot();
+ return;
+ }
+ parent.getPath(builder);
+ if (index == Path.INDEX_UNDEFINED || index == Path.INDEX_DEFAULT) {
+ builder.addLast(name);
+ } else {
+ builder.addLast(name, index);
+ }
+ }
+
+ /**
+ * Checks whether this element has the specified path. Introduced to
+ * avoid catching a MalformedPathException for simple
+ * path comparisons.
+ * @param path path to compare to
+ * @return true if this child has the path
+ * path, false otherwise
+ */
+ public boolean hasPath(Path path) {
+ return hasPath(path.getElements(), path.getLength());
+ }
+
+ /**
+ * Checks whether this element has the specified path, given by
+ * path elements.
+ * @param elements path elements to compare to
+ * @param len number of elements to compare to
+ * @return true if this element has the path given;
+ * otherwise false
+ */
+ private boolean hasPath(Path.PathElement[] elements, int len) {
+ if (getPathElement().equals(elements[len - 1])) {
+ if (parent != null) {
+ return parent.hasPath(elements, len - 1);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return 0-based index of a path element.
+ */
+ private static int getZeroBasedIndex(Path.PathElement nameIndex) {
+ return nameIndex.getNormalizedIndex() - 1;
+ }
+
+ /**
+ * Recursively invoked traversal method.
+ * @param visitor visitor to invoke
+ * @param includeEmpty if true invoke call back on every
+ * element regardless, whether the associated object is empty
+ * or not; otherwise call back on non-empty children only
+ */
+ public void traverse(ElementVisitor visitor, boolean includeEmpty) {
+ if (children != null) {
+ Iterator iter = children.values().iterator();
+ while (iter.hasNext()) {
+ ArrayList list = (ArrayList) iter.next();
+ for (int i = 0; i < list.size(); i++) {
+ Element element = (Element) list.get(i);
+ if (element != null) {
+ element.traverse(visitor, includeEmpty);
+ }
+ }
+ }
+ }
+ if (includeEmpty || obj != null) {
+ visitor.elementVisited(this);
+ }
+ }
+
+ /**
+ * Return the depth of this element. Defined to be 0 for the
+ * root element and n + 1 for some element if the depth of
+ * its parent is n.
+ */
+ public int getDepth() {
+ if (parent != null) {
+ return parent.getDepth() + 1;
+ }
+ // Root
+ return Path.ROOT_DEPTH;
+ }
+
+ /**
+ * Return a flag indicating whether the specified node is a
+ * child of this node.
+ * @param other node to check
+ */
+ public boolean isAncestorOf(Element other) {
+ Element parent = other.parent;
+ while (parent != null) {
+ if (parent == this) {
+ return true;
+ }
+ parent = parent.parent;
+ }
+ return false;
+ }
+
+ /**
+ * Return the parent of this element
+ * @return parent or null if this is the root element
+ */
+ public Element getParent() {
+ return parent;
+ }
+
+ /**
+ * Return the children count of this element
+ * @return children count
+ */
+ public int getChildrenCount() {
+ return childrenCount;
+ }
+
+ /**
+ * Return an iterator over all of this element's children. Every
+ * element returned by this iterator is of type {@link #Element}.
+ */
+ public Iterator getChildren() {
+ ArrayList result = new ArrayList();
+
+ if (children != null) {
+ Iterator iter = children.values().iterator();
+ while (iter.hasNext()) {
+ ArrayList list = (ArrayList) iter.next();
+ for (int i = 0; i < list.size(); i++) {
+ Element element = (Element) list.get(i);
+ if (element != null) {
+ result.add(element);
+ }
+ }
+ }
+ }
+ return result.iterator();
+ }
+ }
+
+ /**
+ * Element visitor used in {@link PathMap#traverse}
+ */
+ public interface ElementVisitor {
+
+ /**
+ * Invoked for every element visited on a tree traversal
+ * @param element element visited
+ */
+ void elementVisited(Element element);
+ }
+}
Property changes on: src\main\java\org\apache\jackrabbit\util\PathMap.java
___________________________________________________________________
Name: svn:keywords
+ author date id revision url
Name: svn:eol-style
+ native
Index: src/main/java/org/apache/jackrabbit/util/Text.java
===================================================================
--- src/main/java/org/apache/jackrabbit/util/Text.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/util/Text.java (working copy)
@@ -532,6 +532,19 @@
}
/**
+ * Same as {@link #getName(String)} but adding the possibility
+ * to pass paths that end with a trailing '/'
+ *
+ * @see #getName(String)
+ */
+ public static String getName(String path, boolean ignoreTrailingSlash) {
+ if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) {
+ path = path.substring(0, path.length()-1);
+ }
+ return getName(path);
+ }
+
+ /**
* Returns the namespace prefix of the given qname. If the
* prefix is missing, an empty string is returned. Please note, that this
* method does not validate the name or prefix.
@@ -637,6 +650,19 @@
}
/**
+ * Same as {@link #getRelativeParent(String, int)} but adding the possibility
+ * to pass paths that end with a trailing '/'
+ *
+ * @see #getRelativeParent(String, int)
+ */
+ public static String getRelativeParent(String path, int level, boolean ignoreTrailingSlash) {
+ if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) {
+ path = path.substring(0, path.length()-1);
+ }
+ return getRelativeParent(path, level);
+ }
+
+ /**
* Returns the nth absolute parent of the path, where n=level.
* Example:
*
Index: src/main/java/org/apache/jackrabbit/value/NameValue.java
===================================================================
--- src/main/java/org/apache/jackrabbit/value/NameValue.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/value/NameValue.java (working copy)
@@ -16,8 +16,8 @@
*/
package org.apache.jackrabbit.value;
+import org.apache.jackrabbit.name.NameFormat;
import org.apache.jackrabbit.name.IllegalNameException;
-import org.apache.jackrabbit.name.QName;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
@@ -50,7 +50,7 @@
public static NameValue valueOf(String s) throws ValueFormatException {
if (s != null) {
try {
- QName.checkFormat(s);
+ NameFormat.checkFormat(s);
} catch (IllegalNameException ine) {
throw new ValueFormatException(ine.getMessage());
}
Index: src/main/java/org/apache/jackrabbit/value/PathValue.java
===================================================================
--- src/main/java/org/apache/jackrabbit/value/PathValue.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/value/PathValue.java (working copy)
@@ -17,7 +17,7 @@
package org.apache.jackrabbit.value;
import org.apache.jackrabbit.name.MalformedPathException;
-import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.PathFormat;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
@@ -51,7 +51,7 @@
public static PathValue valueOf(String s) throws ValueFormatException {
if (s != null) {
try {
- Path.checkFormat(s);
+ PathFormat.checkFormat(s);
} catch (MalformedPathException mpe) {
throw new ValueFormatException(mpe.getMessage());
}
Index: src/main/java/org/apache/jackrabbit/value/ValueFactoryImpl.java
===================================================================
--- src/main/java/org/apache/jackrabbit/value/ValueFactoryImpl.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/value/ValueFactoryImpl.java (working copy)
@@ -21,22 +21,34 @@
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
+import javax.jcr.PropertyType;
import java.io.InputStream;
import java.util.Calendar;
/**
- * This class implements the ValueFactory interface.
+ * This class implements the ValueFactory interface.
*
* @see javax.jcr.Session#getValueFactory()
*/
public class ValueFactoryImpl implements ValueFactory {
+ private static final ValueFactory valueFactory = new ValueFactoryImpl();
+
/**
* Constructs a ValueFactory object.
*/
- public ValueFactoryImpl() {
+ private ValueFactoryImpl() {
}
+ //--------------------------------------------------------------------------
+ /**
+ *
+ * @return
+ */
+ public static ValueFactory getInstance() {
+ return valueFactory;
+ }
+
//---------------------------------------------------------< ValueFactory >
/**
* {@inheritDoc}
@@ -92,6 +104,38 @@
*/
public Value createValue(String value, int type)
throws ValueFormatException {
- return ValueHelper.convert(value, type);
+ Value val;
+ switch (type) {
+ case PropertyType.STRING:
+ val = new StringValue(value);
+ break;
+ case PropertyType.BOOLEAN:
+ val = BooleanValue.valueOf(value);
+ break;
+ case PropertyType.DOUBLE:
+ val = DoubleValue.valueOf(value);
+ break;
+ case PropertyType.LONG:
+ val = LongValue.valueOf(value);
+ break;
+ case PropertyType.DATE:
+ val = DateValue.valueOf(value);
+ break;
+ case PropertyType.NAME:
+ val = NameValue.valueOf(value);
+ break;
+ case PropertyType.PATH:
+ val = PathValue.valueOf(value);
+ break;
+ case PropertyType.REFERENCE:
+ val = ReferenceValue.valueOf(value);
+ break;
+ case PropertyType.BINARY:
+ val = new BinaryValue(value);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid type constant: " + type);
+ }
+ return val;
}
}
Index: src/main/java/org/apache/jackrabbit/value/ValueHelper.java
===================================================================
--- src/main/java/org/apache/jackrabbit/value/ValueHelper.java (revision 417443)
+++ src/main/java/org/apache/jackrabbit/value/ValueHelper.java (working copy)
@@ -24,6 +24,7 @@
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
+import javax.jcr.ValueFactory;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.Reader;
@@ -48,36 +49,105 @@
}
/**
+ * Same as {@link #convert(String, int, ValueFactory)} using
+ * ValueFactoryImpl.
+ *
* @param srcValue
* @param targetType
* @return
* @throws ValueFormatException
* @throws IllegalArgumentException
+ * @deprecated Use {@link #convert(String, int, ValueFactory)} instead.
*/
public static Value convert(String srcValue, int targetType)
throws ValueFormatException, IllegalArgumentException {
+ return convert(srcValue, targetType, ValueFactoryImpl.getInstance());
+ }
+
+ /**
+ * @param srcValue
+ * @param targetType
+ * @param factory
+ * @return
+ * @throws ValueFormatException
+ * @throws IllegalArgumentException
+ */
+ public static Value convert(String srcValue, int targetType, ValueFactory factory)
+ throws ValueFormatException, IllegalArgumentException {
if (srcValue == null) {
return null;
} else {
- return convert(new StringValue(srcValue), targetType);
+ return factory.createValue(srcValue, targetType);
}
}
/**
+ * Same as {@link #convert(InputStream, int, ValueFactory)} using
+ * ValueFactoryImpl.
+ *
+ * @param srcValue
+ * @param targetType
+ * @return
+ * @throws ValueFormatException
+ * @throws IllegalArgumentException
+ * @deprecated Use {@link #convert(InputStream, int, ValueFactory)} instead.
+ */
+ public static Value convert(InputStream srcValue, int targetType)
+ throws ValueFormatException, IllegalArgumentException {
+ return convert(srcValue, targetType, ValueFactoryImpl.getInstance());
+ }
+
+ /**
+ * @param srcValue
+ * @param targetType
+ * @param factory
+ * @return
+ * @throws ValueFormatException
+ * @throws IllegalArgumentException
+ */
+ public static Value convert(InputStream srcValue, int targetType, ValueFactory factory)
+ throws ValueFormatException, IllegalArgumentException {
+ if (srcValue == null) {
+ return null;
+ } else {
+ return convert(factory.createValue(srcValue), targetType, factory);
+ }
+ }
+
+ /**
+ * Same as {@link #convert(String[], int, ValueFactory)} using
+ * ValueFactoryImpl.
+ *
* @param srcValues
* @param targetType
* @return
* @throws ValueFormatException
* @throws IllegalArgumentException
+ * @deprecated Use {@link #convert(String[], int, ValueFactory)} instead.
*/
public static Value[] convert(String[] srcValues, int targetType)
throws ValueFormatException, IllegalArgumentException {
+ return convert(srcValues, targetType, ValueFactoryImpl.getInstance());
+ }
+
+ /**
+ * Same as {@link #convert(String[], int, ValueFactory)} using
+ * ValueFactoryImpl.
+ *
+ * @param srcValues
+ * @param targetType
+ * @return
+ * @throws ValueFormatException
+ * @throws IllegalArgumentException
+ */
+ public static Value[] convert(String[] srcValues, int targetType, ValueFactory factory)
+ throws ValueFormatException, IllegalArgumentException {
if (srcValues == null) {
return null;
}
Value[] newValues = new Value[srcValues.length];
for (int i = 0; i < srcValues.length; i++) {
- newValues[i] = convert(srcValues[i], targetType);
+ newValues[i] = convert(srcValues[i], targetType, factory);
}
return newValues;
}
@@ -89,7 +159,45 @@
* @throws ValueFormatException
* @throws IllegalArgumentException
*/
+ public static Value[] convert(InputStream[] srcValues, int targetType,
+ ValueFactory factory)
+ throws ValueFormatException, IllegalArgumentException {
+ if (srcValues == null) {
+ return null;
+ }
+ Value[] newValues = new Value[srcValues.length];
+ for (int i = 0; i < srcValues.length; i++) {
+ newValues[i] = convert(srcValues[i], targetType, factory);
+ }
+ return newValues;
+ }
+
+ /**
+ * Same as {@link #convert(Value[], int, ValueFactory)} using
+ * ValueFactoryImpl.
+ *
+ * @param srcValues
+ * @param targetType
+ * @return
+ * @throws ValueFormatException
+ * @throws IllegalArgumentException
+ * @deprecated Use {@link #convert(Value[], int, ValueFactory)} instead.
+ */
public static Value[] convert(Value[] srcValues, int targetType)
+ throws ValueFormatException, IllegalArgumentException {
+ return convert(srcValues, targetType, ValueFactoryImpl.getInstance());
+ }
+
+ /**
+ * @param srcValues
+ * @param targetType
+ * @param factory
+ * @return
+ * @throws ValueFormatException
+ * @throws IllegalArgumentException
+ */
+ public static Value[] convert(Value[] srcValues, int targetType,
+ ValueFactory factory)
throws ValueFormatException, IllegalArgumentException {
if (srcValues == null) {
return null;
@@ -111,20 +219,39 @@
throw new ValueFormatException(msg);
}
- newValues[i] = convert(srcValues[i], targetType);
+ newValues[i] = convert(srcValues[i], targetType, factory);
}
return newValues;
}
/**
+ * Same as {@link #convert(Value, int, ValueFactory)} using
+ * ValueFactoryImpl.
+ *
* @param srcValue
* @param targetType
* @return
* @throws ValueFormatException
* @throws IllegalStateException
* @throws IllegalArgumentException
+ * @deprecated Use {@link #convert(Value, int, ValueFactory)} instead.
*/
public static Value convert(Value srcValue, int targetType)
+ throws ValueFormatException, IllegalStateException,
+ IllegalArgumentException {
+ return convert(srcValue, targetType, ValueFactoryImpl.getInstance());
+ }
+
+ /**
+ * @param srcValue
+ * @param targetType
+ * @param factory
+ * @return
+ * @throws ValueFormatException
+ * @throws IllegalStateException
+ * @throws IllegalArgumentException
+ */
+ public static Value convert(Value srcValue, int targetType, ValueFactory factory)
throws ValueFormatException, IllegalStateException,
IllegalArgumentException {
if (srcValue == null) {
@@ -143,7 +270,7 @@
case PropertyType.STRING:
// convert to STRING
try {
- val = new StringValue(srcValue.getString());
+ val = factory.createValue(srcValue.getString());
} catch (RepositoryException re) {
throw new ValueFormatException("conversion failed: "
+ PropertyType.nameFromValue(srcType) + " to "
@@ -154,7 +281,7 @@
case PropertyType.BINARY:
// convert to BINARY
try {
- val = new BinaryValue(srcValue.getStream());
+ val = factory.createValue(srcValue.getStream());
} catch (RepositoryException re) {
throw new ValueFormatException("conversion failed: "
+ PropertyType.nameFromValue(srcType) + " to "
@@ -165,7 +292,7 @@
case PropertyType.BOOLEAN:
// convert to BOOLEAN
try {
- val = new BooleanValue(srcValue.getBoolean());
+ val = factory.createValue(srcValue.getBoolean());
} catch (RepositoryException re) {
throw new ValueFormatException("conversion failed: "
+ PropertyType.nameFromValue(srcType) + " to "
@@ -176,7 +303,7 @@
case PropertyType.DATE:
// convert to DATE
try {
- val = new DateValue(srcValue.getDate());
+ val = factory.createValue(srcValue.getDate());
} catch (RepositoryException re) {
throw new ValueFormatException("conversion failed: "
+ PropertyType.nameFromValue(srcType) + " to "
@@ -187,7 +314,7 @@
case PropertyType.DOUBLE:
// convert to DOUBLE
try {
- val = new DoubleValue(srcValue.getDouble());
+ val = factory.createValue(srcValue.getDouble());
} catch (RepositoryException re) {
throw new ValueFormatException("conversion failed: "
+ PropertyType.nameFromValue(srcType) + " to "
@@ -198,7 +325,7 @@
case PropertyType.LONG:
// convert to LONG
try {
- val = new LongValue(srcValue.getLong());
+ val = factory.createValue(srcValue.getLong());
} catch (RepositoryException re) {
throw new ValueFormatException("conversion failed: "
+ PropertyType.nameFromValue(srcType) + " to "
@@ -227,7 +354,7 @@
throw new ValueFormatException("failed to convert source value to PATH value",
re);
}
- val = PathValue.valueOf(path);
+ val = factory.createValue(path, targetType);
break;
case PropertyType.BOOLEAN:
@@ -265,7 +392,7 @@
throw new ValueFormatException("failed to convert source value to NAME value",
re);
}
- val = NameValue.valueOf(name);
+ val = factory.createValue(name, targetType);
break;
case PropertyType.BOOLEAN:
@@ -299,10 +426,9 @@
uuid = srcValue.getString();
} catch (RepositoryException re) {
// should never happen
- throw new ValueFormatException("failed to convert source value to REFERENCE value",
- re);
+ throw new ValueFormatException("failed to convert source value to REFERENCE value", re);
}
- val = ReferenceValue.valueOf(uuid);
+ val = factory.createValue(uuid, targetType);
break;
case PropertyType.BOOLEAN:
@@ -328,11 +454,26 @@
}
/**
+ * Same as {@link #copy(Value, ValueFactory)} using ValueFactoryImpl.
+ *
* @param srcValue
* @return
* @throws IllegalStateException
+ * @deprecated Use {@link #copy(Value, ValueFactory)} instead.
*/
public static Value copy(Value srcValue) throws IllegalStateException {
+ return copy(srcValue, ValueFactoryImpl.getInstance());
+ }
+
+ /**
+ *
+ * @param srcValue
+ * @param factory
+ * @return
+ * @throws IllegalStateException
+ */
+ public static Value copy(Value srcValue, ValueFactory factory)
+ throws IllegalStateException {
if (srcValue == null) {
return null;
}
@@ -341,39 +482,33 @@
try {
switch (srcValue.getType()) {
case PropertyType.BINARY:
- newVal = new BinaryValue(srcValue.getStream());
+ newVal = factory.createValue(srcValue.getStream());
break;
case PropertyType.BOOLEAN:
- newVal = new BooleanValue(srcValue.getBoolean());
+ newVal = factory.createValue(srcValue.getBoolean());
break;
case PropertyType.DATE:
- newVal = new DateValue(srcValue.getDate());
+ newVal = factory.createValue(srcValue.getDate());
break;
case PropertyType.DOUBLE:
- newVal = new DoubleValue(srcValue.getDouble());
+ newVal = factory.createValue(srcValue.getDouble());
break;
case PropertyType.LONG:
- newVal = new LongValue(srcValue.getLong());
+ newVal = factory.createValue(srcValue.getLong());
break;
case PropertyType.PATH:
- newVal = PathValue.valueOf(srcValue.getString());
- break;
-
case PropertyType.NAME:
- newVal = NameValue.valueOf(srcValue.getString());
- break;
-
case PropertyType.REFERENCE:
- newVal = ReferenceValue.valueOf(srcValue.getString());
+ newVal = factory.createValue(srcValue.getString(), srcValue.getType());
break;
case PropertyType.STRING:
- newVal = new StringValue(srcValue.getString());
+ newVal = factory.createValue(srcValue.getString());
break;
}
} catch (RepositoryException re) {
@@ -383,18 +518,32 @@
}
/**
+ * Same as {@link #copy(Value[], ValueFactory)} using ValueFactoryImpl.
+ *
* @param srcValues
* @return
* @throws IllegalStateException
+ * @deprecated Use {@link #copy(Value[], ValueFactory)} instead.
*/
public static Value[] copy(Value[] srcValues) throws IllegalStateException {
+ return copy(srcValues, ValueFactoryImpl.getInstance());
+ }
+
+ /**
+ * @param srcValues
+ * @param factory
+ * @return
+ * @throws IllegalStateException
+ */
+ public static Value[] copy(Value[] srcValues, ValueFactory factory)
+ throws IllegalStateException {
if (srcValues == null) {
return null;
}
Value[] newValues = new Value[srcValues.length];
for (int i = 0; i < srcValues.length; i++) {
- newValues[i] = copy(srcValues[i]);
+ newValues[i] = copy(srcValues[i], factory);
}
return newValues;
}
@@ -470,6 +619,8 @@
/**
* Deserializes the given string to a Value of the given type.
+ * Same as {@link #deserialize(String, int, boolean, ValueFactory)} using
+ * ValueFactoryImpl.
*
* @param value string to be deserialized
* @param type type of value
@@ -481,10 +632,33 @@
* format
* @throws RepositoryException if an error occured during the
* deserialization.
+ * @deprecated Use {@link #deserialize(String, int, boolean, ValueFactory)}
+ * instead.
*/
public static Value deserialize(String value, int type,
boolean decodeBlanks)
throws ValueFormatException, RepositoryException {
+ return deserialize(value, type, decodeBlanks, ValueFactoryImpl.getInstance());
+ }
+
+ /**
+ * Deserializes the given string to a Value of the given type.
+ *
+ * @param value string to be deserialized
+ * @param type type of value
+ * @param decodeBlanks if true "_x0020_"
+ * character sequences will be decoded to single space
+ * characters each.
+ * @param factory ValueFactory used to build the Value object.
+ * @return the deserialized Value
+ * @throws ValueFormatException if the string data is not of the required
+ * format
+ * @throws RepositoryException if an error occured during the
+ * deserialization.
+ */
+ public static Value deserialize(String value, int type, boolean decodeBlanks,
+ ValueFactory factory)
+ throws ValueFormatException, RepositoryException {
if (type == PropertyType.BINARY) {
// base64 encoded binary value;
// the encodeBlanks flag can be ignored since base64-encoded
@@ -498,19 +672,24 @@
throw new RepositoryException("failed to decode binary value",
ioe);
}
+ // NOTE: for performance reasons the BinaryValue is created directly
+ // from the byte-array. This is inconsistent with the other calls,
+ // that delegate the value creation to the ValueFactory.
return new BinaryValue(baos.toByteArray());
} else {
if (decodeBlanks) {
// decode encoded blanks in value
value = Text.replace(value, "_x0020_", " ");
}
- return convert(value, type);
+ return convert(value, type, factory);
}
}
/**
* Deserializes the string data read from the given reader to a
- * Value of the given type.
+ * Value of the given type. Same as
+ * {@link #deserialize(Reader, int, boolean, ValueFactory)} using
+ * ValueFactoryImpl.
*
* @param reader reader for the string data to be deserialized
* @param type type of value
@@ -524,10 +703,36 @@
* format
* @throws RepositoryException if an error occured during the
* deserialization.
+ * @deprecated Use {@link #deserialize(Reader, int, boolean, ValueFactory)}
+ * instead.
*/
public static Value deserialize(Reader reader, int type,
boolean decodeBlanks)
throws IOException, ValueFormatException, RepositoryException {
+ return deserialize(reader, type, decodeBlanks, ValueFactoryImpl.getInstance());
+ }
+
+ /**
+ * Deserializes the string data read from the given reader to a
+ * Value of the given type.
+ *
+ * @param reader reader for the string data to be deserialized
+ * @param type type of value
+ * @param decodeBlanks if true "_x0020_"
+ * character sequences will be decoded to single space
+ * characters each.
+ * @param factory ValueFactory used to build the Value object.
+ * @return the deserialized Value
+ * @throws IOException if an i/o error occured during the
+ * serialization
+ * @throws ValueFormatException if the string data is not of the required
+ * format
+ * @throws RepositoryException if an error occured during the
+ * deserialization.
+ */
+ public static Value deserialize(Reader reader, int type,
+ boolean decodeBlanks, ValueFactory factory)
+ throws IOException, ValueFormatException, RepositoryException {
if (type == PropertyType.BINARY) {
// base64 encoded binary value;
// the encodeBlanks flag can be ignored since base64-encoded
@@ -546,8 +751,8 @@
// create an InputStream that keeps a hard reference to the temp file
// in order to prevent its automatic deletion once the associated
// File object is reclaimed by the garbage collector;
- // pass InputStream wrapper to BinaryValue constructor
- return new BinaryValue(new FilterInputStream(new FileInputStream(tmpFile)) {
+ // pass InputStream wrapper to ValueFactory, that creates a BinaryValue.
+ return factory.createValue(new FilterInputStream(new FileInputStream(tmpFile)) {
public void close() throws IOException {
in.close();
@@ -574,7 +779,7 @@
// decode encoded blanks in value
value = Text.replace(value, "_x0020_", " ");
}
- return convert(value, type);
+ return convert(value, type, factory);
}
}
}
Index: src/test/java/org/apache/jackrabbit/name/PathTest.java
===================================================================
--- src/test/java/org/apache/jackrabbit/name/PathTest.java (revision 417443)
+++ src/test/java/org/apache/jackrabbit/name/PathTest.java (working copy)
@@ -19,6 +19,8 @@
import junit.framework.TestCase;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Iterator;
import org.apache.jackrabbit.util.Text;
@@ -273,6 +275,110 @@
return normalize ? builder.getPath().getNormalizedPath() : builder.getPath();
}
+ public void testNormalizedPaths() throws Exception {
+ List paths = new ArrayList();
+
+ // normalized paths
+ paths.add(PathFormat.parse("/", resolver));
+ paths.add(PathFormat.parse("/foo", resolver));
+ paths.add(PathFormat.parse("/foo/bar", resolver));
+ paths.add(PathFormat.parse("foo/bar", resolver));
+ paths.add(PathFormat.parse("foo", resolver));
+ paths.add(PathFormat.parse("../../foo/bar", resolver));
+ paths.add(PathFormat.parse("..", resolver));
+
+ for (Iterator it = paths.iterator(); it.hasNext(); ) {
+ Path path = (Path) it.next();
+ assertTrue("path is not normalized: " + PathFormat.format(path, resolver), path.isNormalized());
+ }
+
+ paths.clear();
+
+ // not normalized paths
+ paths.add(PathFormat.parse("/foo/..", resolver));
+ paths.add(PathFormat.parse("/foo/.", resolver));
+ paths.add(PathFormat.parse("/foo/../bar", resolver));
+ paths.add(PathFormat.parse("/foo/./bar", resolver));
+ paths.add(PathFormat.parse("./foo", resolver));
+ paths.add(PathFormat.parse(".", resolver));
+ paths.add(PathFormat.parse("foo/..", resolver));
+ paths.add(PathFormat.parse("../foo/..", resolver));
+ paths.add(PathFormat.parse("../foo/.", resolver));
+
+ for (Iterator it = paths.iterator(); it.hasNext(); ) {
+ Path path = (Path) it.next();
+ assertFalse("path is normalized: " + PathFormat.format(path, resolver), path.isNormalized());
+ }
+ }
+
+ public void testAbsolutePaths() throws Exception {
+ List paths = new ArrayList();
+
+ // absolute paths
+ paths.add(PathFormat.parse("/", resolver));
+ paths.add(PathFormat.parse("/foo", resolver));
+ paths.add(PathFormat.parse("/foo/bar", resolver));
+ paths.add(PathFormat.parse("/foo/../bar", resolver));
+ paths.add(PathFormat.parse("/foo/..", resolver));
+ paths.add(PathFormat.parse("/foo/./bar", resolver));
+ paths.add(PathFormat.parse("/foo/.././bar/./foo", resolver));
+
+ for (Iterator it = paths.iterator(); it.hasNext(); ) {
+ Path path = (Path) it.next();
+ assertTrue("path is not absolute: " + PathFormat.format(path, resolver), path.isAbsolute());
+ }
+
+ paths.clear();
+
+ // not absoulute paths
+ paths.add(PathFormat.parse("foo/..", resolver));
+ paths.add(PathFormat.parse("foo/.", resolver));
+ paths.add(PathFormat.parse("foo/../bar", resolver));
+ paths.add(PathFormat.parse("foo/./bar", resolver));
+ paths.add(PathFormat.parse("./foo", resolver));
+ paths.add(PathFormat.parse(".", resolver));
+ paths.add(PathFormat.parse("foo/..", resolver));
+ paths.add(PathFormat.parse("../foo/..", resolver));
+ paths.add(PathFormat.parse("../foo/.", resolver));
+
+ for (Iterator it = paths.iterator(); it.hasNext(); ) {
+ Path path = (Path) it.next();
+ assertFalse("path is absolute: " + PathFormat.format(path, resolver), path.isAbsolute());
+ }
+ }
+
+ public void testCanonicalPaths() throws Exception {
+ List paths = new ArrayList();
+
+ // canonical paths
+ paths.add(PathFormat.parse("/", resolver));
+ paths.add(PathFormat.parse("/foo", resolver));
+ paths.add(PathFormat.parse("/foo/bar", resolver));
+
+ for (Iterator it = paths.iterator(); it.hasNext(); ) {
+ Path path = (Path) it.next();
+ assertTrue("path is not canonical: " + PathFormat.format(path, resolver), path.isCanonical());
+ }
+
+ paths.clear();
+
+ // not canonical paths
+ paths.add(PathFormat.parse("/foo/..", resolver));
+ paths.add(PathFormat.parse("/foo/.", resolver));
+ paths.add(PathFormat.parse("/foo/../bar", resolver));
+ paths.add(PathFormat.parse("/foo/./bar", resolver));
+ paths.add(PathFormat.parse("./foo", resolver));
+ paths.add(PathFormat.parse(".", resolver));
+ paths.add(PathFormat.parse("/foo/..", resolver));
+ paths.add(PathFormat.parse("/../foo/..", resolver));
+ paths.add(PathFormat.parse("/../foo/.", resolver));
+
+ for (Iterator it = paths.iterator(); it.hasNext(); ) {
+ Path path = (Path) it.next();
+ assertFalse("path is canonical: " + PathFormat.format(path, resolver), path.isCanonical());
+ }
+ }
+
private static class Test {
private final String path;