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 }