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.graph.connector.base;
25  
26  import java.io.Serializable;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.UUID;
34  import org.modeshape.graph.property.Name;
35  import org.modeshape.graph.property.Path;
36  import org.modeshape.graph.property.Property;
37  import org.modeshape.graph.property.Path.Segment;
38  
39  /**
40   * A {@link Node} implementation used by the path-based connector (see {@link PathWorkspace} and {@link PathTransaction}), which
41   * stores all node state in a tree of content, where a specific path exists to each node in the tree.
42   * <p>
43   * Strictly speaking, this class is not immutable or thread safe. However, the persisted state cannot be changed. Instead, any
44   * changes made to the object are stored in a transient area and are made "persistable"via the {@link #freeze()} method.
45   * </p>
46   * <p>
47   * The {@link PathTransaction} maintains an unfrozen, changed instance within it transactional state, and always puts the
48   * {@link #freeze() frozen}, read-only representation inside the
49   */
50  public class PathNode implements Node, Serializable, Cloneable {
51  
52      private static final long serialVersionUID = 1L;
53  
54      /* These members MUST be treated as "final", even though they cannot be to correctly implement Serializable */
55      private/*final*/UUID uuid;
56      private/*final*/Path parent;
57      private/*final*/Segment name;
58      private/*final*/Map<Name, Property> properties;
59      private/*final*/List<Segment> children;
60      private/*final*/int version = 1;
61  
62      /** The changes made to this object, making it unfrozen */
63      protected transient Changes changes;
64  
65      /**
66       * Create a new node instance.
67       * 
68       * @param uuid the UUID of the node; may be null
69       * @param parent the path to the parent node; may be null only if the name is null
70       * @param name the name of this node, relative to the parent
71       * @param properties the unmodifiable map of properties; may be null or empty
72       * @param children the unmodifiable list of child segments; may be null or empty
73       */
74      public PathNode( UUID uuid,
75                       Path parent,
76                       Segment name,
77                       Map<Name, Property> properties,
78                       List<Segment> children ) {
79          this.uuid = uuid;
80          this.parent = parent;
81          this.name = name;
82          this.properties = properties != null ? properties : Collections.<Name, Property>emptyMap();
83          this.children = children != null ? children : Collections.<Segment>emptyList();
84          assert this.properties != null;
85          assert this.children != null;
86          assert this.name != null ? this.parent != null : this.parent == null;
87      }
88  
89      /**
90       * Create a new node instance.
91       * 
92       * @param uuid the UUID of the node; may be null
93       * @param parent the path to the parent node; may be null only if the name is null
94       * @param name the name of this node, relative to the parent
95       * @param properties the unmodifiable map of properties; may be null or empty
96       * @param children the unmodifiable list of child segments; may be null or empty
97       * @param version the version number
98       */
99      protected PathNode( UUID uuid,
100                         Path parent,
101                         Segment name,
102                         Map<Name, Property> properties,
103                         List<Segment> children,
104                         int version ) {
105         this.uuid = uuid;
106         this.parent = parent;
107         this.name = name;
108         this.properties = properties != null ? properties : Collections.<Name, Property>emptyMap();
109         this.children = children != null ? children : Collections.<Segment>emptyList();
110         this.version = version;
111         assert this.properties != null;
112         assert this.children != null;
113         assert this.name != null ? this.parent != null : this.parent == null;
114     }
115 
116     /**
117      * Create a new node instance.
118      * 
119      * @param uuid the UUID of the node; may be null
120      * @param parent the path to the parent node; may be null only if the name is null
121      * @param name the name of this node, relative to the parent
122      * @param properties the properties that are to be copied into the new node; may be null or empty
123      * @param children the unmodifiable list of child segments; may be null or empty
124      */
125     public PathNode( UUID uuid,
126                      Path parent,
127                      Segment name,
128                      Iterable<Property> properties,
129                      List<Segment> children ) {
130         this.uuid = uuid;
131         this.parent = parent;
132         this.name = name;
133         if (properties != null) {
134             Map<Name, Property> props = new HashMap<Name, Property>();
135             for (Property prop : properties) {
136                 props.put(prop.getName(), prop);
137             }
138             this.properties = props.isEmpty() ? Collections.<Name, Property>emptyMap() : Collections.unmodifiableMap(props);
139         } else {
140             this.properties = Collections.emptyMap();
141         }
142         this.children = children != null ? children : Collections.<Segment>emptyList();
143         assert this.properties != null;
144         assert this.children != null;
145         assert this.name != null ? this.parent != null : this.parent == null;
146     }
147 
148     /**
149      * Create a root node with the supplied UUID.
150      * 
151      * @param uuid the UUID of the root node; may not be null
152      */
153     public PathNode( UUID uuid ) {
154         this.uuid = uuid;
155         this.parent = null;
156         this.name = null;
157         this.properties = Collections.emptyMap();
158         this.children = Collections.emptyList();
159         assert this.uuid != null;
160         assert this.properties != null;
161         assert this.children != null;
162     }
163 
164     /**
165      * Get the version number of this node.
166      * 
167      * @return the version number
168      */
169     public int getVersion() {
170         return version;
171     }
172 
173     /**
174      * {@inheritDoc}
175      * 
176      * @see org.modeshape.graph.connector.base.Node#getUuid()
177      */
178     public UUID getUuid() {
179         return uuid;
180     }
181 
182     /**
183      * {@inheritDoc}
184      * 
185      * @see org.modeshape.graph.connector.base.Node#getName()
186      */
187     public Segment getName() {
188         return changes != null ? changes.getName() : name;
189     }
190 
191     /**
192      * @return parent
193      */
194     public Path getParent() {
195         return changes != null ? changes.getParent() : parent;
196     }
197 
198     /**
199      * {@inheritDoc}
200      * 
201      * @see org.modeshape.graph.connector.base.Node#getProperties()
202      */
203     public Map<Name, Property> getProperties() {
204         return changes != null ? changes.getProperties(false) : properties;
205     }
206 
207     /**
208      * {@inheritDoc}
209      * 
210      * @see org.modeshape.graph.connector.base.Node#getProperty(org.modeshape.graph.property.Name)
211      */
212     public Property getProperty( Name name ) {
213         return getProperties().get(name);
214     }
215 
216     /**
217      * @return children
218      */
219     public List<Segment> getChildren() {
220         return changes != null ? changes.getChildren(false) : children;
221     }
222 
223     @Override
224     public int hashCode() {
225         final int prime = 31;
226         int result = 1;
227         result = prime * result + ((children == null) ? 0 : children.hashCode());
228         result = prime * result + ((name == null) ? 0 : name.hashCode());
229         result = prime * result + ((parent == null) ? 0 : parent.hashCode());
230         return result;
231     }
232 
233     @Override
234     public boolean equals( Object obj ) {
235         if (this == obj) return true;
236         if (obj == null) return false;
237         if (getClass() != obj.getClass()) return false;
238         PathNode other = (PathNode)obj;
239         if (name == null) {
240             if (other.name != null) return false;
241         } else if (!name.equals(other.name)) return false;
242         if (parent == null) {
243             if (other.parent != null) return false;
244         } else if (!parent.equals(other.parent)) return false;
245         return true;
246     }
247 
248     /**
249      * {@inheritDoc}
250      * 
251      * @see java.lang.Object#toString()
252      */
253     @Override
254     public String toString() {
255         StringBuilder sb = new StringBuilder();
256         if (this.parent == null) {
257             sb.append("/");
258         } else {
259             sb.append(this.getParent()).append("/").append(this.getName());
260         }
261         sb.append(" (");
262         sb.append(this.getUuid()).append(")");
263         return sb.toString();
264     }
265 
266     /**
267      * {@inheritDoc}
268      * <p>
269      * This method never clones the {@link #hasChanges() changes}.
270      * </p>
271      * 
272      * @see java.lang.Object#clone()
273      */
274     @Override
275     public PathNode clone() {
276         return new PathNode(uuid, parent, name, new HashMap<Name, Property>(properties), new ArrayList<Segment>(children));
277     }
278 
279     /**
280      * Determine if this node has any unsaved changes.
281      * 
282      * @return true if there are unsaved changes, or false otherwise
283      */
284     protected boolean hasChanges() {
285         return changes != null;
286     }
287 
288     /**
289      * Create the {@link Changes} implementation. Subclasses that require a specialized class should overwrite this method. Note
290      * that this method does not modify any internal state; it should just instantiate and return the correct Changes class.
291      * 
292      * @return the changes object.
293      */
294     protected Changes newChanges() {
295         return new Changes();
296     }
297 
298     /**
299      * Return the frozen node with all internal state reflective of any changes. If this node has no changes, this method simply
300      * returns this same node. Otherwise, this method creates a new node that has no changes and that mirrors this node's current
301      * state, and this new node will have an incremented {@link #getVersion() version} number.
302      * 
303      * @return the unfrozen node; never null
304      */
305     public PathNode freeze() {
306         if (!hasChanges()) return this;
307         return new PathNode(uuid, changes.getParent(), changes.getName(), changes.getUnmodifiableProperties(),
308                             changes.getUnmodifiableChildren(), version + 1);
309     }
310 
311     /**
312      * Create a copy of this node except using the supplied path.
313      * 
314      * @param parent sets parent to the specified value.
315      * @return the new path node; never null
316      */
317     public PathNode withParent( Path parent ) {
318         if (changes == null) {
319             PathNode copy = clone();
320             copy.changes = newChanges();
321             copy.changes.setParent(parent);
322             return copy;
323         }
324         changes.setParent(parent);
325         return this;
326     }
327 
328     /**
329      * Create a copy of this node except using the supplied name.
330      * 
331      * @param name sets name to the specified value.
332      * @return the new path node; never null
333      */
334     public PathNode withName( Segment name ) {
335         if (changes == null) {
336             PathNode copy = clone();
337             copy.changes = newChanges();
338             copy.changes.setName(name);
339             return copy;
340         }
341         changes.setName(name);
342         return this;
343     }
344 
345     /**
346      * Create a copy of this node except adding the supplied node at the end of the existing children.
347      * 
348      * @param child the segment of the child that is to be added; may not be null
349      * @return the new path node; never null
350      */
351     public PathNode withChild( Segment child ) {
352         assert child != null;
353         if (getChildren().indexOf(child) != -1) return this;
354         if (changes == null) {
355             PathNode copy = clone();
356             List<Segment> children = new LinkedList<Segment>(getChildren());
357             assert !children.contains(child);
358             children.add(child);
359             copy.changes = newChanges();
360             copy.changes.setChildren(children);
361             return copy;
362         }
363         changes.getChildren(true).add(child);
364         return this;
365     }
366 
367     /**
368      * Create a copy of this node except adding the supplied node into the existing children at the specified index.
369      * 
370      * @param index the index at which the child is to appear
371      * @param child the segment of the child that is to be added at the end of the existing children
372      * @return the new path node; never null
373      */
374     public PathNode withChild( int index,
375                                Segment child ) {
376         assert child != null;
377         assert index >= 0;
378         int existingIndex = getChildren().indexOf(child);
379         if (existingIndex == index) {
380             // No need to add twice, so simply return (have not yet made any changes)
381             return this;
382         }
383         if (changes == null) {
384             PathNode copy = clone();
385             List<Segment> children = new LinkedList<Segment>(getChildren());
386             if (existingIndex >= 0) {
387                 // The child is moving positions, so remove it before we add it ...
388                 children.remove(existingIndex);
389                 if (existingIndex < index) --index;
390             }
391             children.add(index, child);
392             copy.changes = newChanges();
393             copy.changes.setChildren(children);
394             return copy;
395         }
396         List<Segment> children = changes.getChildren(true);
397         if (existingIndex >= 0) {
398             // The child is moving positions, so remove it before we add it ...
399             children.remove(existingIndex);
400             if (existingIndex < index) --index;
401         }
402         children.add(index, child);
403         return this;
404     }
405 
406     /**
407      * Create a copy of this node except without the supplied child node.
408      * 
409      * @param child the segment of the child that is to be removed; may not be null
410      * @return the new path node; never null
411      */
412     public PathNode withoutChild( Segment child ) {
413         assert child != null;
414         if (changes == null) {
415             PathNode copy = clone();
416             List<Segment> children = new LinkedList<Segment>(getChildren());
417             children.remove(child);
418             copy.changes = newChanges();
419             copy.changes.setChildren(children);
420             return copy;
421         }
422         changes.getChildren(true).remove(child);
423         return this;
424     }
425 
426     /**
427      * Create a copy of this node except with none of the children.
428      * 
429      * @return the new path node; never null
430      */
431     public PathNode withoutChildren() {
432         if (getChildren().isEmpty()) return this;
433         if (changes == null) {
434             PathNode copy = clone();
435             copy.changes = newChanges();
436             copy.changes.setChildren(new LinkedList<Segment>());
437             return copy;
438         }
439         changes.getChildren(true).clear();
440         return this;
441     }
442 
443     /**
444      * Create a copy of this node except with the changes to the properties.
445      * 
446      * @param propertiesToSet the properties that are to be set; may be null if no properties are to be set
447      * @param propertiesToRemove the names of the properties that are to be removed; may be null if no properties are to be
448      *        removed
449      * @param removeAllExisting true if all existing properties should be removed
450      * @return the unfrozen path node; never null
451      */
452     public PathNode withProperties( Iterable<Property> propertiesToSet,
453                                     Iterable<Name> propertiesToRemove,
454                                     boolean removeAllExisting ) {
455         if (propertiesToSet == null && propertiesToRemove == null && !removeAllExisting) {
456             // no changes ...
457             return this;
458         }
459         Map<Name, Property> newProperties = null;
460         PathNode result = this;
461         if (changes == null) {
462             PathNode copy = clone();
463             copy.changes = newChanges();
464             copy.changes.setProperties(new HashMap<Name, Property>(this.properties));
465             newProperties = copy.changes.getProperties(true);
466             result = copy;
467         } else {
468             newProperties = changes.getProperties(true);
469         }
470         if (removeAllExisting) {
471             newProperties.clear();
472         } else {
473             if (propertiesToRemove != null) {
474                 for (Name name : propertiesToRemove) {
475                     newProperties.remove(name);
476                 }
477             } else if (propertiesToSet == null) {
478                 return this;
479             }
480         }
481         if (propertiesToSet != null) {
482             for (Property property : propertiesToSet) {
483                 newProperties.put(property.getName(), property);
484             }
485         }
486         return result;
487     }
488 
489     /**
490      * Create a copy of this node except with the new property.
491      * 
492      * @param property the property to set
493      * @return this path node
494      */
495     public PathNode withProperty( Property property ) {
496         if (property == null) return this;
497         if (changes == null) {
498             PathNode copy = clone();
499             copy.changes = newChanges();
500             Map<Name, Property> newProps = new HashMap<Name, Property>(this.properties);
501             newProps.put(property.getName(), property);
502             copy.changes.setProperties(newProps);
503             return copy;
504         }
505         changes.getProperties(true).put(property.getName(), property);
506         return this;
507     }
508 
509     /**
510      * Create a copy of this node except without the new property.
511      * 
512      * @param propertyName the name of the property that is to be removed
513      * @return this path node, or this node if the named properties does not exist on this node
514      */
515     public PathNode withoutProperty( Name propertyName ) {
516         if (propertyName == null || !getProperties().containsKey(propertyName)) return this;
517         if (changes == null) {
518             PathNode copy = clone();
519             copy.changes = newChanges();
520             copy.changes.setProperties(new HashMap<Name, Property>(this.properties));
521             return copy;
522         }
523         changes.getProperties(true).remove(propertyName);
524         return this;
525     }
526 
527     /**
528      * Create a copy of this node without any properties
529      * 
530      * @return this path node, or this node if this node has no properties
531      */
532     public PathNode withoutProperties() {
533         if (getProperties().isEmpty()) return this;
534         if (changes == null) {
535             PathNode copy = clone();
536             copy.changes = newChanges();
537             copy.changes.setProperties(new HashMap<Name, Property>());
538             return copy;
539         }
540         changes.getProperties(true).clear();
541         return this;
542 
543     }
544 
545     @SuppressWarnings( "synthetic-access" )
546     protected class Changes {
547         private Path parent;
548         private Segment name;
549         private Map<Name, Property> properties;
550         private List<Segment> children;
551 
552         public Path getParent() {
553             return parent != null ? parent : PathNode.this.parent;
554         }
555 
556         public void setParent( Path parent ) {
557             this.parent = parent;
558         }
559 
560         public Segment getName() {
561             return name != null ? name : PathNode.this.name;
562         }
563 
564         public void setName( Segment name ) {
565             this.name = name;
566         }
567 
568         public Map<Name, Property> getProperties( boolean createIfMissing ) {
569             if (properties == null) {
570                 if (createIfMissing) {
571                     properties = new HashMap<Name, Property>(PathNode.this.properties);
572                     return properties;
573                 }
574                 return PathNode.this.properties;
575             }
576             return properties;
577         }
578 
579         public Map<Name, Property> getUnmodifiableProperties() {
580             return properties != null ? Collections.unmodifiableMap(properties) : PathNode.this.properties;
581         }
582 
583         public void setProperties( Map<Name, Property> properties ) {
584             this.properties = properties;
585         }
586 
587         public List<Segment> getChildren( boolean createIfMissing ) {
588             if (children == null) {
589                 if (createIfMissing) {
590                     children = new LinkedList<Segment>();
591                     return children;
592                 }
593                 return PathNode.this.children;
594             }
595             return children;
596         }
597 
598         public List<Segment> getUnmodifiableChildren() {
599             return children != null ? Collections.unmodifiableList(children) : PathNode.this.children;
600         }
601 
602         public void setChildren( List<Segment> children ) {
603             this.children = children;
604         }
605     }
606 
607 }