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/nodetype/compact/CompactNodeTypeDefReader.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/nodetype/compact/CompactNodeTypeDefReader.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/nodetype/compact/CompactNodeTypeDefReader.java (working copy) @@ -35,6 +35,7 @@ import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.ValueFormatException; +import javax.jcr.ValueFactory; import javax.jcr.version.OnParentVersionAction; import java.io.Reader; import java.util.ArrayList; @@ -132,6 +133,12 @@ private NamespaceMapping nsMapping; /** + * the ValueFactory used to create qualified values from + * JCR values. + */ + private ValueFactory valueFactory; + + /** * the underlying lexer */ private Lexer lexer; @@ -145,10 +152,11 @@ * Creates a new CND reader. * * @param r + * @param valueFactory * @throws ParseException */ - public CompactNodeTypeDefReader(Reader r, String systemId) throws ParseException { - this(r, systemId, new NamespaceMapping()); + public CompactNodeTypeDefReader(Reader r, String systemId, ValueFactory valueFactory) throws ParseException { + this(r, systemId, new NamespaceMapping(), valueFactory); } @@ -156,12 +164,16 @@ * Creates a new CND reader. * * @param r + * @param valueFactory * @throws ParseException */ - public CompactNodeTypeDefReader(Reader r, String systemId, NamespaceMapping mapping) + public CompactNodeTypeDefReader(Reader r, String systemId, + NamespaceMapping mapping, + ValueFactory valueFactory) throws ParseException { lexer = new Lexer(r, systemId); this.nsMapping = mapping; + this.valueFactory = valueFactory; nextToken(); parse(); } @@ -484,7 +496,7 @@ nextToken(); InternalValue value = null; try { - value = InternalValue.create(currentToken, pdi.getRequiredType(), nsMapping); + value = InternalValue.create(currentToken, pdi.getRequiredType(), nsMapping, valueFactory); } catch (ValueFormatException e) { lexer.fail("'" + currentToken + "' is not a valid string representation of a value of type " + pdi.getRequiredType()); } catch (RepositoryException e) { Index: src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeDefStore.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeDefStore.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeDefStore.java (working copy) @@ -19,6 +19,7 @@ import org.apache.jackrabbit.core.nodetype.xml.NodeTypeReader; import org.apache.jackrabbit.core.nodetype.xml.NodeTypeWriter; import org.apache.jackrabbit.name.QName; +import org.apache.jackrabbit.value.ValueFactoryImpl; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; @@ -52,7 +53,13 @@ public void load(InputStream in) throws IOException, InvalidNodeTypeDefException, RepositoryException { - NodeTypeDef[] types = NodeTypeReader.read(in); + /* TODO: improve. instead of accessing a specific ValueFactory instance + the factory should be provided by the NodeTypeRegistry (and thus + by the Repository). this has been avoided in order not to modify + the RepositoryImpl. the result obtained with the given solution is + identical to the original code, where Value instances were created + by the ValueHelper (which in turn is used by InternalValue). */ + NodeTypeDef[] types = NodeTypeReader.read(in, ValueFactoryImpl.getInstance()); for (int i = 0; i < types.length; i++) { add(types[i]); } Index: src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeImpl.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeImpl.java (working copy) @@ -29,6 +29,7 @@ import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; +import javax.jcr.ValueFactory; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; @@ -49,6 +50,8 @@ private final NodeTypeManagerImpl ntMgr; // namespace resolver used to translate qualified names to JCR names private final NamespaceResolver nsResolver; + // value factory used to to translate qualified names to JCR names + private final ValueFactory valueFactory; /** * Package private constructor @@ -63,11 +66,13 @@ * @param nsResolver namespace resolver */ NodeTypeImpl(EffectiveNodeType ent, NodeTypeDef ntd, - NodeTypeManagerImpl ntMgr, NamespaceResolver nsResolver) { + NodeTypeManagerImpl ntMgr, NamespaceResolver nsResolver, + ValueFactory valueFactory) { this.ent = ent; this.ntMgr = ntMgr; this.nsResolver = nsResolver; this.ntd = ntd; + this.valueFactory = valueFactory; } /** @@ -378,7 +383,7 @@ // create InternalValue from Value and perform // type conversion as necessary InternalValue internalValue = InternalValue.create(value, targetType, - nsResolver); + nsResolver, valueFactory); EffectiveNodeType.checkSetPropertyValueConstraints( def, new InternalValue[]{internalValue}); return true; @@ -448,7 +453,7 @@ // type conversion as necessary InternalValue internalValue = InternalValue.create(values[i], targetType, - nsResolver); + nsResolver, valueFactory); list.add(internalValue); } } Index: src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeManagerImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeManagerImpl.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeManagerImpl.java (working copy) @@ -36,6 +36,7 @@ import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; +import javax.jcr.ValueFactory; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.NodeTypeIterator; @@ -90,6 +91,12 @@ private final NamespaceResolver nsResolver; /** + * The ValueFactory used to parse JCR values of a given type + * into qualified values. + */ + private final ValueFactory valueFactory; + + /** * A cache for NodeType instances created by this * NodeTypeManager */ @@ -115,11 +122,12 @@ */ public NodeTypeManagerImpl( NodeTypeRegistry ntReg, NamespaceRegistryImpl nsReg, - NamespaceResolver nsResolver) { + NamespaceResolver nsResolver, ValueFactory valueFactory) { this.nsResolver = nsResolver; this.ntReg = ntReg; this.nsReg = nsReg; this.ntReg.addListener(this); + this.valueFactory = valueFactory; // setup caches with soft references to node type // & item definition instances @@ -186,7 +194,7 @@ if (nt == null) { EffectiveNodeType ent = ntReg.getEffectiveNodeType(name); NodeTypeDef def = ntReg.getNodeTypeDef(name); - nt = new NodeTypeImpl(ent, def, this, nsResolver); + nt = new NodeTypeImpl(ent, def, this, nsResolver, valueFactory); ntCache.put(name, nt); } return nt; @@ -353,7 +361,7 @@ public NodeType[] registerNodeTypes(InputSource in) throws SAXException, RepositoryException { try { - NodeTypeReader ntr = new NodeTypeReader(in.getByteStream()); + NodeTypeReader ntr = new NodeTypeReader(in.getByteStream(), valueFactory); Properties namespaces = ntr.getNamespaces(); if (namespaces != null) { @@ -397,7 +405,7 @@ } else if (contentType.equalsIgnoreCase(TEXT_X_JCR_CND)) { NamespaceMapping mapping = new NamespaceMapping(nsResolver); CompactNodeTypeDefReader reader = new CompactNodeTypeDefReader( - new InputStreamReader(in), "cnd input stream", mapping); + new InputStreamReader(in), "cnd input stream", mapping, valueFactory); Map nsMap = mapping.getPrefixToURIMapping(); Iterator iterator = nsMap.entrySet().iterator(); Index: src/main/java/org/apache/jackrabbit/core/nodetype/xml/NodeTypeReader.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/nodetype/xml/NodeTypeReader.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/nodetype/xml/NodeTypeReader.java (working copy) @@ -34,6 +34,7 @@ import javax.jcr.PropertyType; import javax.jcr.RepositoryException; +import javax.jcr.ValueFactory; import javax.jcr.version.OnParentVersionAction; import java.io.IOException; import java.io.InputStream; @@ -52,16 +53,18 @@ * returned. * * @param xml XML input stream + * @param valueFactory used for conversion of JCR values read from the + * definition. * @return node type definitions * @throws IOException if the node type definitions * cannot be read * @throws InvalidNodeTypeDefException if the node type definition * format is invalid */ - public static NodeTypeDef[] read(InputStream xml) + public static NodeTypeDef[] read(InputStream xml, ValueFactory valueFactory) throws IOException, InvalidNodeTypeDefException { try { - NodeTypeReader reader = new NodeTypeReader(xml); + NodeTypeReader reader = new NodeTypeReader(xml, valueFactory); return reader.getNodeTypeDefs(); } catch (IllegalNameException e) { throw new InvalidNodeTypeDefException( @@ -81,16 +84,22 @@ /** The namespace resolver. */ private final NamespaceResolver resolver; + /** The ValueFactory for value conversion (between types) */ + private final ValueFactory valueFactory; + /** * Creates a node type definition file reader. * * @param xml node type definition file + * @param valueFactory used for conversion of JCR values read from the + * definition. * @throws IOException if the node type definition file cannot be read */ - public NodeTypeReader(InputStream xml) throws IOException { + public NodeTypeReader(InputStream xml, ValueFactory valueFactory) throws IOException { walker = new DOMWalker(xml); namespaces = walker.getNamespaces(); resolver = new AdditionalNamespaceResolver(namespaces); + this.valueFactory = valueFactory; } /** @@ -255,7 +264,7 @@ while (walker.iterateElements(Constants.DEFAULTVALUE_ELEMENT)) { String value = walker.getContent(); try { - values.add(InternalValue.create(value, type, resolver)); + values.add(InternalValue.create(value, type, resolver, valueFactory)); } catch (RepositoryException e) { throw new InvalidNodeTypeDefException( "Unable to create default value: " + value, e); Index: src/main/java/org/apache/jackrabbit/core/PropertyImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/PropertyImpl.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/PropertyImpl.java (working copy) @@ -349,7 +349,7 @@ // type conversion required internalValue = InternalValue.create(InternalValue.create(name).toJCRValue(session.getNamespaceResolver()), - reqType, session.getNamespaceResolver()); + reqType, session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required internalValue = InternalValue.create(name); @@ -398,7 +398,7 @@ // type conversion required internalValue = InternalValue.create(InternalValue.create(name).toJCRValue(session.getNamespaceResolver()), - reqType, session.getNamespaceResolver()); + reqType, session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required internalValue = InternalValue.create(name); @@ -686,7 +686,8 @@ InternalValue value; if (reqType != PropertyType.DATE) { // type conversion required - value = InternalValue.create(new DateValue(date), reqType, session.getNamespaceResolver()); + value = InternalValue.create(new DateValue(date), reqType, + session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required value = InternalValue.create(date); @@ -717,7 +718,8 @@ InternalValue value; if (reqType != PropertyType.DOUBLE) { // type conversion required - value = InternalValue.create(new DoubleValue(number), reqType, session.getNamespaceResolver()); + value = InternalValue.create(new DoubleValue(number), reqType, + session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required value = InternalValue.create(number); @@ -754,7 +756,8 @@ try { if (reqType != PropertyType.BINARY) { // type conversion required - value = InternalValue.create(new BLOBFileValue(stream), reqType, session.getNamespaceResolver()); + value = InternalValue.create(new BLOBFileValue(stream), reqType, + session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required value = InternalValue.create(stream); @@ -795,7 +798,8 @@ InternalValue internalValue; if (reqType != PropertyType.STRING) { // type conversion required - internalValue = InternalValue.create(string, reqType, session.getNamespaceResolver()); + internalValue = InternalValue.create(string, reqType, + session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required internalValue = InternalValue.create(string); @@ -832,7 +836,8 @@ if (string != null) { if (reqType != PropertyType.STRING) { // type conversion required - internalValue = InternalValue.create(string, reqType, session.getNamespaceResolver()); + internalValue = InternalValue.create(string, reqType, + session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required internalValue = InternalValue.create(string); @@ -867,7 +872,8 @@ InternalValue value; if (reqType != PropertyType.BOOLEAN) { // type conversion required - value = InternalValue.create(new BooleanValue(b), reqType, session.getNamespaceResolver()); + value = InternalValue.create(new BooleanValue(b), reqType, + session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required value = InternalValue.create(b); @@ -940,7 +946,8 @@ InternalValue value; if (reqType != PropertyType.LONG) { // type conversion required - value = InternalValue.create(new LongValue(number), reqType, session.getNamespaceResolver()); + value = InternalValue.create(new LongValue(number), reqType, + session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required value = InternalValue.create(number); @@ -980,7 +987,8 @@ InternalValue internalValue; if (reqType != value.getType()) { // type conversion required - internalValue = InternalValue.create(value, reqType, session.getNamespaceResolver()); + internalValue = InternalValue.create(value, reqType, + session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required internalValue = InternalValue.create(value, session.getNamespaceResolver()); @@ -1038,7 +1046,8 @@ if (reqType != PropertyType.UNDEFINED && reqType != value.getType()) { // type conversion required - internalValue = InternalValue.create(value, reqType, session.getNamespaceResolver()); + internalValue = InternalValue.create(value, reqType, + session.getNamespaceResolver(), session.getValueFactory()); } else { // no type conversion required internalValue = InternalValue.create(value, session.getNamespaceResolver()); 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) @@ -237,7 +237,7 @@ } this.subject = subject; nsMappings = new LocalNamespaceMappings(rep.getNamespaceRegistry()); - ntMgr = new NodeTypeManagerImpl(rep.getNodeTypeRegistry(), rep.getNamespaceRegistry(), getNamespaceResolver()); + ntMgr = new NodeTypeManagerImpl(rep.getNodeTypeRegistry(), rep.getNamespaceRegistry(), getNamespaceResolver(), getValueFactory()); String wspName = wspConfig.getName(); wsp = createWorkspaceInstance(wspConfig, rep.getWorkspaceStateManager(wspName), rep, this); @@ -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/value/InternalValue.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/value/InternalValue.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/value/InternalValue.java (working copy) @@ -41,6 +41,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; +import javax.jcr.ValueFactory; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -151,12 +152,14 @@ * @throws RepositoryException */ public static InternalValue create(Value value, int targetType, - NamespaceResolver nsResolver) + NamespaceResolver nsResolver, + ValueFactory valueFactory) throws ValueFormatException, RepositoryException { if (value == null) { throw new IllegalArgumentException("null value"); } - return create(ValueHelper.convert(value, targetType), nsResolver); + Value targetValue = ValueHelper.convert(value, targetType, valueFactory); + return create(targetValue, nsResolver); } /** @@ -168,12 +171,14 @@ * @throws RepositoryException */ public static InternalValue create(String value, int targetType, - NamespaceResolver nsResolver) + NamespaceResolver nsResolver, + ValueFactory valueFactory) throws ValueFormatException, RepositoryException { if (value == null) { throw new IllegalArgumentException("null value"); } - return create(ValueHelper.convert(value, targetType), nsResolver); + Value targetValue = ValueHelper.convert(value, targetType, valueFactory); + return create(targetValue, nsResolver); } /** Index: src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java (working copy) @@ -30,6 +30,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; +import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.name.NamespaceResolver; @@ -226,7 +227,8 @@ //--------------------------------------------------------< TextValue > - public Value getValue(int targetType, NamespaceResolver resolver) + public Value getValue(int targetType, NamespaceResolver resolver, + ValueFactory valueFactory) throws ValueFormatException, RepositoryException { try { if (targetType == PropertyType.NAME @@ -238,26 +240,26 @@ // convert serialized value to InternalValue using // current namespace context of xml document InternalValue ival = - InternalValue.create(retrieve(), targetType, nsContext); + InternalValue.create(retrieve(), targetType, nsContext, valueFactory); // convert InternalValue to Value using this // session's namespace mappings return ival.toJCRValue(resolver); } else if (targetType == PropertyType.BINARY) { if (length() < 0x10000) { // < 65kb: deserialize BINARY type using String - return ValueHelper.deserialize(retrieve(), targetType, false); + return ValueHelper.deserialize(retrieve(), targetType, false, valueFactory); } else { // >= 65kb: deserialize BINARY type using Reader Reader reader = reader(); try { - return ValueHelper.deserialize(reader, targetType, false); + return ValueHelper.deserialize(reader, targetType, false, valueFactory); } finally { reader.close(); } } } else { // all other types - return ValueHelper.deserialize(retrieve(), targetType, true); + return ValueHelper.deserialize(retrieve(), targetType, true, valueFactory); } } catch (IOException e) { String msg = "failed to retrieve serialized value"; @@ -266,7 +268,7 @@ } } - public InternalValue getInternalValue(int type) + public InternalValue getInternalValue(int type, ValueFactory valueFactory) throws ValueFormatException, RepositoryException { try { if (type == PropertyType.BINARY) { @@ -297,7 +299,7 @@ } else { // convert serialized value to InternalValue using // current namespace context of xml document - return InternalValue.create(retrieve(), type, nsContext); + return InternalValue.create(retrieve(), type, nsContext, valueFactory); } } catch (IOException e) { throw new RepositoryException("Error accessing property value", e); 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/PropInfo.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/xml/PropInfo.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/xml/PropInfo.java (working copy) @@ -22,6 +22,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; +import javax.jcr.ValueFactory; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.version.VersionException; @@ -88,7 +89,9 @@ } } - public void apply(NodeImpl node, NamespaceResolver resolver, ReferenceChangeTracker refTracker) throws RepositoryException, ConstraintViolationException, ValueFormatException, VersionException, LockException, ItemNotFoundException { + public void apply(NodeImpl node, NamespaceResolver resolver, + ReferenceChangeTracker refTracker, ValueFactory valueFactory) + throws RepositoryException, ConstraintViolationException, ValueFormatException, VersionException, LockException, ItemNotFoundException { // find applicable definition PropDef def = getApplicablePropertyDef(node.getEffectiveNodeType()); if (def.isProtected()) { @@ -101,7 +104,7 @@ Value[] va = new Value[values.length]; int targetType = getTargetType(def); for (int i = 0; i < values.length; i++) { - va[i] = values[i].getValue(targetType, resolver); + va[i] = values[i].getValue(targetType, resolver, valueFactory); } // multi- or single-valued property? @@ -127,7 +130,9 @@ } } - public void apply(NodeState node, BatchedItemOperations itemOps, NodeTypeRegistry ntReg, ReferenceChangeTracker refTracker) throws ItemNotFoundException, RepositoryException, ItemExistsException, ConstraintViolationException, ValueFormatException { + public void apply(NodeState node, BatchedItemOperations itemOps, + NodeTypeRegistry ntReg, ReferenceChangeTracker refTracker, + ValueFactory valueFactory) throws ItemNotFoundException, RepositoryException, ItemExistsException, ConstraintViolationException, ValueFormatException { PropertyState prop = null; PropDef def = null; @@ -171,7 +176,7 @@ int targetType = getTargetType(def); InternalValue[] iva = new InternalValue[values.length]; for (int i = 0; i < values.length; i++) { - iva[i] = values[i].getInternalValue(targetType); + iva[i] = values[i].getInternalValue(targetType, valueFactory); } // set values Index: src/main/java/org/apache/jackrabbit/core/xml/SessionImporter.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/xml/SessionImporter.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/xml/SessionImporter.java (working copy) @@ -259,7 +259,7 @@ Iterator iter = propInfos.iterator(); while (iter.hasNext()) { PropInfo pi = (PropInfo) iter.next(); - pi.apply(node, session.getNamespaceResolver(), refTracker); + pi.apply(node, session.getNamespaceResolver(), refTracker, session.getValueFactory()); } parents.push(node); 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,13 +18,12 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; +import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.name.NamespaceResolver; @@ -53,7 +52,7 @@ //--------------------------------------------------------< TextValue > - public Value getValue(int type, NamespaceResolver resolver) + public Value getValue(int type, NamespaceResolver resolver, ValueFactory valueFactory) throws ValueFormatException, RepositoryException { if (type == PropertyType.NAME || type == PropertyType.PATH) { // NAME and PATH require special treatment because @@ -62,19 +61,19 @@ // convert serialized value to InternalValue using // current namespace context of xml document - InternalValue ival = InternalValue.create(value, type, nsContext); + InternalValue ival = InternalValue.create(value, type, nsContext, valueFactory); // convert InternalValue to Value using this // session's namespace mappings return ival.toJCRValue(resolver); } else if (type == PropertyType.BINARY) { - return ValueHelper.deserialize(value, type, false); + return ValueHelper.deserialize(value, type, false, valueFactory); } else { // all other types - return ValueHelper.deserialize(value, type, true); + return ValueHelper.deserialize(value, type, true, valueFactory); } } - public InternalValue getInternalValue(int targetType) + public InternalValue getInternalValue(int targetType, ValueFactory valueFactory) throws ValueFormatException, RepositoryException { try { if (targetType == PropertyType.BINARY) { @@ -86,7 +85,7 @@ } else { // convert serialized value to InternalValue using // current namespace context of xml document - return InternalValue.create(value, targetType, nsContext); + return InternalValue.create(value, targetType, nsContext, valueFactory); } } catch (IOException e) { throw new RepositoryException("Error decoding Base64 content", e); Index: src/main/java/org/apache/jackrabbit/core/xml/TextValue.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/xml/TextValue.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/xml/TextValue.java (working copy) @@ -19,6 +19,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; +import javax.jcr.ValueFactory; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.name.NamespaceResolver; @@ -29,10 +30,10 @@ */ public interface TextValue { - Value getValue(int type, NamespaceResolver resolver) + Value getValue(int type, NamespaceResolver resolver, ValueFactory valueFactory) throws ValueFormatException, RepositoryException; - InternalValue getInternalValue(int type) + InternalValue getInternalValue(int type, ValueFactory valueFactory) throws ValueFormatException, RepositoryException; /** Index: src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java (revision 417443) +++ src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java (working copy) @@ -493,7 +493,7 @@ Iterator iter = propInfos.iterator(); while (iter.hasNext()) { PropInfo pi = (PropInfo) iter.next(); - pi.apply(node, itemOps, ntReg, refTracker); + pi.apply(node, itemOps, ntReg, refTracker, wsp.getSession().getValueFactory()); } // store affected nodes 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: + *

+ */ + private static final Pattern NAME_PATTERN = Pattern.compile( + "(([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?):)?" + + "([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?)"); + + /** + * Matcher instance as thread-local. + */ + private static final ThreadLocal NAME_MATCHER = new ThreadLocal() { + protected Object initialValue() { + return NAME_PATTERN.matcher("dummy"); + } + }; + + /** + * Parses the 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:

- *

+ * Constant representing an undefined index value */ - private static final Pattern PATH_ELEMENT_PATTERN = - Pattern.compile("(\\.)|" - + "(\\.\\.)|" - + "(([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?):)?" - + "([^ /:\\[\\]*'\"|](?:[^/:\\[\\]*'\"|]*[^ /:\\[\\]*'\"|])?)" - + "(\\[([1-9]\\d*)\\])?"); + public static final int INDEX_UNDEFINED = 0; /** - * Matcher instance as thread-local. + * Constant representing the default (initial) index value. */ - private static final ThreadLocal PATH_ELEMENT_MATCHER = new ThreadLocal() { - protected Object initialValue() { - return PATH_ELEMENT_PATTERN.matcher("dummy"); - } - }; + public static final int INDEX_DEFAULT = 1; /** + * Constant defining the depth of the root path + */ + public static final int ROOT_DEPTH = 0; + + /** * the elements of this path */ private final PathElement[] elements; @@ -177,6 +160,18 @@ //------------------------------------------------------< factory methods > /** + * @param elements + * @return + */ + public static Path create(PathElement[] elements) { + // Path constructor uses elements array as is + // need to copy here because Path.create() is public + PathElement[] tmp = new PathElement[elements.length]; + System.arraycopy(elements, 0, tmp, 0, elements.length); + return new Path(tmp, true); + } + + /** * Creates a new 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:

+ *

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

- */ - 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/core/nodetype/compact/CompactNodeTypeDefTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/nodetype/compact/CompactNodeTypeDefTest.java (revision 417443) +++ src/test/java/org/apache/jackrabbit/core/nodetype/compact/CompactNodeTypeDefTest.java (working copy) @@ -27,8 +27,10 @@ import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.name.QName; import org.apache.jackrabbit.util.name.NamespaceMapping; +import org.apache.jackrabbit.value.ValueFactoryImpl; import javax.jcr.PropertyType; +import javax.jcr.ValueFactory; import javax.jcr.version.OnParentVersionAction; import java.io.FileReader; import java.io.StringReader; @@ -55,6 +57,8 @@ private static final QName REQUIRED_NODE_TYPE_2 = new QName(NS_URI, "RequiredNodeType2"); private static final QName[] REQUIRED_NODE_TYPES = new QName[]{REQUIRED_NODE_TYPE_1, REQUIRED_NODE_TYPE_2}; + private static final ValueFactory VALUE_FACTORY = ValueFactoryImpl.getInstance(); + private NodeTypeDef modelNodeTypeDef; protected void setUp() throws Exception { @@ -102,7 +106,7 @@ public void testCompactNodeTypeDef() throws Exception { // Read in node type def from test file - CompactNodeTypeDefReader cndReader = new CompactNodeTypeDefReader(new FileReader(TEST_FILE), TEST_FILE); + CompactNodeTypeDefReader cndReader = new CompactNodeTypeDefReader(new FileReader(TEST_FILE), TEST_FILE, VALUE_FACTORY); List ntdList = cndReader.getNodeTypeDefs(); NamespaceMapping nsm = cndReader.getNamespaceMapping(); NodeTypeDef ntd = (NodeTypeDef)ntdList.get(0); @@ -118,7 +122,7 @@ CompactNodeTypeDefWriter.write(ntdList, nsm, sw); // Rerun the reader on the product of the writer - cndReader = new CompactNodeTypeDefReader(new StringReader(sw.toString()), TEST_FILE); + cndReader = new CompactNodeTypeDefReader(new StringReader(sw.toString()), TEST_FILE, VALUE_FACTORY); ntdList = cndReader.getNodeTypeDefs(); ntd = (NodeTypeDef)ntdList.get(0); Index: src/test/java/org/apache/jackrabbit/core/nodetype/converter/SchemaConverterTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/nodetype/converter/SchemaConverterTest.java (revision 417443) +++ src/test/java/org/apache/jackrabbit/core/nodetype/converter/SchemaConverterTest.java (working copy) @@ -22,6 +22,7 @@ import org.apache.jackrabbit.util.name.NamespaceExtractor; import org.apache.jackrabbit.core.nodetype.NodeTypeDef; import org.apache.jackrabbit.name.NamespaceResolver; +import org.apache.jackrabbit.value.ValueFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,7 +58,7 @@ SchemaConverter nts = new SchemaConverter(TEST_INPUT_FILE); List testList = nts.getNodeTypeDefs(); - CompactNodeTypeDefReader ntr = new CompactNodeTypeDefReader(new FileReader(MODEL_RESULT_FILE), MODEL_RESULT_FILE, nsm); + CompactNodeTypeDefReader ntr = new CompactNodeTypeDefReader(new FileReader(MODEL_RESULT_FILE), MODEL_RESULT_FILE, nsm, ValueFactoryImpl.getInstance()); List modelList = ntr.getNodeTypeDefs(); TreeMap orderedTestList = new TreeMap(); Index: src/test/java/org/apache/jackrabbit/core/nodetype/xml/TestAll.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/nodetype/xml/TestAll.java (revision 417443) +++ src/test/java/org/apache/jackrabbit/core/nodetype/xml/TestAll.java (working copy) @@ -28,6 +28,7 @@ import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.name.NamespaceResolver; import org.apache.jackrabbit.name.QName; +import org.apache.jackrabbit.value.ValueFactoryImpl; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; @@ -76,7 +77,7 @@ InputStream xml = getClass().getClassLoader().getResourceAsStream(TEST_NODETYPES); - types = NodeTypeReader.read(xml); + types = NodeTypeReader.read(xml, ValueFactoryImpl.getInstance()); registry = new SimpleNamespaceRegistry(); registry.registerNamespace("test", TEST_NAMESPACE); @@ -569,7 +570,7 @@ NodeTypeWriter.write(xml, types, registry); byte[] bytes = xml.toByteArray(); NodeTypeDef[] output = - NodeTypeReader.read(new ByteArrayInputStream(bytes)); + NodeTypeReader.read(new ByteArrayInputStream(bytes), ValueFactoryImpl.getInstance()); assertTrue("write output", Arrays.equals(types, output)); } catch (InvalidNodeTypeDefException e) { fail(e.getMessage()); 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;