001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors.
008     *
009     * Unless otherwise indicated, all code in JBoss DNA is licensed
010     * to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     * 
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.jcr;
025    
026    import java.io.Serializable;
027    import java.util.HashMap;
028    import net.jcip.annotations.Immutable;
029    import org.jboss.dna.graph.property.Name;
030    import org.jboss.dna.graph.property.NameFactory;
031    import org.jboss.dna.graph.property.ValueFormatException;
032    
033    /**
034     * An immutable identifier for a node definition. Although instances can be serialized, the node definitions are often stored
035     * within the graph as {@link #getString() string values} on a property. These string values can later be
036     * {@link #fromString(String, NameFactory) parsed} to reconstruct the identifier. Note that this string representation does not
037     * use namespace prefixes, so they are long-lasting and durable.
038     * <p>
039     * What distinguishes one property definition from another is not well documented in the JSR-170 specification. The closest this
040     * version of the spec gets is Section 6.7.15, but that merely says that more than one property definition can have the same name.
041     * The proposed draft of the JSR-283 specification does clarify this more: Section 4.7.15 says :
042     * </p>
043     * <p>
044     * <quote>"Similarly, a node type may have two or more child node definitions with identical name attributes as long as they are
045     * distinguishable by the required primary types attribute (the value returned by
046     * NodeDefinition.getRequiredPrimaryTypes)."</quote>
047     * </p>
048     * <p>
049     * This class is {@link Serializable} and designed to be used as a key in a {@link HashMap}.
050     * </p>
051     */
052    @Immutable
053    public final class NodeDefinitionId implements Serializable {
054    
055        /**
056         * Current version is {@value} .
057         */
058        private static final long serialVersionUID = 1L;
059    
060        /**
061         * The string-form of the name that can be used to represent a residual property definition.
062         */
063        public static final String ANY_NAME = JcrNodeType.RESIDUAL_ITEM_NAME;
064    
065        private final Name nodeTypeName;
066        private final Name childDefinitionName;
067        private final Name[] requiredPrimaryTypes;
068        /**
069         * A cached string representation, which is used for {@link #equals(Object)} and {@link #hashCode()} among other things.
070         */
071        private final String stringRepresentation;
072    
073        /**
074         * Create an identifier for a node definition.
075         * 
076         * @param nodeTypeName the name of the node type on which this child node definition is defined; may not be null
077         * @param childDefinitionName the name of the child node definition, which may be a {@link #ANY_NAME residual child
078         *        definition}; may not be null
079         * @param requiredPrimaryTypes the names of the required primary types for the child node definition
080         */
081        public NodeDefinitionId( Name nodeTypeName,
082                                 Name childDefinitionName,
083                                 Name[] requiredPrimaryTypes ) {
084            assert nodeTypeName != null;
085            assert childDefinitionName != null;
086            this.nodeTypeName = nodeTypeName;
087            this.childDefinitionName = childDefinitionName;
088            this.requiredPrimaryTypes = requiredPrimaryTypes;
089            StringBuilder sb = new StringBuilder(this.nodeTypeName.getString());
090            sb.append('/').append(this.childDefinitionName.getString());
091            for (Name requiredPrimaryType : requiredPrimaryTypes) {
092                sb.append('/');
093                sb.append(requiredPrimaryType.getString());
094            }
095            this.stringRepresentation = sb.toString();
096        }
097    
098        /**
099         * Get the name of the node type on which the child node definition is defined.
100         * 
101         * @return the node type's name; never null
102         */
103        public Name getNodeTypeName() {
104            return nodeTypeName;
105        }
106    
107        /**
108         * Get the name of the child definition.
109         * 
110         * @return the child definition's name; never null
111         */
112        public Name getChildDefinitionName() {
113            return childDefinitionName;
114        }
115    
116        /**
117         * @return requiredPrimaryTypes
118         */
119        public Name[] getRequiredPrimaryTypes() {
120            Name[] copy = new Name[requiredPrimaryTypes.length];
121            System.arraycopy(requiredPrimaryTypes, 0, copy, 0, requiredPrimaryTypes.length);
122            return copy;
123        }
124    
125        /**
126         * Determine whether this node definition defines any named child.
127         * 
128         * @return true if this node definition allows children with any name, or false if this definition requires a particular child
129         *         name
130         */
131        public boolean allowsAnyChildName() {
132            return childDefinitionName.getLocalName().equals(ANY_NAME) && childDefinitionName.getNamespaceUri().length() == 0;
133        }
134    
135        /**
136         * Get the string form of this identifier. This form can be persisted, since it does not rely upon namespace prefixes.
137         * 
138         * @return the string form
139         */
140        public String getString() {
141            return this.stringRepresentation;
142        }
143    
144        /**
145         * Parse the supplied string for of an identifer, and return the object form for that identifier.
146         * 
147         * @param definition the {@link #getString() string form of the identifier}; may not be null
148         * @param factory the factory that should be used to create Name objects; may not be null
149         * @return the object form of the identifier; never null
150         * @throws ValueFormatException if the definition is not the valid format
151         */
152        public static NodeDefinitionId fromString( String definition,
153                                                   NameFactory factory ) {
154            String[] parts = definition.split("/");
155            String nodeTypeNameString = parts[0];
156            String childDefinitionNameString = parts[1];
157            Name[] requiredPrimaryTypes = new Name[parts.length - 2];
158            for (int i = 2, j = 0; i != parts.length; ++i, ++j) {
159                requiredPrimaryTypes[j] = factory.create(parts[i]);
160            }
161            Name nodeTypeName = factory.create(nodeTypeNameString);
162            Name childDefinitionName = factory.create(childDefinitionNameString);
163            return new NodeDefinitionId(nodeTypeName, childDefinitionName, requiredPrimaryTypes);
164        }
165    
166        /**
167         * {@inheritDoc}
168         * 
169         * @see java.lang.Object#hashCode()
170         */
171        @Override
172        public int hashCode() {
173            return stringRepresentation.hashCode();
174        }
175    
176        /**
177         * {@inheritDoc}
178         * 
179         * @see java.lang.Object#equals(java.lang.Object)
180         */
181        @Override
182        public boolean equals( Object obj ) {
183            if (obj == this) return true;
184            if (obj instanceof NodeDefinitionId) {
185                NodeDefinitionId that = (NodeDefinitionId)obj;
186                return this.stringRepresentation.equals(that.stringRepresentation);
187            }
188            return false;
189        }
190    
191        /**
192         * {@inheritDoc}
193         * 
194         * @see java.lang.Object#toString()
195         */
196        @Override
197        public String toString() {
198            return this.stringRepresentation;
199        }
200    
201    }