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 * Return whether there is at least one {@link #getRequiredPrimaryTypes() required primary type}
127 *
128 * @return true if there is at least one required primary type, or false otherewise
129 */
130 public boolean hasRequiredPrimaryTypes() {
131 return requiredPrimaryTypes.length != 0;
132 }
133
134 /**
135 * Determine whether this node definition defines any named child.
136 *
137 * @return true if this node definition allows children with any name, or false if this definition requires a particular child
138 * name
139 */
140 public boolean allowsAnyChildName() {
141 return childDefinitionName.getLocalName().equals(ANY_NAME) && childDefinitionName.getNamespaceUri().length() == 0;
142 }
143
144 /**
145 * Get the string form of this identifier. This form can be persisted, since it does not rely upon namespace prefixes.
146 *
147 * @return the string form
148 */
149 public String getString() {
150 return this.stringRepresentation;
151 }
152
153 /**
154 * Parse the supplied string for of an identifer, and return the object form for that identifier.
155 *
156 * @param definition the {@link #getString() string form of the identifier}; may not be null
157 * @param factory the factory that should be used to create Name objects; may not be null
158 * @return the object form of the identifier; never null
159 * @throws ValueFormatException if the definition is not the valid format
160 */
161 public static NodeDefinitionId fromString( String definition,
162 NameFactory factory ) {
163 String[] parts = definition.split("/");
164 String nodeTypeNameString = parts[0];
165 String childDefinitionNameString = parts[1];
166 Name[] requiredPrimaryTypes = new Name[parts.length - 2];
167 for (int i = 2, j = 0; i != parts.length; ++i, ++j) {
168 requiredPrimaryTypes[j] = factory.create(parts[i]);
169 }
170 Name nodeTypeName = factory.create(nodeTypeNameString);
171 Name childDefinitionName = factory.create(childDefinitionNameString);
172 return new NodeDefinitionId(nodeTypeName, childDefinitionName, requiredPrimaryTypes);
173 }
174
175 /**
176 * {@inheritDoc}
177 *
178 * @see java.lang.Object#hashCode()
179 */
180 @Override
181 public int hashCode() {
182 return stringRepresentation.hashCode();
183 }
184
185 /**
186 * {@inheritDoc}
187 *
188 * @see java.lang.Object#equals(java.lang.Object)
189 */
190 @Override
191 public boolean equals( Object obj ) {
192 if (obj == this) return true;
193 if (obj instanceof NodeDefinitionId) {
194 NodeDefinitionId that = (NodeDefinitionId)obj;
195 return this.stringRepresentation.equals(that.stringRepresentation);
196 }
197 return false;
198 }
199
200 /**
201 * {@inheritDoc}
202 *
203 * @see java.lang.Object#toString()
204 */
205 @Override
206 public String toString() {
207 return this.stringRepresentation;
208 }
209
210 }