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.map;
25  
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.ListIterator;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.UUID;
36  import org.modeshape.common.util.HashCode;
37  import org.modeshape.graph.ExecutionContext;
38  import org.modeshape.graph.Location;
39  import org.modeshape.graph.connector.UuidAlreadyExistsException;
40  import org.modeshape.graph.property.Name;
41  import org.modeshape.graph.property.NamespaceRegistry;
42  import org.modeshape.graph.property.Path;
43  import org.modeshape.graph.property.PathFactory;
44  import org.modeshape.graph.property.Property;
45  import org.modeshape.graph.property.PropertyFactory;
46  import org.modeshape.graph.property.PropertyType;
47  import org.modeshape.graph.property.Reference;
48  import org.modeshape.graph.property.UuidFactory;
49  import org.modeshape.graph.property.ValueFactory;
50  import org.modeshape.graph.property.Path.Segment;
51  import org.modeshape.graph.property.basic.RootPath;
52  import org.modeshape.graph.query.QueryResults;
53  import org.modeshape.graph.request.AccessQueryRequest;
54  
55  /**
56   * A default implementation of {@link MapWorkspace} that only requires the user to implement some simple, map-like operations.
57   */
58  public abstract class AbstractMapWorkspace implements MapWorkspace {
59  
60      private final MapRepository repository;
61      private final String name;
62  
63      protected AbstractMapWorkspace( MapRepository repository,
64                                      String name ) {
65          assert repository != null;
66          assert name != null;
67  
68          this.repository = repository;
69          this.name = name;
70      }
71  
72      protected void initialize() {
73          // Create the root node ...
74          MapNode root = createMapNode(repository.getRootNodeUuid());
75          assert root != null;
76          addNodeToMap(root);
77      }
78  
79      /**
80       * Get the repository that owns this workspace.
81       * 
82       * @return the repository; never null
83       */
84      protected MapRepository getRepository() {
85          return repository;
86      }
87  
88      /**
89       * Adds the given node to the backing map, replacing any node already in the backing map with the same UUID.
90       * 
91       * @param node the node to add to the map; may not be null
92       */
93      protected abstract void addNodeToMap( MapNode node );
94  
95      /**
96       * Removes the node with the given UUID from the backing map, returning the node if it exists
97       * 
98       * @param nodeUuid the UUID of the node to replace; may not be null
99       * @return if a node with that UUID exists in the backing map (prior to removal), that node is returned; otherwise null
100      */
101     protected abstract MapNode removeNodeFromMap( UUID nodeUuid );
102 
103     /**
104      * Removes all of the nodes from the backing map
105      */
106     protected abstract void removeAllNodesFromMap();
107 
108     /**
109      * Gets the node with the given UUID from the backing map, if one exists
110      * 
111      * @param nodeUuid the UUID of the node to be retrieved; may not be null
112      * @return the node that exists in the backing map with the given UUID
113      */
114     public abstract MapNode getNode( UUID nodeUuid );
115 
116     /**
117      * Creates an empty {@link MapNode node} with the given UUID.
118      * <p>
119      * This method does not add the new map node to the map. That must be done with a separate call to
120      * {@link #addNodeToMap(MapNode)}.
121      * </p>
122      * 
123      * @param uuid the UUID that identifies the new node; may not be null
124      * @return the new node with the given UUID
125      */
126     protected MapNode createMapNode( UUID uuid ) {
127         assert uuid != null;
128         return new DefaultMapNode(uuid);
129     }
130 
131     /**
132      * @return name
133      */
134     public String getName() {
135         return name;
136     }
137 
138     /**
139      * {@inheritDoc}
140      * 
141      * @see MapWorkspace#getRoot()
142      */
143     public MapNode getRoot() {
144         return getNode(repository.getRootNodeUuid());
145     }
146 
147     /**
148      * Find a node with the given path.
149      * 
150      * @param context the execution context to use to convert {@code path} to a {@link Path}; may not be null
151      * @param path the path to the node; may not be null
152      * @return the node with the path, or null if the node does not exist
153      */
154     public MapNode getNode( ExecutionContext context,
155                             String path ) {
156         assert context != null;
157         assert path != null;
158         return getNode(context.getValueFactories().getPathFactory().create(path));
159     }
160 
161     /**
162      * Find a node with the given path.
163      * 
164      * @param path the path to the node; may not be null
165      * @return the node with the path, or null if the node does not exist
166      */
167     public MapNode getNode( Path path ) {
168         assert path != null;
169         MapNode node = getRoot();
170         for (Path.Segment segment : path) {
171             MapNode desiredChild = null;
172             for (MapNode child : node.getChildren()) {
173                 if (child == null) continue;
174                 Path.Segment childName = child.getName();
175                 if (childName == null) continue;
176                 if (childName.equals(segment)) {
177                     desiredChild = child;
178                     break;
179                 }
180             }
181             if (desiredChild != null) {
182                 node = desiredChild;
183             } else {
184                 return null;
185             }
186         }
187         return node;
188     }
189 
190     /**
191      * Returns the absolute path to the given node
192      * 
193      * @param pathFactory the path factory to use to create the path from the list of names of all of the nodes on the path from
194      *        the root node to the given node
195      * @param node the node for which the path should be returned
196      * @return the absolute path to the given node
197      */
198     public Path pathFor( PathFactory pathFactory,
199                          MapNode node ) {
200         assert node != null;
201         assert pathFactory != null;
202 
203         LinkedList<Path.Segment> segments = new LinkedList<Path.Segment>();
204         MapNode root = getRoot();
205 
206         do {
207             segments.addFirst(node.getName());
208             node = node.getParent();
209         } while (!node.equals(root));
210 
211         return pathFactory.createAbsolutePath(segments);
212     }
213 
214     /**
215      * Find the lowest existing node along the path.
216      * 
217      * @param path the path to the node; may not be null
218      * @return the lowest existing node along the path, or the root node if no node exists on the path
219      */
220     public Path getLowestExistingPath( Path path ) {
221         assert path != null;
222         MapNode node = getRoot();
223         int segmentNumber = 0;
224         for (Path.Segment segment : path) {
225             MapNode desiredChild = null;
226             for (MapNode child : node.getChildren()) {
227                 if (child == null) continue;
228                 Path.Segment childName = child.getName();
229                 if (childName == null) continue;
230                 if (childName.equals(segment)) {
231                     desiredChild = child;
232                     break;
233                 }
234             }
235             if (desiredChild != null) {
236                 node = desiredChild;
237             } else {
238                 return path.subpath(0, segmentNumber);
239             }
240             ++segmentNumber;
241         }
242         return RootPath.INSTANCE;
243     }
244 
245     /**
246      * Removes the given node and its children, correcting the SNS and child indices for its parent. This method will return false
247      * if the given node does not exist in this workspace.
248      * 
249      * @param context the current execution context; may not be null
250      * @param node the node to be removed; may not be null
251      * @return whether a node was removed as a result of this operation
252      */
253     public boolean removeNode( ExecutionContext context,
254                                MapNode node ) {
255         assert context != null;
256         assert node != null;
257 
258         if (getRoot().equals(node)) {
259             removeAllNodesFromMap();
260             // Recreate the root node ...
261             addNodeToMap(createMapNode(repository.getRootNodeUuid()));
262             return true;
263         }
264         MapNode parent = node.getParent();
265         assert parent != null;
266         if (!parent.removeChild(node)) return false;
267         correctSameNameSiblingIndexes(context, parent, node.getName().getName());
268         removeUuidReference(node);
269         return true;
270     }
271 
272     /**
273      * Recursively removes the given node and its children from the backing map
274      * 
275      * @param node the root of the branch to be removed
276      */
277     protected void removeUuidReference( MapNode node ) {
278         assert node != null;
279         removeNodeFromMap(node.getUuid());
280 
281         for (MapNode child : node.getChildren()) {
282             removeUuidReference(child);
283         }
284     }
285 
286     /**
287      * Create a node at the supplied path. The parent of the new node must already exist.
288      * 
289      * @param context the environment; may not be null
290      * @param pathToNewNode the path to the new node; may not be null
291      * @param properties the properties for the new node
292      * @return the new node (or root if the path specified the root)
293      */
294     public MapNode createNode( ExecutionContext context,
295                                String pathToNewNode,
296                                Iterable<Property> properties ) {
297         assert context != null;
298         assert pathToNewNode != null;
299         Path path = context.getValueFactories().getPathFactory().create(pathToNewNode);
300         if (path.isRoot()) return getRoot();
301         Path parentPath = path.getParent();
302         MapNode parentNode = getNode(parentPath);
303         Name name = path.getLastSegment().getName();
304         return createNode(context, parentNode, name, null, properties);
305     }
306 
307     /**
308      * Create a new node with the supplied name, as a child of the supplied parent.
309      * 
310      * @param context the execution context
311      * @param parentNode the parent node; may not be null
312      * @param name the name; may not be null
313      * @param uuid the UUID of the node, or null if the UUID is to be generated
314      * @param properties the properties for the new node
315      * @return the new node
316      */
317     public MapNode createNode( ExecutionContext context,
318                                MapNode parentNode,
319                                Name name,
320                                UUID uuid,
321                                Iterable<Property> properties ) {
322         assert context != null;
323         assert name != null;
324         if (parentNode == null) parentNode = getRoot();
325         if (uuid == null) uuid = UUID.randomUUID();
326 
327         MapNode node = createMapNode(uuid);
328 
329         // Find the last node with this same name ...
330         int nextIndex = 1;
331         Set<Name> uniqueNames = parentNode.getUniqueChildNames();
332         if (uniqueNames.contains(name)) {
333             List<MapNode> children = parentNode.getChildren();
334             ListIterator<MapNode> iter = children.listIterator(children.size());
335             while (iter.hasPrevious()) {
336                 MapNode prev = iter.previous();
337                 if (prev.getName().getName().equals(name)) {
338                     nextIndex = prev.getName().getIndex() + 1;
339                     break;
340                 }
341             }
342         }
343 
344         Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, nextIndex);
345         node.setName(newName);
346         node.setProperties(properties);
347         node.setParent(parentNode);
348 
349         parentNode.addChild(node);
350         parentNode.getUniqueChildNames().add(name);
351         addNodeToMap(node);
352         return node;
353     }
354 
355     /**
356      * Move the supplied node to the new parent. This method automatically removes the node from its existing parent, and also
357      * correctly adjusts the {@link Path.Segment#getIndex() index} to be correct in the new parent.
358      * 
359      * @param context
360      * @param node the node to be moved; may not be the {@link AbstractMapWorkspace#getRoot() root}
361      * @param desiredNewName the new name for the node, if it is to be changed; may be null
362      * @param newWorkspace the workspace containing the new parent node
363      * @param newParent the new parent; may not be the {@link AbstractMapWorkspace#getRoot() root}
364      * @param beforeNode the node before which this new node should be placed
365      */
366     public void moveNode( ExecutionContext context,
367                           MapNode node,
368                           Name desiredNewName,
369                           MapWorkspace newWorkspace,
370                           MapNode newParent,
371                           MapNode beforeNode ) {
372         assert context != null;
373         assert newParent != null;
374         assert node != null;
375         assert newWorkspace instanceof AbstractMapWorkspace;
376 
377         AbstractMapWorkspace newAbstractMapWorkspace = (AbstractMapWorkspace)newWorkspace;
378 
379         // Why was this restriction here? -- BRC
380         // assert newAbstractMapWorkspace.getRoot().equals(newParent) != true;
381         assert this.getRoot().equals(node) != true;
382         MapNode oldParent = node.getParent();
383         Name oldName = node.getName().getName();
384 
385         if (this.equals(newAbstractMapWorkspace) && node.getParent().getUuid().equals(newParent.getUuid())
386             && node.equals(beforeNode)) {
387             // Trivial move of a node to its parent before itself
388             return;
389         }
390 
391         if (oldParent != null) {
392             boolean removed = oldParent.removeChild(node);
393             assert removed == true;
394             node.setParent(null);
395             correctSameNameSiblingIndexes(context, oldParent, oldName);
396         }
397         node.setParent(newParent);
398         Name newName = oldName;
399         if (desiredNewName != null) {
400             newName = desiredNewName;
401             node.setName(context.getValueFactories().getPathFactory().createSegment(desiredNewName, 1));
402         }
403 
404         if (beforeNode == null) {
405             newParent.addChild(node);
406         } else {
407             int index = newParent.getChildren().indexOf(beforeNode);
408             newParent.addChild(index, node);
409         }
410         correctSameNameSiblingIndexes(context, newParent, newName);
411 
412         // If the node was moved to a new workspace...
413         if (!this.equals(newAbstractMapWorkspace)) {
414             // We need to remove the node from this workspace's map of nodes ...
415             this.moveNodeToWorkspace(node, newAbstractMapWorkspace);
416         }
417     }
418 
419     /**
420      * Moves the branch rooted at the given node to the new workspace, removing it from this workspace. If a node with any of the
421      * UUIDs in the branch already exists in the new workspace, it will be replaced by the node from the branch.
422      * 
423      * @param node the root node of the branch to be moved
424      * @param newWorkspace the workspace to which the branch should be moved
425      */
426     protected void moveNodeToWorkspace( MapNode node,
427                                         AbstractMapWorkspace newWorkspace ) {
428         assert getNode(node.getUuid()) != null;
429         assert newWorkspace.getNode(node.getUuid()) == null;
430 
431         removeNodeFromMap(node.getUuid());
432         newWorkspace.addNodeToMap(node);
433 
434         for (MapNode child : node.getChildren()) {
435             moveNodeToWorkspace(child, newWorkspace);
436         }
437     }
438 
439     /**
440      * This should copy the subgraph given by the original node and place the new copy under the supplied new parent. Note that
441      * internal references between nodes within the original subgraph must be reflected as internal nodes within the new subgraph.
442      * 
443      * @param context the context; may not be null
444      * @param original the node to be copied; may not be null
445      * @param newWorkspace the workspace containing the new parent node; may not be null
446      * @param newParent the parent where the copy is to be placed; may not be null
447      * @param desiredName the desired name for the node; if null, the name will be obtained from the original node
448      * @param recursive true if the copy should be recursive
449      * @return the new node, which is the top of the new subgraph
450      */
451     public MapNode copyNode( ExecutionContext context,
452                              MapNode original,
453                              MapWorkspace newWorkspace,
454                              MapNode newParent,
455                              Name desiredName,
456                              boolean recursive ) {
457 
458         Map<UUID, UUID> oldToNewUuids = new HashMap<UUID, UUID>();
459         MapNode copyRoot = copyNode(context, original, newWorkspace, newParent, desiredName, true, oldToNewUuids);
460 
461         // Now, adjust any references in the new subgraph to objects in the original subgraph
462         // (because they were internal references, and need to be internal to the new subgraph)
463         PropertyFactory propertyFactory = context.getPropertyFactory();
464         UuidFactory uuidFactory = context.getValueFactories().getUuidFactory();
465         ValueFactory<Reference> referenceFactory = context.getValueFactories().getReferenceFactory();
466         for (Map.Entry<UUID, UUID> oldToNew : oldToNewUuids.entrySet()) {
467             MapNode oldNode = this.getNode(oldToNew.getKey());
468             MapNode newNode = newWorkspace.getNode(oldToNew.getValue());
469             assert oldNode != null;
470             assert newNode != null;
471             // Iterate over the properties of the new ...
472             for (Map.Entry<Name, Property> entry : newNode.getProperties().entrySet()) {
473                 Property property = entry.getValue();
474                 // Now see if any of the property values are references ...
475                 List<Object> newValues = new ArrayList<Object>();
476                 boolean foundReference = false;
477                 for (Iterator<?> iter = property.getValues(); iter.hasNext();) {
478                     Object value = iter.next();
479                     PropertyType type = PropertyType.discoverType(value);
480                     if (type == PropertyType.REFERENCE) {
481                         UUID oldReferencedUuid = uuidFactory.create(value);
482                         UUID newReferencedUuid = oldToNewUuids.get(oldReferencedUuid);
483                         if (newReferencedUuid != null) {
484                             newValues.add(referenceFactory.create(newReferencedUuid));
485                             foundReference = true;
486                         }
487                     } else {
488                         newValues.add(value);
489                     }
490                 }
491                 // If we found at least one reference, we have to build a new Property object ...
492                 if (foundReference) {
493                     Property newProperty = propertyFactory.create(property.getName(), newValues);
494                     entry.setValue(newProperty);
495                 }
496             }
497         }
498         return copyRoot;
499     }
500 
501     /**
502      * This should copy the subgraph given by the original node and place the new copy under the supplied new parent. Note that
503      * internal references between nodes within the original subgraph must be reflected as internal nodes within the new subgraph.
504      * 
505      * @param context the context; may not be null
506      * @param original the node to be copied; may not be null
507      * @param newWorkspace the workspace containing the new parent node; may not be null
508      * @param newParent the parent where the copy is to be placed; may not be null
509      * @param desiredName the desired name for the node; if null, the name will be obtained from the original node
510      * @param recursive true if the copy should be recursive
511      * @param oldToNewUuids the map of UUIDs of nodes in the new subgraph keyed by the UUIDs of nodes in the original; may be null
512      *        if the UUIDs are to be maintained
513      * @return the new node, which is the top of the new subgraph
514      */
515     protected MapNode copyNode( ExecutionContext context,
516                                 MapNode original,
517                                 MapWorkspace newWorkspace,
518                                 MapNode newParent,
519                                 Name desiredName,
520                                 boolean recursive,
521                                 Map<UUID, UUID> oldToNewUuids ) {
522         assert context != null;
523         assert original != null;
524         assert newParent != null;
525         assert newWorkspace != null;
526         boolean reuseUuids = oldToNewUuids == null;
527 
528         // Get or create the new node ...
529         Name childName = desiredName != null ? desiredName : original.getName().getName();
530         UUID uuidForCopy = reuseUuids ? original.getUuid() : UUID.randomUUID();
531 
532         MapNode copy = newWorkspace.createNode(context, newParent, childName, uuidForCopy, original.getProperties().values());
533 
534         if (!reuseUuids) {
535             assert oldToNewUuids != null;
536             oldToNewUuids.put(original.getUuid(), copy.getUuid());
537         }
538 
539         if (recursive) {
540             // Loop over each child and call this method ...
541             for (MapNode child : original.getChildren()) {
542                 copyNode(context, child, newWorkspace, copy, null, true, oldToNewUuids);
543             }
544         }
545 
546         return copy;
547     }
548 
549     /**
550      * {@inheritDoc}
551      * 
552      * @see MapWorkspace#cloneNode(ExecutionContext, MapNode, MapWorkspace, MapNode, Name, Segment, boolean, Set)
553      */
554     public MapNode cloneNode( ExecutionContext context,
555                               MapNode original,
556                               MapWorkspace newWorkspace,
557                               MapNode newParent,
558                               Name desiredName,
559                               Segment desiredSegment,
560                               boolean removeExisting,
561                               Set<Location> removedExistingNodes ) throws UuidAlreadyExistsException {
562         assert context != null;
563         assert original != null;
564         assert newWorkspace != null;
565         assert newParent != null;
566 
567         Set<UUID> uuidsInFromBranch = getUuidsUnderNode(original);
568         MapNode existing;
569 
570         PathFactory pathFactory = context.getValueFactories().getPathFactory();
571 
572         // TODO: Need to handle removing/throwing root node
573         if (removeExisting) {
574             // Remove all of the nodes that have a UUID from under the original node, but DO NOT yet remove
575             // a node that has the UUID of the original node (as this will be handled later) ...
576             for (UUID uuid : uuidsInFromBranch) {
577                 if (null != (existing = newWorkspace.getNode(uuid))) {
578                     if (removedExistingNodes != null) {
579                         Path path = pathFor(pathFactory, existing);
580                         removedExistingNodes.add(Location.create(path, uuid));
581                     }
582                     newWorkspace.removeNode(context, existing);
583                 }
584             }
585         } else {
586             uuidsInFromBranch.add(original.getUuid()); // uuidsInFromBranch does not include the UUID of the original
587             for (UUID uuid : uuidsInFromBranch) {
588                 if (null != (existing = newWorkspace.getNode(uuid))) {
589                     NamespaceRegistry namespaces = context.getNamespaceRegistry();
590                     String path = newWorkspace.pathFor(pathFactory, existing).getString(namespaces);
591                     throw new UuidAlreadyExistsException(repository.getSourceName(), uuid, path, newWorkspace.getName());
592                 }
593             }
594         }
595 
596         if (desiredSegment != null) {
597             MapNode newRoot = null;
598             for (MapNode child : newParent.getChildren()) {
599                 if (desiredSegment.equals(child.getName())) {
600                     newRoot = child;
601                     break;
602                 }
603             }
604 
605             assert newRoot != null;
606 
607             newRoot.getProperties().clear();
608             newRoot.setProperties(original.getProperties().values());
609 
610             newRoot.clearChildren();
611 
612             assert newRoot.getChildren().isEmpty();
613 
614             for (MapNode child : original.getChildren()) {
615                 copyNode(context, child, newWorkspace, newRoot, null, true, (Map<UUID, UUID>)null);
616             }
617             return newRoot;
618         }
619 
620         // Now deal with an existing node that has the same UUID as the original node ...
621         existing = newWorkspace.getNode(original.getUuid());
622         if (existing != null) {
623             if (removedExistingNodes != null) {
624                 Path path = pathFor(pathFactory, existing);
625                 removedExistingNodes.add(Location.create(path, original.getUuid()));
626             }
627             newWorkspace.removeNode(context, existing);
628         }
629         return copyNode(context, original, newWorkspace, newParent, desiredName, true, (Map<UUID, UUID>)null);
630     }
631 
632     /**
633      * {@inheritDoc}
634      * 
635      * @see org.modeshape.graph.connector.map.MapWorkspace#query(org.modeshape.graph.ExecutionContext,
636      *      org.modeshape.graph.request.AccessQueryRequest)
637      */
638     public QueryResults query( ExecutionContext context,
639                                AccessQueryRequest accessQuery ) {
640         return null;
641     }
642 
643     /**
644      * {@inheritDoc}
645      * 
646      * @see org.modeshape.graph.connector.map.MapWorkspace#search(org.modeshape.graph.ExecutionContext, java.lang.String)
647      */
648     public QueryResults search( ExecutionContext context,
649                                 String fullTextSearchExpression ) {
650         return null;
651     }
652 
653     /**
654      * Returns all of the UUIDs in the branch rooted at {@code node}. The UUID of {@code node} <i>will</i> be included in the set
655      * of returned UUIDs.
656      * 
657      * @param node the root of the branch
658      * @return all of the UUIDs in the branch rooted at {@code node}
659      */
660     public Set<UUID> getUuidsUnderNode( MapNode node ) {
661         Set<UUID> uuids = new HashSet<UUID>();
662         uuidsUnderNode(node, uuids);
663         return uuids;
664     }
665 
666     private void uuidsUnderNode( MapNode node,
667                                  Set<UUID> accumulator ) {
668         for (MapNode child : node.getChildren()) {
669             accumulator.add(child.getUuid());
670             uuidsUnderNode(child, accumulator);
671         }
672     }
673 
674     /**
675      * Corrects the SNS indices for all children of the node with the given name
676      * 
677      * @param context the execution context
678      * @param parentNode the parent node
679      * @param name the name of the child nodes for which the SNS indices should be recalculated
680      */
681     protected void correctSameNameSiblingIndexes( ExecutionContext context,
682                                                   MapNode parentNode,
683                                                   Name name ) {
684         if (parentNode == null) return;
685         // Look for the highest existing index ...
686         List<MapNode> childrenWithSameNames = new LinkedList<MapNode>();
687         for (MapNode child : parentNode.getChildren()) {
688             if (child.getName().getName().equals(name)) childrenWithSameNames.add(child);
689         }
690         if (childrenWithSameNames.size() == 0) return;
691         if (childrenWithSameNames.size() == 1) {
692             MapNode childWithSameName = childrenWithSameNames.get(0);
693             Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, Path.DEFAULT_INDEX);
694             childWithSameName.setName(newName);
695             return;
696         }
697         int index = 1;
698         for (MapNode childWithSameName : childrenWithSameNames) {
699             Path.Segment segment = childWithSameName.getName();
700             if (segment.getIndex() != index) {
701                 Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, index);
702                 childWithSameName.setName(newName);
703             }
704             ++index;
705         }
706     }
707 
708     /**
709      * {@inheritDoc}
710      * 
711      * @see java.lang.Object#equals(java.lang.Object)
712      */
713     @Override
714     public boolean equals( Object obj ) {
715         if (obj == this) return true;
716         if (obj instanceof AbstractMapWorkspace) {
717             AbstractMapWorkspace that = (AbstractMapWorkspace)obj;
718             // Assume the workspaces are in the same repository ...
719             if (!this.name.equals(that.name)) return false;
720             return true;
721         }
722         return false;
723     }
724 
725     @Override
726     public int hashCode() {
727         return HashCode.compute(getName());
728     }
729 
730     /**
731      * {@inheritDoc}
732      * 
733      * @see java.lang.Object#toString()
734      */
735     @Override
736     public String toString() {
737         return repository.getSourceName() + "/" + this.getName();
738     }
739 }