View Javadoc

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    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed 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.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.LinkedList;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  import javax.jcr.PropertyType;
37  import javax.jcr.Value;
38  import javax.jcr.nodetype.NodeType;
39  import javax.jcr.nodetype.NodeTypeIterator;
40  import javax.jcr.nodetype.PropertyDefinition;
41  import net.jcip.annotations.ThreadSafe;
42  import org.modeshape.common.util.CheckArg;
43  import org.modeshape.graph.ExecutionContext;
44  import org.modeshape.graph.property.Name;
45  import org.modeshape.graph.property.basic.BasicName;
46  
47  /**
48   * ModeShape implementation of JCR {@link NodeType}s.
49   */
50  @ThreadSafe
51  class JcrNodeType implements NodeType {
52  
53      public static final String RESIDUAL_ITEM_NAME = "*";
54      public static final Name RESIDUAL_NAME = new BasicName("", RESIDUAL_ITEM_NAME);
55  
56      /** The name of the node type (e.g., <code>{http://www.jcp.org/jcr/nt/1.0" target="alexandria_uri">http://www.jcp.org/jcr/nt/1.0}base</code>) */
57      private final Name name;
58      /** The name of the node's primary item */
59      private final Name primaryItemName;
60  
61      /** The supertypes for this node. */
62      private final List<JcrNodeType> declaredSupertypes;
63  
64      /**
65       * The list of all supertypes for this node, beginning with the immediate supertypes, followed by the supertypes of those
66       * supertypes, etc.
67       */
68      private final List<JcrNodeType> allSupertypes;
69  
70      /**
71       * The list of this type and all supertypes for this node, beginning with this type, continuing with the immediate supertypes,
72       * followed by the supertypes of those supertypes, etc.
73       */
74      private final List<JcrNodeType> thisAndAllSupertypes;
75      private final Set<Name> thisAndAllSupertypesNames;
76  
77      /** Indicates whether this node type is a mixin type (as opposed to a primary type). */
78      private final boolean mixin;
79      /** Indicates whether the child nodes of nodes of this type can be ordered. */
80      private final boolean orderableChildNodes;
81  
82      /** Indicates whether this node type is abstract */
83      private final boolean isAbstract;
84  
85      /** Indicates whether this node is queryable (i.e., should be included in query results) */
86      private final boolean queryable;
87  
88      /**
89       * The child node definitions that are defined on this node type.
90       */
91      private final List<JcrNodeDefinition> childNodeDefinitions;
92  
93      /**
94       * The property definitions that are defined on this node type.
95       */
96      private final List<JcrPropertyDefinition> propertyDefinitions;
97  
98      /**
99       * A local cache of all defined and inherited child node definitions and property definitions. Residual definitions are
100      * included. This class's methods to find a property definition and find child node definitions, and since they're frequently
101      * used by SessionCache this cache provides very quick access.
102      */
103     private final DefinitionCache allDefinitions;
104 
105     /**
106      * A reference to the execution context in which this node type exists, used to remap the internal names to their appropriate
107      * prefixed version (e.g., <code>{http://www.jcp.org/jcr/nt/1.0}base</code> to <code>&quot;nt:base&quot;</code>.).
108      */
109     private ExecutionContext context;
110 
111     /** Link to the repository node type manager for the repository to which this node type belongs. */
112     private RepositoryNodeTypeManager nodeTypeManager;
113 
114     JcrNodeType( ExecutionContext context,
115                  RepositoryNodeTypeManager nodeTypeManager,
116                  Name name,
117                  List<JcrNodeType> declaredSupertypes,
118                  Name primaryItemName,
119                  Collection<JcrNodeDefinition> childNodeDefinitions,
120                  Collection<JcrPropertyDefinition> propertyDefinitions,
121                  boolean mixin,
122                  boolean isAbstract,
123                  boolean queryable,
124                  boolean orderableChildNodes ) {
125         assert context != null;
126 
127         this.context = context;
128         this.nodeTypeManager = nodeTypeManager;
129         this.name = name;
130         this.primaryItemName = primaryItemName;
131         this.declaredSupertypes = declaredSupertypes != null ? declaredSupertypes : Collections.<JcrNodeType>emptyList();
132         this.mixin = mixin;
133         this.queryable = queryable;
134         this.isAbstract = isAbstract;
135         this.orderableChildNodes = orderableChildNodes;
136         this.propertyDefinitions = new ArrayList<JcrPropertyDefinition>(propertyDefinitions.size());
137         for (JcrPropertyDefinition property : propertyDefinitions) {
138             this.propertyDefinitions.add(property.with(this));
139         }
140 
141         this.childNodeDefinitions = new ArrayList<JcrNodeDefinition>(childNodeDefinitions.size());
142         for (JcrNodeDefinition childNode : childNodeDefinitions) {
143             this.childNodeDefinitions.add(childNode.with(this));
144         }
145 
146         // Build the list of all types, including supertypes ...
147         List<JcrNodeType> thisAndAllSupertypes = new LinkedList<JcrNodeType>();
148         Set<Name> typeNames = new HashSet<Name>();
149         thisAndAllSupertypes.add(this);
150         typeNames.add(this.name);
151         for (int i = 0; i != thisAndAllSupertypes.size(); ++i) {
152             JcrNodeType superType = thisAndAllSupertypes.get(i);
153             for (NodeType superSuperType : superType.getDeclaredSupertypes()) {
154                 JcrNodeType jcrSuperSuperType = (JcrNodeType)superSuperType;
155 
156                 if (jcrSuperSuperType == null) {
157                     assert JcrNtLexicon.BASE.equals(name);
158                     continue;
159                 }
160 
161                 if (typeNames.add(jcrSuperSuperType.getInternalName())) {
162                     thisAndAllSupertypes.add(jcrSuperSuperType);
163                 }
164             }
165         }
166         this.thisAndAllSupertypes = Collections.unmodifiableList(thisAndAllSupertypes);
167         // Make the list of all supertypes to be a sublist of the first ...
168         this.allSupertypes = thisAndAllSupertypes.size() > 1 ? thisAndAllSupertypes.subList(1, thisAndAllSupertypes.size()) : Collections.<JcrNodeType>emptyList();
169 
170         // Set up the set of all supertype names ...
171         this.thisAndAllSupertypesNames = Collections.unmodifiableSet(typeNames);
172 
173         this.allDefinitions = new DefinitionCache(this);
174     }
175 
176     List<JcrNodeType> getTypeAndSupertypes() {
177         return thisAndAllSupertypes;
178     }
179 
180     List<JcrNodeType> supertypes() {
181         return allSupertypes;
182     }
183 
184     /**
185      * Get the child definitions defined on this node type (excluding inherited definitions).
186      * 
187      * @return this node's child node definitions; never null
188      */
189     List<JcrNodeDefinition> childNodeDefinitions() {
190         return childNodeDefinitions;
191     }
192 
193     /**
194      * Get the property definitions defined on this node type (excluding inherited definitions).
195      * 
196      * @return this node's property definitions; never null
197      */
198     List<JcrPropertyDefinition> propertyDefinitions() {
199         return propertyDefinitions;
200     }
201 
202     /**
203      * Get all of the property definitions defined on this node type and its supertypes.
204      * 
205      * @return this node's explicit and inherited property definitions; never null
206      */
207     Collection<JcrPropertyDefinition> allPropertyDefinitions() {
208         return allDefinitions.allPropertyDefinitions();
209     }
210 
211     Collection<JcrPropertyDefinition> allSingleValuePropertyDefinitions( Name propertyName ) {
212         return allDefinitions.allSingleValuePropertyDefinitions(propertyName);
213     }
214 
215     Collection<JcrPropertyDefinition> allMultiValuePropertyDefinitions( Name propertyName ) {
216         return allDefinitions.allMultiValuePropertyDefinitions(propertyName);
217     }
218 
219     Collection<JcrPropertyDefinition> allPropertyDefinitions( Name propertyName ) {
220         return allDefinitions.allPropertyDefinitions(propertyName);
221     }
222 
223     /**
224      * Get all of the child node definitions defined on this node type and its supertypes.
225      * 
226      * @return this node's explicit and inherited child node definitions; never null
227      */
228     Collection<JcrNodeDefinition> allChildNodeDefinitions() {
229         return allDefinitions.allChildNodeDefinitions();
230     }
231 
232     Collection<JcrNodeDefinition> allChildNodeDefinitions( Name childName,
233                                                            boolean requireSns ) {
234         return allDefinitions.allChildNodeDefinitions(childName, requireSns);
235     }
236 
237     Collection<JcrNodeDefinition> allChildNodeDefinitions( Name childName ) {
238         return allDefinitions.allChildNodeDefinitions(childName);
239     }
240 
241     JcrNodeDefinition childNodeDefinition( NodeDefinitionId nodeDefnId ) {
242         List<Name> requiredPrimaryTypeNames = Arrays.asList(nodeDefnId.getRequiredPrimaryTypes());
243         for (JcrNodeDefinition nodeDefn : allChildNodeDefinitions(nodeDefnId.getChildDefinitionName())) {
244             if (nodeDefn.requiredPrimaryTypeNameSet().size() == requiredPrimaryTypeNames.size()
245                 && nodeDefn.requiredPrimaryTypeNameSet().containsAll(requiredPrimaryTypeNames)) {
246                 return nodeDefn;
247             }
248         }
249         return null;
250     }
251 
252     /**
253      * {@inheritDoc}
254      * 
255      * @see javax.jcr.nodetype.NodeType#canAddChildNode(java.lang.String)
256      */
257     public boolean canAddChildNode( String childNodeName ) {
258         CheckArg.isNotNull(childNodeName, "childNodeName");
259         Name childName = context.getValueFactories().getNameFactory().create(childNodeName);
260         return nodeTypeManager().findChildNodeDefinition(this.name, null, childName, null, 0, true) != null;
261     }
262 
263     /**
264      * {@inheritDoc}
265      * 
266      * @see javax.jcr.nodetype.NodeType#canAddChildNode(java.lang.String, java.lang.String)
267      */
268     public boolean canAddChildNode( String childNodeName,
269                                     String primaryNodeTypeName ) {
270 
271         CheckArg.isNotNull(childNodeName, "childNodeName");
272         CheckArg.isNotNull(primaryNodeTypeName, "primaryNodeTypeName");
273         Name childName = context.getValueFactories().getNameFactory().create(childNodeName);
274         Name childPrimaryTypeName = context.getValueFactories().getNameFactory().create(primaryNodeTypeName);
275 
276         if (primaryNodeTypeName != null) {
277             JcrNodeType childType = this.nodeTypeManager().getNodeType(childPrimaryTypeName);
278             if (childType.isAbstract() || childType.isMixin()) return false;
279         }
280 
281         return nodeTypeManager().findChildNodeDefinition(this.name, null, childName, childPrimaryTypeName, 0, true) != null;
282     }
283 
284     public boolean canRemoveNode( String itemName ) {
285         CheckArg.isNotNull(itemName, "itemName");
286         Name childName = context.getValueFactories().getNameFactory().create(itemName);
287         return nodeTypeManager().canRemoveAllChildren(this.name, null, childName, true);
288     }
289 
290     /**
291      * {@inheritDoc}
292      * <p>
293      * According to the JCR 1.0 JavaDoc, this method applies to all children. However, this appears to be changed in the JSR-283
294      * draft to apply only to nodes, and it is also deprecated.
295      * </p>
296      * 
297      * @see javax.jcr.nodetype.NodeType#canRemoveItem(java.lang.String)
298      */
299     public boolean canRemoveItem( String itemName ) {
300         CheckArg.isNotNull(itemName, "itemName");
301         Name childName = context.getValueFactories().getNameFactory().create(itemName);
302         return nodeTypeManager().canRemoveItem(this.name, null, childName, true);
303     }
304 
305     /**
306      * Returns <code>true</code> if <code>value</code> can be cast to <code>property.getRequiredType()</code> per the type
307      * conversion rules in section 6.2.6 of the JCR 1.0 specification AND <code>value</code> satisfies the constraints (if any)
308      * for the property definition. If the property definition has a required type of {@link PropertyType#UNDEFINED}, the cast
309      * will be considered to have succeeded and the value constraints (if any) will be interpreted using the semantics for the
310      * type specified in <code>value.getType()</code>.
311      * 
312      * @param propertyDefinition the property definition to validate against
313      * @param value the value to be validated
314      * @return <code>true</code> if the value can be cast to the required type for the property definition (if it exists) and
315      *         satisfies the constraints for the property (if any exist).
316      * @see PropertyDefinition#getValueConstraints()
317      * @see JcrPropertyDefinition#satisfiesConstraints(Value)
318      */
319     boolean canCastToTypeAndMatchesConstraints( JcrPropertyDefinition propertyDefinition,
320                                                 Value value ) {
321         try {
322             assert value instanceof JcrValue : "Illegal implementation of Value interface";
323             ((JcrValue)value).asType(propertyDefinition.getRequiredType()); // throws ValueFormatException if there's a problem
324             return propertyDefinition.satisfiesConstraints(value);
325         } catch (javax.jcr.ValueFormatException vfe) {
326             // Cast failed
327             return false;
328         }
329     }
330 
331     /**
332      * Returns <code>true</code> if <code>value</code> can be cast to <code>property.getRequiredType()</code> per the type
333      * conversion rules in section 6.2.6 of the JCR 1.0 specification AND <code>value</code> satisfies the constraints (if any)
334      * for the property definition. If the property definition has a required type of {@link PropertyType#UNDEFINED}, the cast
335      * will be considered to have succeeded and the value constraints (if any) will be interpreted using the semantics for the
336      * type specified in <code>value.getType()</code>.
337      * 
338      * @param propertyDefinition the property definition to validate against
339      * @param values the values to be validated
340      * @return <code>true</code> if the value can be cast to the required type for the property definition (if it exists) and
341      *         satisfies the constraints for the property (if any exist).
342      * @see PropertyDefinition#getValueConstraints()
343      * @see JcrPropertyDefinition#satisfiesConstraints(Value)
344      */
345     boolean canCastToTypeAndMatchesConstraints( JcrPropertyDefinition propertyDefinition,
346                                                 Value[] values ) {
347         for (Value value : values) {
348             if (!canCastToTypeAndMatchesConstraints(propertyDefinition, value)) return false;
349         }
350         return true;
351     }
352 
353     /**
354      * {@inheritDoc}
355      * 
356      * @see javax.jcr.nodetype.NodeType#canSetProperty(java.lang.String, javax.jcr.Value)
357      */
358     public boolean canSetProperty( String propertyName,
359                                    Value value ) {
360         CheckArg.isNotNull(propertyName, "propertyName");
361         Name name = context.getValueFactories().getNameFactory().create(propertyName);
362 
363         // Reuse the logic in RepositoryNodeTypeManager ...
364         return nodeTypeManager().findPropertyDefinition(this.name, null, name, value, false, true) != null;
365     }
366 
367     /**
368      * {@inheritDoc}
369      * 
370      * @see javax.jcr.nodetype.NodeType#canSetProperty(java.lang.String, javax.jcr.Value[])
371      */
372     public boolean canSetProperty( String propertyName,
373                                    Value[] values ) {
374         CheckArg.isNotNull(propertyName, "propertyName");
375         if (values == null || values.length == 0) {
376             return canRemoveProperty(propertyName);
377         }
378 
379         Name name = context.getValueFactories().getNameFactory().create(propertyName);
380         // Reuse the logic in RepositoryNodeTypeManager ...
381         return nodeTypeManager().findPropertyDefinition(this.name, null, name, values, true) != null;
382     }
383 
384     public boolean canRemoveProperty( String propertyName ) {
385         CheckArg.isNotNull(propertyName, "propertyName");
386         Name name = context.getValueFactories().getNameFactory().create(propertyName);
387 
388         // Reuse the logic in RepositoryNodeTypeManager ...
389         return nodeTypeManager().canRemoveProperty(this.name, null, name, true);
390     }
391 
392     /**
393      * {@inheritDoc}
394      * 
395      * @see javax.jcr.nodetype.NodeType#getDeclaredChildNodeDefinitions()
396      */
397     public JcrNodeDefinition[] getDeclaredChildNodeDefinitions() {
398         // Always have to make a copy to prevent changes ...
399         return childNodeDefinitions.toArray(new JcrNodeDefinition[childNodeDefinitions.size()]);
400     }
401 
402     /**
403      * {@inheritDoc}
404      * 
405      * @see javax.jcr.nodetype.NodeType#getChildNodeDefinitions()
406      */
407     public JcrNodeDefinition[] getChildNodeDefinitions() {
408         // Always have to make a copy to prevent changes ...
409         Collection<JcrNodeDefinition> definitions = this.allDefinitions.allChildNodeDefinitions();
410         return definitions.toArray(new JcrNodeDefinition[definitions.size()]);
411     }
412 
413     /**
414      * {@inheritDoc}
415      * 
416      * @see javax.jcr.nodetype.NodeType#getPropertyDefinitions()
417      */
418     public JcrPropertyDefinition[] getPropertyDefinitions() {
419         // Always have to make a copy to prevent changes ...
420         Collection<JcrPropertyDefinition> definitions = this.allDefinitions.allPropertyDefinitions();
421         return definitions.toArray(new JcrPropertyDefinition[definitions.size()]);
422     }
423 
424     /**
425      * {@inheritDoc}
426      * 
427      * @see javax.jcr.nodetype.NodeType#getDeclaredSupertypes()
428      */
429     public JcrNodeType[] getDeclaredSupertypes() {
430         // Always have to make a copy to prevent changes ...
431         return declaredSupertypes.toArray(new JcrNodeType[declaredSupertypes.size()]);
432     }
433 
434     /**
435      * @return the array of names of supertypes declared for this node; possibly empty, never null
436      */
437     public String[] getDeclaredSupertypeNames() {
438         List<String> supertypeNames = new ArrayList<String>(declaredSupertypes.size());
439 
440         for (JcrNodeType declaredSupertype : declaredSupertypes) {
441             supertypeNames.add(declaredSupertype.getName());
442         }
443 
444         // Always have to make a copy to prevent changes ...
445         return supertypeNames.toArray(new String[supertypeNames.size()]);
446     }
447 
448     public NodeTypeIterator getSubtypes() {
449         return new JcrNodeTypeIterator(nodeTypeManager.subtypesFor(this));
450     }
451 
452     public NodeTypeIterator getDeclaredSubtypes() {
453         return new JcrNodeTypeIterator(nodeTypeManager.declaredSubtypesFor(this));
454     }
455 
456     /**
457      * {@inheritDoc}
458      * 
459      * @see javax.jcr.nodetype.NodeType#getName()
460      */
461     public String getName() {
462         // Translate the name to the correct prefix. Need to check the session to support url-remapping.
463         return name.getString(context.getNamespaceRegistry());
464     }
465 
466     /**
467      * Returns the internal {@link Name} object for the node type. This method exists outside the JCR API and should not be
468      * exposed outside of the package.
469      * 
470      * @return the internal {@link Name} object for the node type.
471      */
472     Name getInternalName() {
473         return name;
474     }
475 
476     /**
477      * Returns the internal {@link Name} object for the primary item of this node type. This method exists outside the JCR API and
478      * should not be exposed outside of the package.
479      * 
480      * @return the internal {@link Name} object for the primary item of this node type.
481      */
482     Name getInternalPrimaryItemName() {
483         return primaryItemName;
484     }
485 
486     /**
487      * {@inheritDoc}
488      * 
489      * @see javax.jcr.nodetype.NodeType#getPrimaryItemName()
490      */
491     public String getPrimaryItemName() {
492         if (primaryItemName == null) {
493             return null;
494         }
495 
496         // Translate the name to the correct prefix. Need to check the session to support url-remapping.
497         return primaryItemName.getString(context.getNamespaceRegistry());
498     }
499 
500     /**
501      * {@inheritDoc}
502      * 
503      * @see javax.jcr.nodetype.NodeType#getDeclaredPropertyDefinitions()
504      */
505     public JcrPropertyDefinition[] getDeclaredPropertyDefinitions() {
506         return propertyDefinitions.toArray(new JcrPropertyDefinition[propertyDefinitions.size()]);
507     }
508 
509     /**
510      * {@inheritDoc}
511      * 
512      * @see javax.jcr.nodetype.NodeType#getSupertypes()
513      */
514     public NodeType[] getSupertypes() {
515         return allSupertypes.toArray(new NodeType[allSupertypes.size()]);
516     }
517 
518     /**
519      * {@inheritDoc}
520      * 
521      * @see javax.jcr.nodetype.NodeType#hasOrderableChildNodes()
522      */
523     public boolean hasOrderableChildNodes() {
524         return orderableChildNodes;
525     }
526 
527     /**
528      * {@inheritDoc}
529      * 
530      * @see javax.jcr.nodetype.NodeType#isMixin()
531      */
532     public boolean isMixin() {
533         return mixin;
534     }
535 
536     public boolean isAbstract() {
537         return isAbstract;
538     }
539 
540     public boolean isQueryable() {
541         return queryable;
542     }
543 
544     /**
545      * {@inheritDoc}
546      * 
547      * @see javax.jcr.nodetype.NodeType#isNodeType(java.lang.String)
548      */
549     public boolean isNodeType( String nodeTypeName ) {
550         if (nodeTypeName == null) return false;
551         Name name = context.getValueFactories().getNameFactory().create(nodeTypeName);
552         return this.thisAndAllSupertypesNames.contains(name);
553     }
554 
555     boolean isNodeType( Name nodeTypeName ) {
556         if (nodeTypeName == null) return false;
557         return this.thisAndAllSupertypesNames.contains(nodeTypeName);
558     }
559 
560     boolean isNodeTypeOneOf( Name... nodeTypeNames ) {
561         if (nodeTypeNames == null || nodeTypeNames.length == 0) return false;
562         for (Name nodeTypeName : nodeTypeNames) {
563             if (this.thisAndAllSupertypesNames.contains(nodeTypeName)) return true;
564         }
565         return false;
566     }
567 
568     /**
569      * {@inheritDoc}
570      * 
571      * @see java.lang.Object#hashCode()
572      */
573     @Override
574     public int hashCode() {
575         return this.name.hashCode();
576     }
577 
578     /**
579      * {@inheritDoc}
580      * 
581      * @see java.lang.Object#equals(java.lang.Object)
582      */
583     @Override
584     public boolean equals( Object obj ) {
585         if (obj == this) return true;
586         if (obj instanceof JcrNodeType) {
587             JcrNodeType that = (JcrNodeType)obj;
588             return this.name.equals(that.name);
589         }
590         if (obj instanceof NodeType) {
591             NodeType that = (NodeType)obj;
592             return this.getName().equals(that.getName());
593         }
594         return false;
595     }
596 
597     @Override
598     public String toString() {
599         return getName();
600     }
601 
602     /**
603      * Returns a {@link JcrNodeType} that is equivalent to this {@link JcrNodeType}, except with a different repository node type
604      * manager. This method should only be called during the initialization of the repository node type manager, unless some kind
605      * of cross-repository type shipping is implemented.
606      * 
607      * @param nodeTypeManager the new repository node type manager
608      * @return a new {@link JcrNodeType} that has the same state as this node type, but with the given node type manager.
609      */
610     final JcrNodeType with( RepositoryNodeTypeManager nodeTypeManager ) {
611         return new JcrNodeType(this.context, nodeTypeManager, this.name, this.declaredSupertypes, this.primaryItemName,
612                                this.childNodeDefinitions, this.propertyDefinitions, this.mixin, this.isAbstract, this.queryable,
613                                this.orderableChildNodes);
614     }
615 
616     /**
617      * Returns a {@link JcrNodeType} that is equivalent to this {@link JcrNodeType}, except with a different execution context.
618      * 
619      * @param context the new execution context
620      * @return a new {@link JcrNodeType} that has the same state as this node type, but with the given node type manager.
621      * @see JcrNodeTypeManager
622      */
623     final JcrNodeType with( ExecutionContext context ) {
624         return new JcrNodeType(context, this.nodeTypeManager, this.name, this.declaredSupertypes, this.primaryItemName,
625                                this.childNodeDefinitions, this.propertyDefinitions, this.mixin, this.isAbstract, this.queryable,
626                                this.orderableChildNodes);
627     }
628 
629     final RepositoryNodeTypeManager nodeTypeManager() {
630         return nodeTypeManager;
631     }
632 
633     /**
634      * Returns whether this node type is in conflict with the provided primary node type or mixin types.
635      * <p>
636      * A node type is in conflict with another set of node types if either of the following is true:
637      * <ol>
638      * <li>This node type has the same name as any of the other node types</li>
639      * <li>This node type defines a property (or inherits the definition of a property) with the same name as a property defined
640      * in any of the other node types <i>unless</i> this node type and the other node type both inherited the property definition
641      * from the same ancestor node type</li>
642      * <li>This node type defines a child node (or inherits the definition of a child node) with the same name as a child node
643      * defined in any of the other node types <i>unless</i> this node type and the other node type both inherited the child node
644      * definition from the same ancestor node type</li>
645      * </ol>
646      * </p>
647      * 
648      * @param primaryNodeType the primary node type to check
649      * @param mixinNodeTypes the mixin node types to check
650      * @return true if this node type conflicts with the provided primary or mixin node types as defined below
651      */
652     final boolean conflictsWith( NodeType primaryNodeType,
653                                  NodeType[] mixinNodeTypes ) {
654         Map<PropertyDefinitionId, JcrPropertyDefinition> props = new HashMap<PropertyDefinitionId, JcrPropertyDefinition>();
655         /*
656          * Need to have the same parent name for all PropertyDefinitionIds and NodeDefinitionIds as we're checking for conflicts on the definition name
657          */
658         final Name DEFAULT_NAME = this.name;
659 
660         for (JcrPropertyDefinition property : propertyDefinitions()) {
661             /*
662              * I'm trying really hard to reuse existing code, but it's a stretch in this case.  I don't care about the property
663              * types or where they were declared... if more than one definition with the given name exists (not counting definitions
664              * inherited from the same root definition), then there is a conflict.
665              */
666             PropertyDefinitionId pid = new PropertyDefinitionId(DEFAULT_NAME, property.name, PropertyType.UNDEFINED,
667                                                                 property.isMultiple());
668             props.put(pid, property);
669         }
670 
671         /*
672          * The specification does not mandate whether this should or should not be consider a conflict.  However, the Apache
673          * TCK canRemoveMixin test cases assume that this will generate a conflict.
674          */
675         if (primaryNodeType.getName().equals(getName())) {
676             // This node type has already been applied to the node
677             return true;
678         }
679 
680         for (JcrPropertyDefinition property : ((JcrNodeType)primaryNodeType).propertyDefinitions()) {
681             PropertyDefinitionId pid = new PropertyDefinitionId(DEFAULT_NAME, property.name, PropertyType.UNDEFINED,
682                                                                 property.isMultiple());
683             JcrPropertyDefinition oldProp = props.put(pid, property);
684             if (oldProp != null) {
685                 String oldPropTypeName = oldProp.getDeclaringNodeType().getName();
686                 String propTypeName = property.getDeclaringNodeType().getName();
687                 if (!oldPropTypeName.equals(propTypeName)) {
688                     // The two types conflict as both separately declare a property with the same name
689                     return true;
690                 }
691             }
692         }
693 
694         for (NodeType mixinNodeType : mixinNodeTypes) {
695             /*
696              * The specification does not mandate whether this should or should not be consider a conflict.  However, the Apache
697              * TCK canRemoveMixin test cases assume that this will generate a conflict.
698              */
699             if (mixinNodeType.getName().equals(getName())) {
700                 // This node type has already been applied to the node
701                 return true;
702             }
703 
704             for (JcrPropertyDefinition property : ((JcrNodeType)mixinNodeType).propertyDefinitions()) {
705                 PropertyDefinitionId pid = new PropertyDefinitionId(DEFAULT_NAME, property.name, PropertyType.UNDEFINED,
706                                                                     property.isMultiple());
707                 JcrPropertyDefinition oldProp = props.put(pid, property);
708                 if (oldProp != null) {
709                     String oldPropTypeName = oldProp.getDeclaringNodeType().getName();
710                     String propTypeName = property.getDeclaringNodeType().getName();
711                     if (!oldPropTypeName.equals(propTypeName)) {
712                         // The two types conflict as both separately declare a property with the same name
713                         return true;
714                     }
715                 }
716             }
717         }
718 
719         Map<NodeDefinitionId, JcrNodeDefinition> childNodes = new HashMap<NodeDefinitionId, JcrNodeDefinition>();
720 
721         for (JcrNodeDefinition childNode : childNodeDefinitions()) {
722             NodeDefinitionId nid = new NodeDefinitionId(DEFAULT_NAME, childNode.name, new Name[0]);
723             childNodes.put(nid, childNode);
724         }
725 
726         for (JcrNodeDefinition childNode : ((JcrNodeType)primaryNodeType).childNodeDefinitions()) {
727             NodeDefinitionId nid = new NodeDefinitionId(DEFAULT_NAME, childNode.name, new Name[0]);
728             JcrNodeDefinition oldNode = childNodes.put(nid, childNode);
729             if (oldNode != null) {
730                 String oldNodeTypeName = oldNode.getDeclaringNodeType().getName();
731                 String childNodeTypeName = childNode.getDeclaringNodeType().getName();
732                 if (!oldNodeTypeName.equals(childNodeTypeName)) {
733                     // The two types conflict as both separately declare a child node with the same name
734                     return true;
735                 }
736             }
737         }
738 
739         for (NodeType mixinNodeType : mixinNodeTypes) {
740             for (JcrNodeDefinition childNode : ((JcrNodeType)mixinNodeType).childNodeDefinitions()) {
741                 NodeDefinitionId nid = new NodeDefinitionId(DEFAULT_NAME, childNode.name, new Name[0]);
742                 JcrNodeDefinition oldNode = childNodes.put(nid, childNode);
743                 if (oldNode != null) {
744                     String oldNodeTypeName = oldNode.getDeclaringNodeType().getName();
745                     String childNodeTypeName = childNode.getDeclaringNodeType().getName();
746                     if (!oldNodeTypeName.equals(childNodeTypeName)) {
747                         // The two types conflict as both separately declare a child node with the same name
748                         return true;
749                     }
750                 }
751             }
752         }
753 
754         return false;
755     }
756 
757 }