[dna-commits] DNA SVN: r782 - in trunk/dna-jcr/src: test/java/org/jboss/dna/jcr/cache and 1 other directory.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Wed Mar 18 12:54:55 EDT 2009


Author: rhauch
Date: 2009-03-18 12:54:54 -0400 (Wed, 18 Mar 2009)
New Revision: 782

Added:
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/InternalChildren.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedNodeInfoTest.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/IsNodeInfoWithChildrenHavingNames.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/IsNodeInfoWithChildrenHavingUuids.java
Modified:
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedChildren.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChildNode.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/EmptyChildren.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableChildren.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableNodeInfo.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/NodeInfo.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedChildrenTest.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ImmutableChildrenTest.java
Log:
DNA-194 Implement update JCR capability

Improved the mutable versions of the cached reprentations, and added a large number of unit tests.  The mutable objects should be ready to track adding/removing children and adding/changing/removing properties.  Making the SessionCache to use them is next.

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedChildren.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedChildren.java	2009-03-18 04:38:44 UTC (rev 781)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedChildren.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -68,31 +68,34 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.jcr.cache.ImmutableChildren#without(org.jboss.dna.jcr.cache.ChildNode,
-     *      org.jboss.dna.graph.property.PathFactory)
+     * @see org.jboss.dna.jcr.cache.ImmutableChildren#without(java.util.UUID, org.jboss.dna.graph.property.PathFactory)
      */
     @Override
-    public ChangedChildren without( ChildNode child,
+    public ChangedChildren without( UUID childUuid,
                                     PathFactory pathFactory ) {
-        // Make sure this object contains the child ...
-        if (!childrenByUuid.containsKey(child.getUuid())) {
+        // Remove the object that has the same UUID (regardless of the current SNS index) ...
+        ChildNode toBeRemoved = childrenByUuid.get(childUuid);
+        if (toBeRemoved == null) {
             return this;
         }
-        // Remove the child fro this object, then adjust the remaining child node instances that follow it ...
-        Name childName = child.getName();
+        // Remove the child from this object, then adjust the remaining child node instances that follow it ...
+        Name childName = toBeRemoved.getName();
         List<ChildNode> childrenWithSameName = childrenByName.get(childName);
-        int snsIndex = child.getSnsIndex();
+        int snsIndex = toBeRemoved.getSnsIndex();
         if (snsIndex > childrenWithSameName.size()) {
             // The child node (with that SNS index) is no longer here) ...
             return this;
         }
         ListIterator<ChildNode> iter = childrenWithSameName.listIterator(--snsIndex);
         assert iter.hasNext();
-        iter.next(); // start ...
+        ChildNode willBeRemoved = iter.next();
+        assert willBeRemoved == toBeRemoved;
+        childrenByUuid.remove(toBeRemoved.getUuid());
         iter.remove(); // removes the item that was last returned from 'next()'
         while (iter.hasNext()) {
             ChildNode next = iter.next();
             ChildNode newNext = next.with(pathFactory.createSegment(childName, ++snsIndex));
+            childrenByUuid.put(newNext.getUuid(), newNext);
             iter.set(newNext);
         }
         return this;

Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java	                        (rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -0,0 +1,323 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * Unless otherwise indicated, all code in JBoss DNA is licensed
+ * to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.cache;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.Path.Segment;
+import org.jboss.dna.jcr.NodeDefinitionId;
+
+/**
+ * The information that describes a node. This is the information that is kept in the cache.
+ * <p>
+ * Each instance maintains a reference to the original (usually immutable) NodeInfo representation that was probably read from the
+ * repository.
+ */
+ at Immutable
+public class ChangedNodeInfo implements NodeInfo {
+
+    protected static final PropertyInfo DELETED_PROPERTY = null;
+
+    /**
+     * A reference to the original representation of the node.
+     */
+    private final NodeInfo original;
+
+    /**
+     * The new parent for this node if it has been changed, or null if the parent has not be changed.
+     */
+    private UUID newParent;
+
+    /**
+     * The updated children, or null if the children have not been changed from the original's.
+     */
+    private ChangedChildren changedChildren;
+
+    /**
+     * This map, if it is non-null, contains the changed properties, overriding whatever is in the original. If a property is
+     * removed from the original, an entry is added to this map with the name of the removed property and a null PropertyInfo.
+     */
+    private Map<Name, PropertyInfo> changedProperties;
+
+    /**
+     * Create an immutable NodeInfo instance.
+     * 
+     * @param original the original node information, may not be null
+     */
+    public ChangedNodeInfo( NodeInfo original ) {
+        assert original != null;
+        this.original = original;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#getOriginalLocation()
+     */
+    public Location getOriginalLocation() {
+        return original.getOriginalLocation();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#getUuid()
+     */
+    public UUID getUuid() {
+        return original.getUuid();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#getParent()
+     */
+    public UUID getParent() {
+        // Even if this is used for recording changes to the root node (which has no parent),
+        // the root node cannot be moved to a different node (and no other node can be moved to
+        // the root). Therefore, if this represents the root node, the original's parent UUID will
+        // be the correct parent (null), and this representation will not need to have a different
+        // (non-null) value.
+        if (newParent != null) return newParent;
+        return original.getParent();
+    }
+
+    /**
+     * Record that this node has been moved under a new parent. This method does <i>not</i> change the ChildNode references in the
+     * old or new parent.
+     * 
+     * @param parent the new parent, or null if the original's parent should be used
+     * @return the previous parent (either the original's or the last new parent); may be null
+     */
+    public UUID setParent( UUID parent ) {
+        UUID result = newParent != null ? newParent : original.getParent(); // may still be null
+        newParent = parent;
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#getPrimaryTypeName()
+     */
+    public Name getPrimaryTypeName() {
+        return original.getPrimaryTypeName();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#getDefinitionId()
+     */
+    public NodeDefinitionId getDefinitionId() {
+        return original.getDefinitionId();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#getChildren()
+     */
+    public Children getChildren() {
+        if (changedChildren != null) return changedChildren;
+        return original.getChildren();
+    }
+
+    /**
+     * Add a child to the children. This method does nothing if the child is already in the children.
+     * 
+     * @param childName the name of the child that is to be added; may not be null
+     * @param childUuid the UUID of the child that is to be added; may not be null
+     * @param factory the path factory that should be used to create a {@link Segment} for the new {@link ChildNode} object
+     * @return the child node that was just added; never null
+     */
+    public ChildNode addChild( Name childName,
+                               UUID childUuid,
+                               PathFactory factory ) {
+        if (changedChildren == null) {
+            // We need to capture the original children as a changed contained ...
+            changedChildren = new ChangedChildren(original.getChildren());
+        }
+        return changedChildren.add(childName, childUuid, factory);
+    }
+
+    /**
+     * Remove a child from the children. This method only uses the child's UUID to identify the contained ChildNode instance that
+     * should be removed.
+     * <p>
+     * Note that this method returns the new {@link Children} container, which is the same as would be returned by
+     * {@link #getChildren()} called immediately after this method.
+     * </p>
+     * 
+     * @param childUUID the UUID of the child that is to be removed; may not be null
+     * @param factory the path factory that should be used to create a {@link Segment} for replacement {@link ChildNode} objects
+     *        for nodes with the same name that and higher same-name-sibiling indexes.
+     * @return the Children object that has the modified children
+     */
+    public Children removeChild( UUID childUUID,
+                                 PathFactory factory ) {
+        if (changedChildren == null) {
+            // Create the changed children. First check whether there are 0 or 1 child ...
+            Children existing = original.getChildren();
+            int numExisting = existing.size();
+            if (numExisting == 0) {
+                // nothing to do, so return the original's children
+                return existing;
+            }
+            if (existing.getChild(childUUID) == null) {
+                // The requested child doesn't exist in the children, so return silently ...
+                return existing;
+            }
+            if (numExisting == 1) {
+                // We're removing the only child in the original ...
+                changedChildren = new ChangedChildren(existing.getParentUuid());
+                return changedChildren;
+            }
+            // There is at least one child, so create the new children container ...
+            assert existing instanceof InternalChildren;
+            InternalChildren internal = (InternalChildren)existing;
+            changedChildren = internal.without(childUUID, factory);
+        } else {
+            changedChildren = changedChildren.without(childUUID, factory);
+        }
+        return changedChildren;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#hasProperties()
+     */
+    public boolean hasProperties() {
+        if (changedProperties == null) return original.hasProperties();
+        int numUnchanged = original.getPropertyCount();
+        int numChangedOrDeleted = changedProperties.size();
+        if (numUnchanged > numChangedOrDeleted) return true; // more unchanged than could be deleted
+        // They could all be changed or deleted, so we need to find one changed property ...
+        for (Map.Entry<Name, PropertyInfo> entry : changedProperties.entrySet()) {
+            if (entry.getValue() != DELETED_PROPERTY) return true;
+        }
+        return false; // all properties must have been deleted ...
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#getPropertyCount()
+     */
+    public int getPropertyCount() {
+        int numUnchanged = original.getPropertyCount();
+        if (changedProperties == null) return numUnchanged;
+        return getPropertyNames().size();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#getPropertyNames()
+     */
+    public Set<Name> getPropertyNames() {
+        if (changedProperties != null) {
+            Set<Name> result = new HashSet<Name>(original.getPropertyNames());
+            for (Map.Entry<Name, PropertyInfo> entry : changedProperties.entrySet()) {
+                if (entry.getValue() != DELETED_PROPERTY) {
+                    result.add(entry.getKey());
+                } else {
+                    result.remove(entry.getKey());
+                }
+            }
+            return result; // don't make unmod wrapper, since we've already made a copy ...
+        }
+        return original.getPropertyNames();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#getProperty(org.jboss.dna.graph.property.Name)
+     */
+    public PropertyInfo getProperty( Name name ) {
+        if (changedProperties != null && changedProperties.containsKey(name)) {
+            return changedProperties.get(name); // either the changed PropertyInfo, or null if property was deleted
+        }
+        return original.getProperty(name);
+    }
+
+    public PropertyInfo setProperty( PropertyInfo newProperty ) {
+        Name name = newProperty.getPropertyName();
+        if (changedProperties == null) {
+            // There were no changes made yet ...
+
+            // Create the map of changed properties ...
+            changedProperties = new HashMap<Name, PropertyInfo>();
+            changedProperties.put(name, newProperty);
+
+            // And return the original property (or null if there was none) ...
+            return original.getProperty(name);
+        }
+        // The property may already have been changed, in which case we need to return the changed one ...
+        if (changedProperties.containsKey(name)) {
+            PropertyInfo changed = changedProperties.put(name, null);
+            // The named property was indeed deleted ...
+            return changed;
+        }
+        // Otherwise, the property was not yet changed or deleted ...
+        PropertyInfo changed = original.getProperty(name);
+        changedProperties.put(name, newProperty);
+        return changed;
+    }
+
+    public PropertyInfo removeProperty( Name name ) {
+        if (changedProperties == null) {
+            // Make sure the property was in the original ...
+            PropertyInfo existing = original.getProperty(name);
+            if (existing == null) {
+                // The named property didn't exist in the original, nor was it added and deleted in this object ...
+                return null;
+            }
+
+            // Create the map of changed properties ...
+            changedProperties = new HashMap<Name, PropertyInfo>();
+            changedProperties.put(name, DELETED_PROPERTY);
+            return existing;
+        }
+        // The property may already have been changed, in which case we need to return the changed one ...
+        if (changedProperties.containsKey(name)) {
+            PropertyInfo changed = changedProperties.put(name, null);
+            // The named property was indeed deleted ...
+            return changed;
+        }
+        // Otherwise, the property was not yet changed or deleted ...
+        PropertyInfo changed = original.getProperty(name);
+        changedProperties.put(name, null);
+        return changed;
+    }
+}


Property changes on: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChildNode.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChildNode.java	2009-03-18 04:38:44 UTC (rev 781)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChildNode.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -29,13 +29,19 @@
 import org.jboss.dna.graph.property.Path;
 
 /**
- * 
+ * The representation of a child node. This is an immutable representation of a child node within the collection of its siblings
+ * as the collection appeared at some point in time. This should be used as a guide to determine how long to hold onto references.
+ * <p>
+ * For example, adding and removing children may affect the {@link #getSnsIndex() same-name-sibling index} of the children, so
+ * these kinds of operations will result in the replacement of old ChildObject instances. Therefore, clients should generally find
+ * the ChildNode instances in a {@link Children} container, use the ChildNode objects quickly, then discard their references.
+ * </p>
+ * <p>
+ * There may be times when a client does wish to keep a representation of a ChildNode as it appeared at some moment in time, and
+ * so it may want to hold onto references to ChildNode objects for longer durations. This is fine, as long as it is understood
+ * that at some point the referenced ChildNode may no longer represent the current state.
+ * </p>
  */
-/**
- * The information about a child node. This is designed to be found in the {@link Children}, used quickly, and discarded. Clients
- * should not hold on to these objects, since any changes to the children involve discarding the old ChildNode objects and
- * replacing them with new instances.
- */
 @Immutable
 public final class ChildNode {
 

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/EmptyChildren.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/EmptyChildren.java	2009-03-18 04:38:44 UTC (rev 781)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/EmptyChildren.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -35,7 +35,7 @@
  * An immutable implementation of {@link Children}.
  */
 @Immutable
-public final class EmptyChildren implements Children {
+public final class EmptyChildren implements Children, InternalChildren {
 
     static final Iterator<ChildNode> EMPTY_ITERATOR = new EmptyIterator<ChildNode>();
 
@@ -100,12 +100,10 @@
     }
 
     /**
-     * Create another Children object that is equivalent to this node but with the supplied child added.
+     * {@inheritDoc}
      * 
-     * @param newChildName the name of the new child; may not be null
-     * @param newChildUuid the UUID of the new child; may not be null
-     * @param pathFactory the factory that can be used to create Path and/or Path.Segment instances.
-     * @return the new Children object; never null
+     * @see org.jboss.dna.jcr.cache.InternalChildren#with(org.jboss.dna.graph.property.Name, java.util.UUID,
+     *      org.jboss.dna.graph.property.PathFactory)
      */
     public ChangedChildren with( Name newChildName,
                                  UUID newChildUuid,
@@ -116,14 +114,11 @@
     }
 
     /**
-     * Create another Children object that is equivalent to this node but without the supplied child. If the supplied child is not
-     * a current child, this method silently returns this same instance (since it has not changed).
+     * {@inheritDoc}
      * 
-     * @param child the child to be removed; may not be null
-     * @param pathFactory the factory that can be used to create Path and/or Path.Segment instances.
-     * @return the new Children object; never null
+     * @see org.jboss.dna.jcr.cache.InternalChildren#without(java.util.UUID, org.jboss.dna.graph.property.PathFactory)
      */
-    public ChangedChildren without( ChildNode child,
+    public ChangedChildren without( UUID childUuid,
                                     PathFactory pathFactory ) {
         return new ChangedChildren(this.parentUuid);
     }

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableChildren.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableChildren.java	2009-03-18 04:38:44 UTC (rev 781)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableChildren.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -42,7 +42,7 @@
  * An immutable implementation of {@link Children}.
  */
 @Immutable
-public class ImmutableChildren implements Children {
+public class ImmutableChildren implements Children, InternalChildren {
     protected final UUID parentUuid;
     protected final Map<UUID, ChildNode> childrenByUuid;
     protected final ListMultimap<Name, ChildNode> childrenByName;
@@ -168,12 +168,10 @@
     }
 
     /**
-     * Create another Children object that is equivalent to this node but with the supplied child added.
+     * {@inheritDoc}
      * 
-     * @param newChildName the name of the new child; may not be null
-     * @param newChildUuid the UUID of the new child; may not be null
-     * @param pathFactory the factory that can be used to create Path and/or Path.Segment instances.
-     * @return the new Children object; never null
+     * @see org.jboss.dna.jcr.cache.InternalChildren#with(org.jboss.dna.graph.property.Name, java.util.UUID,
+     *      org.jboss.dna.graph.property.PathFactory)
      */
     public ChangedChildren with( Name newChildName,
                                  UUID newChildUuid,
@@ -184,19 +182,17 @@
     }
 
     /**
-     * Create another Children object that is equivalent to this node but without the supplied child.
+     * {@inheritDoc}
      * 
-     * @param child the child to be removed; may not be null
-     * @param pathFactory the factory that can be used to create Path and/or Path.Segment instances.
-     * @return the new Children object; never null
+     * @see org.jboss.dna.jcr.cache.InternalChildren#without(java.util.UUID, org.jboss.dna.graph.property.PathFactory)
      */
-    public ChangedChildren without( ChildNode child,
+    public ChangedChildren without( UUID childUuid,
                                     PathFactory pathFactory ) {
-        if (this.childrenByUuid.containsKey(child.getUuid()) && this.size() == 1) {
+        if (this.childrenByUuid.containsKey(childUuid) && this.size() == 1) {
             return new ChangedChildren(this.parentUuid);
         }
         ChangedChildren newChildren = new ChangedChildren(this);
-        return newChildren.without(child, pathFactory);
+        return newChildren.without(childUuid, pathFactory);
     }
 
     /**

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableNodeInfo.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableNodeInfo.java	2009-03-18 04:38:44 UTC (rev 781)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ImmutableNodeInfo.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -138,6 +138,15 @@
     /**
      * {@inheritDoc}
      * 
+     * @see org.jboss.dna.jcr.cache.NodeInfo#getPropertyCount()
+     */
+    public int getPropertyCount() {
+        return this.properties.size();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
      * @see org.jboss.dna.jcr.cache.NodeInfo#getPropertyNames()
      */
     public Set<Name> getPropertyNames() {

Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/InternalChildren.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/InternalChildren.java	                        (rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/InternalChildren.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -0,0 +1,57 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * Unless otherwise indicated, all code in JBoss DNA is licensed
+ * to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.cache;
+
+import java.util.UUID;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.PathFactory;
+
+/**
+ * An internal interface for the {@link Children} implementations. Methods on this interface are used internally and should not be
+ * used by components or clients.
+ */
+interface InternalChildren extends Children {
+
+    /**
+     * Create another Children object that is equivalent to this node but with the supplied child added.
+     * 
+     * @param newChildName the name of the new child; may not be null
+     * @param newChildUuid the UUID of the new child; may not be null
+     * @param pathFactory the factory that can be used to create Path and/or Path.Segment instances.
+     * @return the new Children object; never null
+     */
+    ChangedChildren with( Name newChildName,
+                          UUID newChildUuid,
+                          PathFactory pathFactory );
+
+    /**
+     * Create another Children object that is equivalent to this node but without the supplied child.
+     * 
+     * @param childUuid the UUID of the child to be removed; may not be null
+     * @param pathFactory the factory that can be used to create Path and/or Path.Segment instances.
+     * @return the new Children object; never null
+     */
+    ChangedChildren without( UUID childUuid,
+                             PathFactory pathFactory );
+}


Property changes on: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/InternalChildren.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/NodeInfo.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/NodeInfo.java	2009-03-18 04:38:44 UTC (rev 781)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/cache/NodeInfo.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -30,7 +30,7 @@
 import org.jboss.dna.jcr.NodeDefinitionId;
 
 /**
- * The information that describes a node. This is the information that is kept in the cache.
+ * A representation of a node. This is the information that is kept in the cache.
  */
 public interface NodeInfo {
 
@@ -60,7 +60,9 @@
     public NodeDefinitionId getDefinitionId();
 
     /**
-     * Get the children for this node.
+     * Get the children for this node. Generally, clients should not hold onto the returned object but instead should simply use
+     * it and discard the reference. This is because implementations are not required to return the same instance with each call
+     * (although immutable implementations are expected to always return the same instance).
      * 
      * @return the immutable children; never null but possibly empty
      */
@@ -74,6 +76,13 @@
     public boolean hasProperties();
 
     /**
+     * Return the number of properties on this node.
+     * 
+     * @return the number of properties; never negative
+     */
+    public int getPropertyCount();
+
+    /**
      * Get the names of the properties that are owned by this node.
      * 
      * @return the unmodifiable set of property names

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedChildrenTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedChildrenTest.java	2009-03-18 04:38:44 UTC (rev 781)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedChildrenTest.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -78,12 +78,12 @@
     @Test
     public void shouldReturnSameInstanceFromWithoutIfSuppliedChildIsNotFound() {
         ChildNode nonExistant = new ChildNode(UUID.randomUUID(), pathFactory.createSegment("some segment"));
-        assertThat(children.without(nonExistant, pathFactory), is(sameInstance((Children)children)));
+        assertThat(children.without(nonExistant.getUuid(), pathFactory), is(sameInstance((Children)children)));
     }
 
     @Test
     public void shouldReturnEmptyChildrenFromWithoutIfOnlyChildIsRemoved() {
-        Children newChildren = children.without(firstChild, pathFactory);
+        Children newChildren = children.without(firstChild.getUuid(), pathFactory);
         assertThat(newChildren.size(), is(0));
     }
 
@@ -113,7 +113,7 @@
         assertChildNodesWithName(children, "childA", firstChild, child4, child5);
 
         // Remove 'child4' ...
-        Children result = children.without(child4, pathFactory);
+        Children result = children.without(child4.getUuid(), pathFactory);
 
         // but the result should not have child4 ...
         assertChildNodesWithName(result, "childA", firstChild, "childA");

Added: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedNodeInfoTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedNodeInfoTest.java	                        (rev 0)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedNodeInfoTest.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -0,0 +1,629 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * Unless otherwise indicated, all code in JBoss DNA is licensed
+ * to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.cache;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.jboss.dna.jcr.cache.IsNodeInfoWithChildrenHavingNames.hasChild;
+import static org.jboss.dna.jcr.cache.IsNodeInfoWithChildrenHavingNames.hasChildren;
+import static org.jboss.dna.jcr.cache.IsNodeInfoWithChildrenHavingUuids.hasChild;
+import static org.jboss.dna.jcr.cache.IsNodeInfoWithChildrenHavingUuids.hasChildren;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.hasItems;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.stub;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.Path.Segment;
+import org.jboss.dna.jcr.NodeDefinitionId;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * 
+ */
+public class ChangedNodeInfoTest {
+
+    private ExecutionContext context;
+    private PathFactory pathFactory;
+    private NodeInfo original;
+    private UUID uuid;
+    private Name primaryTypeName;
+    private Location location;
+    private NodeDefinitionId definitionId;
+    private ChangedChildren children;
+    private Map<Name, PropertyInfo> properties;
+    private ChangedNodeInfo changes;
+
+    @Before
+    public void beforeEach() {
+        context = new ExecutionContext();
+        context.getNamespaceRegistry().register("acme", "http://example.com/acme");
+        pathFactory = context.getValueFactories().getPathFactory();
+
+        // Set up the original ...
+        uuid = UUID.randomUUID();
+        location = Location.create(uuid);
+        primaryTypeName = name("acme:geniusType");
+        definitionId = new NodeDefinitionId(name("acme:geniusContainerType"), name("acme:geniuses"));
+        children = new ChangedChildren(uuid);
+        properties = new HashMap<Name, PropertyInfo>();
+        original = new ImmutableNodeInfo(location, primaryTypeName, definitionId, uuid, children, properties);
+
+        // Create the changed node representation ...
+        changes = new ChangedNodeInfo(original);
+    }
+
+    protected Name name( String name ) {
+        return context.getValueFactories().getNameFactory().create(name);
+    }
+
+    protected Segment segment( String segment ) {
+        return pathFactory.createSegment(segment);
+    }
+
+    /**
+     * Utility to set a property to the original node representation. This will replace any existing property with the same name.
+     * 
+     * @param name the name of the property; may not be null
+     * @return the new property representation; never null
+     */
+    protected PropertyInfo makePropertyInOriginal( String name ) {
+        Name propName = name(name);
+        PropertyInfo propertyInfo = mock(PropertyInfo.class);
+        stub(propertyInfo.getPropertyName()).toReturn(propName);
+        properties.put(propName, propertyInfo);
+        return propertyInfo;
+    }
+
+    /**
+     * Utility to change a property in the changed representation.
+     * 
+     * @param name the name of the property to change; may not be null
+     * @return the new property; never null
+     */
+    protected PropertyInfo setPropertyInChanged( String name ) {
+        Name propName = name(name);
+        PropertyInfo propertyInfo = mock(PropertyInfo.class);
+        stub(propertyInfo.getPropertyName()).toReturn(propName);
+        changes.setProperty(propertyInfo);
+        return propertyInfo;
+    }
+
+    protected ChildNode makeChildInOriginal( String childName ) {
+        ChildNode newChild = children.add(name(childName), UUID.randomUUID(), pathFactory);
+        return newChild;
+    }
+
+    protected ChildNode addChildInChanged( String childName ) {
+        ChildNode newChild = changes.addChild(name(childName), UUID.randomUUID(), pathFactory);
+        // Make sure that the 'changes' object no longer returns the original object's children object
+        assertThat(changes.getChildren(), is(not(sameInstance(original.getChildren()))));
+        return newChild;
+    }
+
+    protected void removeChildFromChanged( ChildNode child ) {
+        // Verify that a child node with the supplied UUID is contained.
+        // Note that it may not be the same ChildNode instance if another SNS node with smaller index was removed
+        assertThat(changes.getChildren().getChild(child.getUuid()), is(notNullValue()));
+        // Now remove the child, making sure the result is the same as the next 'getChildren()' call ...
+        assertThat(changes.removeChild(child.getUuid(), pathFactory), is(sameInstance(changes.getChildren())));
+        // Verify it no longer exists ...
+        assertThat(changes.getChildren().getChild(child.getUuid()), is(nullValue()));
+    }
+
+    @Test
+    public void shouldHaveLocationFromOriginal() {
+        assertThat(changes.getOriginalLocation(), is(sameInstance(location)));
+    }
+
+    @Test
+    public void shouldHaveParentUuidFromOriginal() {
+        assertThat(changes.getParent(), is(sameInstance(uuid)));
+    }
+
+    @Test
+    public void shouldHavePrimaryTypeNameFromOriginal() {
+        assertThat(changes.getPrimaryTypeName(), is(sameInstance(primaryTypeName)));
+    }
+
+    @Test
+    public void shouldHaveNodeDefinitionIdFromOriginal() {
+        assertThat(changes.getDefinitionId(), is(sameInstance(definitionId)));
+    }
+
+    @Test
+    public void shouldHaveChildrenFromOriginal() {
+        assertThat(changes.getChildren(), is(sameInstance((Children)children)));
+
+        ChildNode childA = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(1));
+        assertThat(changes.getChildren().getChild(childA.getUuid()), is(sameInstance(childA)));
+        assertThat(changes.getChildren(), hasChild(segment("childA[1]")));
+        assertThat(changes.getChildren(), hasChild(childA.getUuid()));
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterAddingChild() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Add child ...
+        ChildNode childA3 = addChildInChanged("childA");
+
+        // Verify that all children are there in the proper order ...
+        assertThat(changes.getChildren().size(), is(4));
+        assertThat(changes.getChildren(), hasChildren(segment("childA[1]"),
+                                                      segment("childB[1]"),
+                                                      segment("childA[2]"),
+                                                      segment("childA[3]")));
+        assertThat(changes.getChildren(), hasChildren(childA1.getUuid(), childB1.getUuid(), childA2.getUuid(), childA3.getUuid()));
+        assertThat(changes.getChildren(), hasItems(childA1, childB1, childA2, childA3));
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterAddingMultipleChildren() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Add some children in the changed representation ...
+        ChildNode childA3 = addChildInChanged("childA");
+        ChildNode childC1 = addChildInChanged("childC");
+        ChildNode childC2 = addChildInChanged("childC");
+
+        // Verify that all children are there in the proper order ...
+        assertThat(changes.getChildren().size(), is(6));
+        assertThat(changes.getChildren(), hasChildren(segment("childA[1]"),
+                                                      segment("childB[1]"),
+                                                      segment("childA[2]"),
+                                                      segment("childA[3]"),
+                                                      segment("childC[1]"),
+                                                      segment("childC[2]")));
+        assertThat(changes.getChildren(), hasChildren(childA1.getUuid(),
+                                                      childB1.getUuid(),
+                                                      childA2.getUuid(),
+                                                      childA3.getUuid(),
+                                                      childC1.getUuid(),
+                                                      childC2.getUuid()));
+        assertThat(changes.getChildren(), hasItems(childA1, childB1, childA2, childA3, childC1, childC2));
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterAddingMultipleChildrenAndRemovingOthers() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Add some children in the changed representation ...
+        ChildNode childA3 = addChildInChanged("childA");
+        ChildNode childC1 = addChildInChanged("childC");
+        ChildNode childC2 = addChildInChanged("childC");
+
+        // Delete a child that was added and another that was an original ...
+        removeChildFromChanged(childC1);
+        removeChildFromChanged(childA2);
+
+        // Verify that all children are there in the proper order ...
+        assertThat(changes.getChildren().size(), is(4));
+        assertThat(changes.getChildren(), hasChildren(segment("childA[1]"),
+                                                      segment("childB[1]"),
+                                                      segment("childA[2]"),
+                                                      segment("childC[1]")));
+        assertThat(changes.getChildren(), hasChildren(childA1.getUuid(), childB1.getUuid(), childA3.getUuid(), childC2.getUuid()));
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterAddingMultipleChildrenAndThenRemovingThoseJustAdded() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Add some children in the changed representation ...
+        ChildNode childA3 = addChildInChanged("childA");
+        ChildNode childC1 = addChildInChanged("childC");
+        ChildNode childC2 = addChildInChanged("childC");
+
+        // Delete a child that was added and another that was an original...
+        removeChildFromChanged(childA3);
+        removeChildFromChanged(childC1); // causes replacement of 'childC2' with lower SNS index
+        removeChildFromChanged(childC2);
+
+        // Verify that all children are there in the proper order ...
+        assertThat(changes.getChildren().size(), is(3));
+        assertThat(changes.getChildren(), hasChildren(segment("childA[1]"), segment("childB[1]"), segment("childA[2]")));
+        assertThat(changes.getChildren(), hasChildren(childA1.getUuid(), childB1.getUuid(), childA2.getUuid()));
+
+        // Do it again, but change the order of delete to delete from the back ...
+
+        // Add some children in the changed representation ...
+        childA3 = addChildInChanged("childA");
+        childC1 = addChildInChanged("childC");
+        childC2 = addChildInChanged("childC");
+
+        // Delete a child that was added and another that was an original ...
+        removeChildFromChanged(childC2);
+        removeChildFromChanged(childC1);
+        removeChildFromChanged(childA3);
+
+        // Verify that all children are there in the proper order ...
+        assertThat(changes.getChildren().size(), is(3));
+        assertThat(changes.getChildren(), hasChildren(segment("childA[1]"), segment("childB[1]"), segment("childA[2]")));
+        assertThat(changes.getChildren(), hasChildren(childA1.getUuid(), childB1.getUuid(), childA2.getUuid()));
+
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterDeletingChild() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Delete a child that was added and another that was an original ...
+        removeChildFromChanged(childA1);
+
+        // Verify that all children are there in the proper order ...
+        assertThat(changes.getChildren().size(), is(2));
+        assertThat(changes.getChildren(), hasChildren(segment("childB[1]"), segment("childA[1]")));
+        assertThat(changes.getChildren(), hasChildren(childB1.getUuid(), childA2.getUuid()));
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterDeletingMultipleChildren() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Delete a child that was added and another that was an original ...
+        removeChildFromChanged(childA1);
+        removeChildFromChanged(childA2);
+
+        // Verify that all children are there in the proper order ...
+        assertThat(changes.getChildren().size(), is(1));
+        assertThat(changes.getChildren(), hasChildren(segment("childB[1]")));
+        assertThat(changes.getChildren(), hasChildren(childB1.getUuid()));
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterDeletingAllChildrenFromTheFirsttChildToTheLast() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Delete all children, from the front to the back ...
+        removeChildFromChanged(childA1); // causes replacement of 'childA2' with lower SNS index
+        removeChildFromChanged(childA2);
+        removeChildFromChanged(childB1);
+
+        // Verify that all children have been removed ...
+        assertThat(changes.getChildren().size(), is(0));
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterDeletingAllChildrenFromTheLastChildToTheFirst() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Delete all children, from the back to the front ...
+        removeChildFromChanged(childA2);
+        removeChildFromChanged(childB1);
+        removeChildFromChanged(childA1);
+
+        // Verify that all children have been removed ...
+        assertThat(changes.getChildren().size(), is(0));
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterAddingSomeChildrenThenDeletingAllChildrenFromTheFirstChildToTheLast() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Add some children in the changed representation ...
+        ChildNode childA3 = addChildInChanged("childA");
+        ChildNode childC1 = addChildInChanged("childC");
+        ChildNode childC2 = addChildInChanged("childC");
+
+        // Delete all children, from the front to the back ...
+        removeChildFromChanged(childA3);
+        removeChildFromChanged(childC1); // causes replacement of 'childC2' with lower SNS index
+        removeChildFromChanged(childC2);
+        removeChildFromChanged(childA1); // causes replacement of 'childA2' with lower SNS index
+        removeChildFromChanged(childB1);
+        removeChildFromChanged(childA2);
+
+        // Verify that all children have been removed ...
+        assertThat(changes.getChildren().size(), is(0));
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterAddingSomeChildrenThenDeletingAllChildrenFromTheLastChildToTheFirst() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Add some children in the changed representation ...
+        ChildNode childA3 = addChildInChanged("childA");
+        ChildNode childC1 = addChildInChanged("childC");
+        ChildNode childC2 = addChildInChanged("childC");
+
+        // Delete all children, from the back to the front ...
+        removeChildFromChanged(childC2);
+        removeChildFromChanged(childC1);
+        removeChildFromChanged(childA3);
+        removeChildFromChanged(childA2);
+        removeChildFromChanged(childB1);
+        removeChildFromChanged(childA1);
+
+        // Verify that all children have been removed ...
+        assertThat(changes.getChildren().size(), is(0));
+    }
+
+    @Test
+    public void shouldHaveChildrenAfterReorderingChildren() {
+
+    }
+
+    @Test
+    @SuppressWarnings( "unused" )
+    public void shouldNotRemoveFromNodeInfoWithNoChildChangesAChildThatMatchesSegmentButNotUuid() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Create a bogus node that has a new UUID but with the same segment as 'childA3' ...
+        Children before = changes.getChildren();
+        int beforeSize = before.size();
+        Children after = changes.removeChild(UUID.randomUUID(), pathFactory);
+        assertThat(after.size(), is(beforeSize));
+        assertThat(after, is(sameInstance(before)));
+        assertThat(after, is(sameInstance(changes.getChildren())));
+    }
+
+    @Test
+    @SuppressWarnings( "unused" )
+    public void shouldNotRemoveFromNodeInfoWithSomeChildChangesAChildThatMatchesSegmentButNotUuid() {
+        // Set up the original ...
+        ChildNode childA1 = makeChildInOriginal("childA");
+        ChildNode childB1 = makeChildInOriginal("childB");
+        ChildNode childA2 = makeChildInOriginal("childA");
+        assertThat(changes.getChildren().size(), is(3));
+
+        // Add some children in the changed representation ...
+        ChildNode childA3 = addChildInChanged("childA");
+        ChildNode childC1 = addChildInChanged("childC");
+        ChildNode childC2 = addChildInChanged("childC");
+
+        // Create a bogus node that has a new UUID but with the same segment as 'childA3' ...
+        Children before = changes.getChildren();
+        int beforeSize = before.size();
+        Children after = changes.removeChild(UUID.randomUUID(), pathFactory);
+        assertThat(after.size(), is(beforeSize));
+        assertThat(after, is(sameInstance(before)));
+        assertThat(after, is(sameInstance(changes.getChildren())));
+    }
+
+    @Test
+    public void shouldFindPropertyThatHasNotBeenModifiedButIsInOriginal() {
+        PropertyInfo propertyInfo = makePropertyInOriginal("test");
+        assertThat(changes.getProperty(name("test")), is(sameInstance(propertyInfo)));
+    }
+
+    @Test
+    public void shouldFindPropertyThatHasBeenModifiedFromOriginal() {
+        PropertyInfo propertyInfo = makePropertyInOriginal("test");
+        assertThat(changes.getProperty(name("test")), is(sameInstance(propertyInfo)));
+
+        // Modify the property ...
+        PropertyInfo newPropertyInfo = mock(PropertyInfo.class);
+        stub(newPropertyInfo.getPropertyName()).toReturn(name("test"));
+        PropertyInfo previous = changes.setProperty(newPropertyInfo);
+        assertThat(previous, is(sameInstance(propertyInfo)));
+
+        // Verify we can find the new property ...
+        assertThat(changes.getProperty(name("test")), is(sameInstance(newPropertyInfo)));
+    }
+
+    @Test
+    public void shouldNotFindPropertyThatHasBeenDeletedFromOriginal() {
+        PropertyInfo propertyInfo = makePropertyInOriginal("test");
+        assertThat(changes.getProperty(name("test")), is(sameInstance(propertyInfo)));
+
+        // Delete the property ...
+        PropertyInfo previous = changes.removeProperty(name("test"));
+        assertThat(previous, is(sameInstance(propertyInfo)));
+
+        // Verify we can not find the new property ...
+        assertThat(changes.getProperty(name("test")), is(nullValue()));
+    }
+
+    @Test
+    public void shouldNotFindPropertyThatIsNotInOriginal() {
+        assertThat(changes.getProperty(name("test")), is(nullValue()));
+
+        makePropertyInOriginal("test");
+        assertThat(changes.getProperty(name("nonExistant")), is(nullValue()));
+    }
+
+    @Test
+    public void shouldFindAllPropertyNamesWhenChangedNodeHasNoChangedProperties() {
+        PropertyInfo propA = makePropertyInOriginal("propA");
+        PropertyInfo propB = makePropertyInOriginal("propB");
+        PropertyInfo propC = makePropertyInOriginal("propC");
+        PropertyInfo propD = makePropertyInOriginal("propD");
+        Set<Name> names = changes.getPropertyNames();
+        assertThat(names.size(), is(4));
+        assertThat(names, hasItems(propA.getPropertyName(),
+                                   propB.getPropertyName(),
+                                   propC.getPropertyName(),
+                                   propD.getPropertyName()));
+    }
+
+    @Test
+    public void shouldFindAllPropertyNamesWhenChangedNodeHasDeletedAllProperties() {
+        PropertyInfo propA = makePropertyInOriginal("propA");
+        PropertyInfo propB = makePropertyInOriginal("propB");
+        PropertyInfo propC = makePropertyInOriginal("propC");
+        PropertyInfo propD = makePropertyInOriginal("propD");
+        // Remove all properties ...
+        assertThat(changes.removeProperty(propA.getPropertyName()), is(sameInstance(propA)));
+        assertThat(changes.removeProperty(propB.getPropertyName()), is(sameInstance(propB)));
+        assertThat(changes.removeProperty(propC.getPropertyName()), is(sameInstance(propC)));
+        assertThat(changes.removeProperty(propD.getPropertyName()), is(sameInstance(propD)));
+        // Now ask for names
+        Set<Name> names = changes.getPropertyNames();
+        assertThat(names.size(), is(0));
+    }
+
+    @Test
+    public void shouldFindAllPropertyNamesWhenChangedNodeHasDeletedSomeProperties() {
+        PropertyInfo propA = makePropertyInOriginal("propA");
+        PropertyInfo propB = makePropertyInOriginal("propB");
+        PropertyInfo propC = makePropertyInOriginal("propC");
+        PropertyInfo propD = makePropertyInOriginal("propD");
+        // Remove some properties ...
+        assertThat(changes.removeProperty(propB.getPropertyName()), is(sameInstance(propB)));
+        assertThat(changes.removeProperty(propD.getPropertyName()), is(sameInstance(propD)));
+        // Now ask for names
+        Set<Name> names = changes.getPropertyNames();
+        assertThat(names.size(), is(2));
+        assertThat(names, hasItems(propA.getPropertyName(), propC.getPropertyName()));
+    }
+
+    @Test
+    @SuppressWarnings( "unused" )
+    public void shouldFindAllPropertyNamesWhenChangedNodeHasChangedSomeProperties() {
+        PropertyInfo propA = makePropertyInOriginal("propA");
+        PropertyInfo propB = makePropertyInOriginal("propB");
+        PropertyInfo propC = makePropertyInOriginal("propC");
+        PropertyInfo propD = makePropertyInOriginal("propD");
+        // Change some properties ...
+        PropertyInfo propB2 = setPropertyInChanged("propB");
+        PropertyInfo propC2 = setPropertyInChanged("propC");
+        // Now ask for names
+        Set<Name> names = changes.getPropertyNames();
+        assertThat(names.size(), is(4));
+        assertThat(names, hasItems(propA.getPropertyName(),
+                                   propB2.getPropertyName(),
+                                   propC2.getPropertyName(),
+                                   propD.getPropertyName()));
+    }
+
+    @Test
+    @SuppressWarnings( "unused" )
+    public void shouldFindAllPropertyNamesWhenChangedNodeHasAddedSomeProperties() {
+        PropertyInfo propA = makePropertyInOriginal("propA");
+        PropertyInfo propB = makePropertyInOriginal("propB");
+        PropertyInfo propC = makePropertyInOriginal("propC");
+        PropertyInfo propD = makePropertyInOriginal("propD");
+        // Add some properties ...
+        PropertyInfo propE = setPropertyInChanged("propE");
+        PropertyInfo propF = setPropertyInChanged("propF");
+        PropertyInfo propG = setPropertyInChanged("propG");
+        // Now ask for names
+        Set<Name> names = changes.getPropertyNames();
+        assertThat(names.size(), is(7));
+        assertThat(names, hasItems(propA.getPropertyName(),
+                                   propB.getPropertyName(),
+                                   propC.getPropertyName(),
+                                   propD.getPropertyName(),
+                                   propE.getPropertyName(),
+                                   propF.getPropertyName()));
+    }
+
+    @Test
+    @SuppressWarnings( "unused" )
+    public void shouldFindAllPropertyNamesWhenChangedNodeHasChangedAndDeletedAndAddedSomeProperties() {
+        PropertyInfo propA = makePropertyInOriginal("propA");
+        PropertyInfo propB = makePropertyInOriginal("propB");
+        PropertyInfo propC = makePropertyInOriginal("propC");
+        PropertyInfo propD = makePropertyInOriginal("propD");
+        // Change some properties ...
+        PropertyInfo propB2 = setPropertyInChanged("propB");
+        PropertyInfo propC2 = setPropertyInChanged("propC");
+        // Add some properties ...
+        PropertyInfo propE = setPropertyInChanged("propE");
+        // Remove some properties ...
+        assertThat(changes.removeProperty(propB2.getPropertyName()), is(sameInstance(propB2)));
+        assertThat(changes.removeProperty(propD.getPropertyName()), is(sameInstance(propD)));
+        // Now ask for names
+        Set<Name> names = changes.getPropertyNames();
+        assertThat(names.size(), is(3));
+        assertThat(names, hasItems(propA.getPropertyName(), propC2.getPropertyName(), propE.getPropertyName()));
+    }
+
+    @Test
+    @SuppressWarnings( "unused" )
+    public void shouldFindAllPropertyNamesWhenChangedNodeHasChangedAndDeletedAllProperties() {
+        PropertyInfo propA = makePropertyInOriginal("propA");
+        PropertyInfo propB = makePropertyInOriginal("propB");
+        PropertyInfo propC = makePropertyInOriginal("propC");
+        PropertyInfo propD = makePropertyInOriginal("propD");
+        // Change some properties ...
+        PropertyInfo propB2 = setPropertyInChanged("propB");
+        PropertyInfo propC2 = setPropertyInChanged("propC");
+        // Remove all properties ...
+        assertThat(changes.removeProperty(propA.getPropertyName()), is(sameInstance(propA)));
+        assertThat(changes.removeProperty(propB2.getPropertyName()), is(sameInstance(propB2)));
+        assertThat(changes.removeProperty(propC2.getPropertyName()), is(sameInstance(propC2)));
+        assertThat(changes.removeProperty(propD.getPropertyName()), is(sameInstance(propD)));
+        // Now ask for names
+        Set<Name> names = changes.getPropertyNames();
+        assertThat(names.size(), is(0));
+    }
+
+}


Property changes on: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ChangedNodeInfoTest.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ImmutableChildrenTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ImmutableChildrenTest.java	2009-03-18 04:38:44 UTC (rev 781)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/ImmutableChildrenTest.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -76,7 +76,7 @@
 
     @Test
     public void shouldReturnEmptyChildrenFromWithoutIfOnlyChildIsRemoved() {
-        Children newChildren = children.without(firstChild, pathFactory);
+        Children newChildren = children.without(firstChild.getUuid(), pathFactory);
         assertThat(newChildren.size(), is(0));
     }
 
@@ -93,7 +93,7 @@
         assertChildNodesWithName(children, "childA", firstChild, child4, child5);
 
         // Remove 'child4' ...
-        Children result = children.without(child4, pathFactory);
+        Children result = children.without(child4.getUuid(), pathFactory);
 
         // the original should not have been changed ...
         assertChildNodesWithName(children, "childA", firstChild, child4, child5);

Added: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/IsNodeInfoWithChildrenHavingNames.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/IsNodeInfoWithChildrenHavingNames.java	                        (rev 0)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/IsNodeInfoWithChildrenHavingNames.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -0,0 +1,84 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors. 
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.cache;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.basic.BasicPathSegment;
+import org.junit.matchers.IsCollectionContaining;
+import org.junit.matchers.TypeSafeMatcher;
+
+/**
+ * A JUnit {@link TypeSafeMatcher matcher} that can be used to assert that a NodeInfo (or other
+ * <code>Iterable&lt;ChildNode></code>) has {@link ChildNode} instances with specific {@link ChildNode#getSegment() names}.
+ */
+public class IsNodeInfoWithChildrenHavingNames extends TypeSafeMatcher<Children> {
+    private final Matcher<Iterable<Path.Segment>> childMatcher;
+
+    public IsNodeInfoWithChildrenHavingNames( Matcher<Iterable<Path.Segment>> childMatcher ) {
+        this.childMatcher = childMatcher;
+    }
+
+    @Override
+    public boolean matchesSafely( Children children ) {
+        List<Path.Segment> childSegments = new ArrayList<Path.Segment>(children.size());
+        for (ChildNode child : children) {
+            childSegments.add(child.getSegment());
+        }
+        return childMatcher.matches(childSegments);
+    }
+
+    public void describeTo( Description description ) {
+        description.appendText("children").appendDescriptionOf(childMatcher);
+    }
+
+    @Factory
+    public static IsNodeInfoWithChildrenHavingNames hasChild( Name name,
+                                                              int sameNameSiblingIndex ) {
+        Path.Segment segment = new BasicPathSegment(name, sameNameSiblingIndex);
+        return new IsNodeInfoWithChildrenHavingNames(IsCollectionContaining.hasItem(segment));
+    }
+
+    @Factory
+    public static IsNodeInfoWithChildrenHavingNames hasChild( Path.Segment child ) {
+        return new IsNodeInfoWithChildrenHavingNames(IsCollectionContaining.hasItem(child));
+    }
+
+    @Factory
+    public static IsNodeInfoWithChildrenHavingNames hasChildren( Path.Segment... childSegments ) {
+        return new IsNodeInfoWithChildrenHavingNames(IsCollectionContaining.hasItems(childSegments));
+    }
+
+    @Factory
+    public static IsNodeInfoWithChildrenHavingNames isEmpty() {
+        Path.Segment[] childSegments = new Path.Segment[] {};
+        return new IsNodeInfoWithChildrenHavingNames(IsCollectionContaining.hasItems(childSegments));
+    }
+
+}


Property changes on: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/IsNodeInfoWithChildrenHavingNames.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/IsNodeInfoWithChildrenHavingUuids.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/IsNodeInfoWithChildrenHavingUuids.java	                        (rev 0)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/IsNodeInfoWithChildrenHavingUuids.java	2009-03-18 16:54:54 UTC (rev 782)
@@ -0,0 +1,75 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors. 
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.jcr.cache;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.junit.matchers.IsCollectionContaining;
+import org.junit.matchers.TypeSafeMatcher;
+
+/**
+ * A JUnit {@link TypeSafeMatcher matcher} that can be used to assert that a NodeInfo (or other
+ * <code>Iterable&lt;ChildNode></code>) has {@link ChildNode} instances with specific {@link ChildNode#getUuid() UUIDs}.
+ */
+public class IsNodeInfoWithChildrenHavingUuids extends TypeSafeMatcher<Children> {
+    private final Matcher<Iterable<UUID>> childMatcher;
+
+    public IsNodeInfoWithChildrenHavingUuids( Matcher<Iterable<UUID>> childMatcher ) {
+        this.childMatcher = childMatcher;
+    }
+
+    @Override
+    public boolean matchesSafely( Children children ) {
+        List<UUID> childSegments = new ArrayList<UUID>(children.size());
+        for (ChildNode child : children) {
+            childSegments.add(child.getUuid());
+        }
+        return childMatcher.matches(childSegments);
+    }
+
+    public void describeTo( Description description ) {
+        description.appendText("children with UUIDs").appendDescriptionOf(childMatcher);
+    }
+
+    @Factory
+    public static IsNodeInfoWithChildrenHavingUuids hasChild( UUID uuid ) {
+        return new IsNodeInfoWithChildrenHavingUuids(IsCollectionContaining.hasItem(uuid));
+    }
+
+    @Factory
+    public static IsNodeInfoWithChildrenHavingUuids hasChildren( UUID... uuids ) {
+        return new IsNodeInfoWithChildrenHavingUuids(IsCollectionContaining.hasItems(uuids));
+    }
+
+    @Factory
+    public static IsNodeInfoWithChildrenHavingUuids isEmpty() {
+        UUID[] uuids = new UUID[] {};
+        return new IsNodeInfoWithChildrenHavingUuids(IsCollectionContaining.hasItems(uuids));
+    }
+
+}


Property changes on: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/cache/IsNodeInfoWithChildrenHavingUuids.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain




More information about the dna-commits mailing list