1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * Unless otherwise indicated, all code in ModeShape is licensed
10 * to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.jcr;
25
26 import java.io.Serializable;
27 import java.util.HashMap;
28 import net.jcip.annotations.Immutable;
29 import org.modeshape.graph.property.Name;
30 import org.modeshape.graph.property.NameFactory;
31 import org.modeshape.graph.property.ValueFormatException;
32
33 /**
34 * An immutable identifier for a node definition. Although instances can be serialized, the node definitions are often stored
35 * within the graph as {@link #getString() string values} on a property. These string values can later be
36 * {@link #fromString(String, NameFactory) parsed} to reconstruct the identifier. Note that this string representation does not
37 * use namespace prefixes, so they are long-lasting and durable.
38 * <p>
39 * What distinguishes one property definition from another is not well documented in the JSR-170 specification. The closest this
40 * 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.
41 * The proposed draft of the JSR-283 specification does clarify this more: Section 4.7.15 says :
42 * </p>
43 * <p>
44 * <quote>"Similarly, a node type may have two or more child node definitions with identical name attributes as long as they are
45 * distinguishable by the required primary types attribute (the value returned by
46 * NodeDefinition.getRequiredPrimaryTypes)."</quote>
47 * </p>
48 * <p>
49 * This class is {@link Serializable} and designed to be used as a key in a {@link HashMap}.
50 * </p>
51 */
52 @Immutable
53 final class NodeDefinitionId implements Serializable {
54
55 /**
56 * Current version is {@value} .
57 */
58 private static final long serialVersionUID = 1L;
59
60 /**
61 * The string-form of the name that can be used to represent a residual property definition.
62 */
63 public static final String ANY_NAME = JcrNodeType.RESIDUAL_ITEM_NAME;
64
65 private final Name nodeTypeName;
66 private final Name childDefinitionName;
67 private final Name[] requiredPrimaryTypes;
68 /**
69 * A cached string representation, which is used for {@link #equals(Object)} and {@link #hashCode()} among other things.
70 */
71 private final String stringRepresentation;
72
73 /**
74 * Create an identifier for a node definition.
75 *
76 * @param nodeTypeName the name of the node type on which this child node definition is defined; may not be null
77 * @param childDefinitionName the name of the child node definition, which may be a {@link #ANY_NAME residual child
78 * definition}; may not be null
79 * @param requiredPrimaryTypes the names of the required primary types for the child node definition
80 */
81 public NodeDefinitionId( Name nodeTypeName,
82 Name childDefinitionName,
83 Name[] requiredPrimaryTypes ) {
84 assert nodeTypeName != null;
85 assert childDefinitionName != null;
86 this.nodeTypeName = nodeTypeName;
87 this.childDefinitionName = childDefinitionName;
88 this.requiredPrimaryTypes = requiredPrimaryTypes;
89 StringBuilder sb = new StringBuilder(this.nodeTypeName.getString());
90 sb.append('/').append(this.childDefinitionName.getString());
91 for (Name requiredPrimaryType : requiredPrimaryTypes) {
92 sb.append('/');
93 sb.append(requiredPrimaryType.getString());
94 }
95 this.stringRepresentation = sb.toString();
96 }
97
98 /**
99 * 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 }