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 javax.jcr.PropertyType;
029    import net.jcip.annotations.Immutable;
030    import org.jboss.dna.graph.property.Name;
031    import org.jboss.dna.graph.property.NameFactory;
032    import org.jboss.dna.graph.property.ValueFormatException;
033    
034    /**
035     * An immutable identifier for a property definition. Although instances can be serialized, the property definitions are often
036     * stored within the graph as {@link #getString() string values} on a property. These string values can later be
037     * {@link #fromString(String, NameFactory) parsed} to reconstruct the identifier. Note that this string representation does not
038     * use namespace prefixes, so they are long-lasting and durable.
039     * <p>
040     * What distinguishes one property definition from another is not well documented in the JSR-170 specification. The closest this
041     * 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.
042     * The proposed draft of the JSR-283 specification does clarify this more: Section 4.7.15 says :
043     * </p>
044     * <p>
045     * <quote>"A node type may have two or more property definitions with identical name attributes (the value returned by
046     * ItemDefinition.getName) as long as the definitions are otherwise distinguishable by either the required type attribute (the
047     * value returned by PropertyDefinition.getRequiredType) or the multiple attribute (the value returned by
048     * PropertyDefinition.isMultiple)."</quote>
049     * </p>
050     * <p>
051     * This class is {@link Serializable} and designed to be used as a key in a {@link HashMap}.
052     * </p>
053     */
054    @Immutable
055    public final class PropertyDefinitionId implements Serializable {
056    
057        /**
058         * Current version is {@value} .
059         */
060        private static final long serialVersionUID = 1L;
061    
062        /**
063         * The string-form of the name that can be used to represent a residual property definition.
064         */
065        public static final String ANY_NAME = JcrNodeType.RESIDUAL_ITEM_NAME;
066    
067        private final Name nodeTypeName;
068        private final Name propertyDefinitionName;
069        private final int propertyType;
070        private final boolean allowsMultiple;
071        /**
072         * A cached string representation, which is used for {@link #equals(Object)} and {@link #hashCode()} among other things.
073         */
074        private final String stringRepresentation;
075    
076        /**
077         * Create a new identifier for a property definition.
078         * 
079         * @param nodeTypeName the name of the node type; may not be null
080         * @param propertyDefinitionName the name of the property definition, which may be a {@link #ANY_NAME residual property}; may
081         *        not be null
082         * @param propertyType the required property type for the definition; must be a valid {@link PropertyType} value
083         * @param allowsMultiple true if the property definition should allow multiple values, or false if it is a single-value
084         *        property definition
085         */
086        public PropertyDefinitionId( Name nodeTypeName,
087                                     Name propertyDefinitionName,
088                                     int propertyType,
089                                     boolean allowsMultiple ) {
090            this.nodeTypeName = nodeTypeName;
091            this.propertyDefinitionName = propertyDefinitionName;
092            this.propertyType = propertyType;
093            this.allowsMultiple = allowsMultiple;
094            this.stringRepresentation = this.nodeTypeName.getString() + '/' + this.propertyDefinitionName.getString() + '/'
095                                        + PropertyType.nameFromValue(propertyType) + '/' + (allowsMultiple ? '*' : '1');
096        }
097    
098        /**
099         * Get the name of the node type on which the property definition is defined
100         * 
101         * @return the node type's name; may not be null
102         */
103        public Name getNodeTypeName() {
104            return nodeTypeName;
105        }
106    
107        /**
108         * Get the name of the property definition.
109         * 
110         * @return the property definition's name; never null
111         */
112        public Name getPropertyDefinitionName() {
113            return propertyDefinitionName;
114        }
115    
116        /**
117         * Get the required property type
118         * 
119         * @return the property type; always a valid {@link PropertyType} value
120         */
121        public int getPropertyType() {
122            return propertyType;
123        }
124    
125        /**
126         * Return whether the property definition allows multiple values.
127         * 
128         * @return true if the property definition allows multiple values, or false if it is a single-value property definition
129         */
130        public boolean allowsMultiple() {
131            return allowsMultiple;
132        }
133    
134        /**
135         * Determine whether this property definition allows properties with any name.
136         * 
137         * @return true if this node definition allows properties with any name, or false if this definition requires a particular
138         *         property name
139         */
140        public boolean allowsAnyChildName() {
141            return propertyDefinitionName.getLocalName().equals(ANY_NAME) && propertyDefinitionName.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 PropertyDefinitionId fromString( String definition,
162                                                       NameFactory factory ) {
163            String[] parts = definition.split("/");
164            String nodeTypeNameString = parts[0];
165            String propertyDefinitionNameString = parts[1];
166            Name nodeTypeName = factory.create(nodeTypeNameString);
167            Name propertyDefinitionName = factory.create(propertyDefinitionNameString);
168            int propertyType = PropertyType.valueFromName(parts[2]);
169            boolean allowsMultiple = parts[3].charAt(0) == '*';
170            return new PropertyDefinitionId(nodeTypeName, propertyDefinitionName, propertyType, allowsMultiple);
171        }
172    
173        public PropertyDefinitionId asSingleValued() {
174            return new PropertyDefinitionId(nodeTypeName, propertyDefinitionName, propertyType, false);
175        }
176    
177        public PropertyDefinitionId asMultiValued() {
178            return new PropertyDefinitionId(nodeTypeName, propertyDefinitionName, propertyType, true);
179        }
180    
181        /**
182         * {@inheritDoc}
183         * 
184         * @see java.lang.Object#hashCode()
185         */
186        @Override
187        public int hashCode() {
188            return this.stringRepresentation.hashCode();
189        }
190    
191        /**
192         * {@inheritDoc}
193         * 
194         * @see java.lang.Object#equals(java.lang.Object)
195         */
196        @Override
197        public boolean equals( Object obj ) {
198            if (obj == this) return true;
199            if (obj instanceof PropertyDefinitionId) {
200                PropertyDefinitionId that = (PropertyDefinitionId)obj;
201                return this.stringRepresentation.equals(that.stringRepresentation);
202            }
203            return false;
204        }
205    
206        /**
207         * {@inheritDoc}
208         * 
209         * @see java.lang.Object#toString()
210         */
211        @Override
212        public String toString() {
213            return this.stringRepresentation;
214        }
215    
216    }