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