[jbosscache-commits] JBoss Cache SVN: r7089 - in core/branches/flat/src: main/java/org/jboss/starobrno/container and 7 other directories.

jbosscache-commits at lists.jboss.org jbosscache-commits at lists.jboss.org
Wed Nov 5 19:31:36 EST 2008


Author: manik.surtani at jboss.com
Date: 2008-11-05 19:31:36 -0500 (Wed, 05 Nov 2008)
New Revision: 7089

Added:
   core/branches/flat/src/main/java/org/jboss/starobrno/tree/
   core/branches/flat/src/main/java/org/jboss/starobrno/tree/Fqn.java
   core/branches/flat/src/main/java/org/jboss/starobrno/tree/FqnComparator.java
   core/branches/flat/src/main/java/org/jboss/starobrno/tree/Node.java
   core/branches/flat/src/main/java/org/jboss/starobrno/tree/NodeImpl.java
   core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeCache.java
   core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeCacheImpl.java
   core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeStructureSupport.java
   core/branches/flat/src/test/java/org/jboss/starobrno/tree/
   core/branches/flat/src/test/java/org/jboss/starobrno/tree/api/
   core/branches/flat/src/test/java/org/jboss/starobrno/tree/api/NodeAPITest.java
   core/branches/flat/src/test/java/org/jboss/starobrno/tree/api/TreeCacheAPITest.java
Modified:
   core/branches/flat/src/main/java/org/jboss/starobrno/Cache.java
   core/branches/flat/src/main/java/org/jboss/starobrno/CacheDelegate.java
   core/branches/flat/src/main/java/org/jboss/starobrno/CacheSPI.java
   core/branches/flat/src/main/java/org/jboss/starobrno/container/UnsortedDataContainer.java
   core/branches/flat/src/main/java/org/jboss/starobrno/factories/EvictionManagerFactory.java
   core/branches/flat/src/main/java/org/jboss/starobrno/manager/CacheManager.java
   core/branches/flat/src/test/resources/configs/local-tx.xml
Log:
Initial tree adapter

Modified: core/branches/flat/src/main/java/org/jboss/starobrno/Cache.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/Cache.java	2008-11-05 20:34:07 UTC (rev 7088)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/Cache.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -24,11 +24,12 @@
 import org.jboss.cache.CacheStatus;
 import org.jboss.starobrno.config.Configuration;
 import org.jboss.starobrno.context.InvocationContext;
+import org.jboss.starobrno.interceptors.base.CommandInterceptor;
 import org.jboss.starobrno.lifecycle.Lifecycle;
 import org.jgroups.Address;
 
+import java.util.List;
 import java.util.Set;
-import java.util.List;
 import java.util.concurrent.ConcurrentMap;
 
 /**
@@ -59,4 +60,16 @@
    List<Address> getMembers();
 
    String getName();
+
+   Address getLocalAddress();
+
+   String getVersion();
+
+   void addInterceptor(CommandInterceptor i, int position);
+
+   void addInterceptor(CommandInterceptor i, Class<? extends CommandInterceptor> afterInterceptor);
+
+   void removeInterceptor(int position);
+
+   void removeInterceptor(Class<? extends CommandInterceptor> interceptorType);
 }

Modified: core/branches/flat/src/main/java/org/jboss/starobrno/CacheDelegate.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/CacheDelegate.java	2008-11-05 20:34:07 UTC (rev 7088)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/CacheDelegate.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -22,11 +22,11 @@
 package org.jboss.starobrno;
 
 import org.jboss.cache.CacheStatus;
+import org.jboss.cache.Version;
 import org.jboss.cache.buddyreplication.BuddyManager;
 import org.jboss.cache.buddyreplication.GravitateResult;
 import org.jboss.cache.loader.CacheLoaderManager;
 import org.jboss.cache.marshall.Marshaller;
-import org.jboss.starobrno.statetransfer.StateTransferManager;
 import org.jboss.starobrno.batch.BatchContainer;
 import org.jboss.starobrno.commands.CommandsFactory;
 import org.jboss.starobrno.commands.read.GetKeyValueCommand;
@@ -39,18 +39,18 @@
 import org.jboss.starobrno.commands.write.ReplaceCommand;
 import org.jboss.starobrno.config.Configuration;
 import org.jboss.starobrno.config.ConfigurationException;
+import org.jboss.starobrno.container.DataContainer;
 import org.jboss.starobrno.context.InvocationContext;
 import org.jboss.starobrno.factories.ComponentRegistry;
 import org.jboss.starobrno.factories.annotations.Inject;
 import org.jboss.starobrno.factories.annotations.NonVolatile;
 import org.jboss.starobrno.interceptors.InterceptorChain;
-import org.jboss.starobrno.interceptors.EvictionInterceptor;
 import org.jboss.starobrno.interceptors.base.CommandInterceptor;
 import org.jboss.starobrno.invocation.InvocationContextContainer;
 import org.jboss.starobrno.notifications.Notifier;
+import org.jboss.starobrno.statetransfer.StateTransferManager;
 import org.jboss.starobrno.transaction.GlobalTransaction;
 import org.jboss.starobrno.transaction.TransactionTable;
-import org.jboss.starobrno.eviction.EvictionCacheManager;
 import org.jgroups.Address;
 
 import javax.transaction.Transaction;
@@ -77,7 +77,9 @@
    protected RPCManager rpcManager;
    private String name;
    private EvictionManager evictionManager;
+   private DataContainer dataContainer;
 
+
    @Inject
    private void injectDependencies(EvictionManager evictionManager,
                                    InvocationContextContainer invocationContextContainer,
@@ -88,7 +90,7 @@
                                    ComponentRegistry componentRegistry,
                                    TransactionManager transactionManager,
                                    BatchContainer batchContainer,
-                                   RPCManager rpcManager)
+                                   RPCManager rpcManager, DataContainer dataContainer)
    {
       this.invocationContextContainer = invocationContextContainer;
       this.commandsFactory = commandsFactory;
@@ -100,6 +102,7 @@
       this.batchContainer = batchContainer;
       this.rpcManager = rpcManager;
       this.evictionManager = evictionManager;
+      this.dataContainer = dataContainer;
    }
 
    public V putIfAbsent(K key, V value)
@@ -326,7 +329,7 @@
 
    public String getClusterName()
    {
-      throw new IllegalStateException();//todo Implement me properly
+      return config.getClusterName();
    }
 
    public GlobalTransaction getCurrentTransaction(Transaction tx, boolean createIfNotExists)
@@ -370,7 +373,7 @@
 
    public List<Address> getMembers()
    {
-      return rpcManager.getMembers();      
+      return rpcManager.getMembers();
    }
 
    public Object getDirect(Object key)
@@ -383,8 +386,24 @@
       return name;
    }
 
+   public String getVersion()
+   {
+      return Version.getVersionString(Version.getVersionShort());
+   }
+
+   public Address getLocalAddress()
+   {
+      return rpcManager.getLocalAddress();
+   }
+
    public void setName(String name)
    {
       this.name = name;
    }
+
+   @Override
+   public String toString()
+   {
+      return dataContainer == null ? super.toString() : dataContainer.toString();
+   }
 }

Modified: core/branches/flat/src/main/java/org/jboss/starobrno/CacheSPI.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/CacheSPI.java	2008-11-05 20:34:07 UTC (rev 7088)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/CacheSPI.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -21,13 +21,7 @@
  */
 package org.jboss.starobrno;
 
-import java.util.List;
-
-import javax.transaction.Transaction;
-import javax.transaction.TransactionManager;
-
 import net.jcip.annotations.ThreadSafe;
-
 import org.jboss.cache.Node;
 import org.jboss.cache.NodeSPI;
 import org.jboss.cache.buddyreplication.BuddyManager;
@@ -43,6 +37,10 @@
 import org.jboss.starobrno.transaction.GlobalTransaction;
 import org.jboss.starobrno.transaction.TransactionTable;
 
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import java.util.List;
+
 /**
  * A more detailed interface to {@link Cache}, which is used when writing plugins for or extending JBoss Cache.  A reference
  * to this interface should only be obtained when it is passed in to your code, for example when you write an
@@ -102,39 +100,6 @@
    Marshaller getMarshaller();
 
    /**
-    * Adds a custom interceptor to the interceptor chain, at specified position, where the first interceptor in the chain
-    * is at position 0 and the last one at getInterceptorChain().size() - 1.
-    *
-    * @param i        the interceptor to add
-    * @param position the position to add the interceptor
-    */
-   void addInterceptor(CommandInterceptor i, int position);
-
-   /**
-    * Adds a custom interceptor to the interceptor chain, after an instance of the specified interceptor type.  Throws a
-    * cache exception if it cannot find an interceptor of the specified type.
-    *
-    * @param i                interceptor to add
-    * @param afterInterceptor interceptor type after which to place custom interceptor
-    */
-   void addInterceptor(CommandInterceptor i, Class<? extends CommandInterceptor> afterInterceptor);
-
-   /**
-    * Removes the interceptor at a specified position, where the first interceptor in the chain
-    * is at position 0 and the last one at getInterceptorChain().size() - 1.
-    *
-    * @param position the position at which to remove an interceptor
-    */
-   void removeInterceptor(int position);
-
-   /**
-    * Removes the interceptor of specified type.
-    *
-    * @param interceptorType type of interceptor to remove
-    */
-   void removeInterceptor(Class<? extends CommandInterceptor> interceptorType);
-
-   /**
     * Retrieves the current CacheCacheLoaderManager instance associated with the current Cache instance.
     * <p/>
     * From 2.1.0, Interceptor authors should obtain this by injection rather than this method.  See the

Modified: core/branches/flat/src/main/java/org/jboss/starobrno/container/UnsortedDataContainer.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/container/UnsortedDataContainer.java	2008-11-05 20:34:07 UTC (rev 7088)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/container/UnsortedDataContainer.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -74,4 +74,9 @@
    {
       throw new UnsupportedOperationException("Not implemented");//todo please implement!
    }
+
+   public String toString()
+   {
+      return data.toString();
+   }
 }

Modified: core/branches/flat/src/main/java/org/jboss/starobrno/factories/EvictionManagerFactory.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/factories/EvictionManagerFactory.java	2008-11-05 20:34:07 UTC (rev 7088)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/factories/EvictionManagerFactory.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -1,24 +1,28 @@
 package org.jboss.starobrno.factories;
 
-import org.jboss.starobrno.factories.annotations.DefaultFactoryFor;
 import org.jboss.starobrno.EvictionManager;
 import org.jboss.starobrno.eviction.EvictionManagerImpl;
+import org.jboss.starobrno.factories.annotations.DefaultFactoryFor;
 
 /**
  * @author Mircea.Markus at jboss.com
  */
- at DefaultFactoryFor (classes = {EvictionManager.class})
+ at DefaultFactoryFor(classes = {EvictionManager.class})
 public class EvictionManagerFactory extends ComponentFactory
 {
    protected <T> T construct(Class<T> componentType)
    {
-      if ( componentType != EvictionManager.class)
+      if (componentType != EvictionManager.class)
       {
          throw new IllegalStateException();
       }
       EvictionManagerImpl evManager = new EvictionManagerImpl();
-      long wakeupInterval = configuration.getEvictionConfig().getWakeupInterval();
-      evManager.configureEvictionThread(wakeupInterval, configuration.getRuntimeConfig().getEvictionTimerThreadFactory());
-      return (T) evManager;
+      if (configuration.getEvictionConfig() != null)
+      {
+         long wakeupInterval = configuration.getEvictionConfig().getWakeupInterval();
+         evManager.configureEvictionThread(wakeupInterval, configuration.getRuntimeConfig().getEvictionTimerThreadFactory());
+         return (T) evManager;
+      }
+      else return null;
    }
 }

Modified: core/branches/flat/src/main/java/org/jboss/starobrno/manager/CacheManager.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/manager/CacheManager.java	2008-11-05 20:34:07 UTC (rev 7088)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/manager/CacheManager.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -25,6 +25,8 @@
 import org.jboss.starobrno.Cache;
 import org.jboss.starobrno.config.Configuration;
 import org.jboss.starobrno.lifecycle.Lifecycle;
+import org.jboss.starobrno.tree.TreeCache;
+import org.jboss.starobrno.tree.TreeCacheImpl;
 
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -39,6 +41,7 @@
 {
    protected Configuration c;
    private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
+   private final ConcurrentMap<String, TreeCache> treeCaches = new ConcurrentHashMap<String, TreeCache>();
 
    public CacheManager(Configuration c)
    {
@@ -47,7 +50,8 @@
          //if a config is shared between multiple managers, then each registers it's
          // own chnnel in runtime
          this.c = c.clone();
-      } catch (CloneNotSupportedException e)
+      }
+      catch (CloneNotSupportedException e)
       {
          throw new RuntimeException(e);
       }
@@ -73,12 +77,36 @@
       return c;
    }
 
+   public TreeCache createTreeCache(String cacheName)
+   {
+      if (!treeCaches.containsKey(cacheName))
+         treeCaches.putIfAbsent(cacheName, createTreeCache());
+      TreeCache tc = treeCaches.get(cacheName);
+      return tc;
+   }
+
    public void destroyCache(String cacheName)
    {
       Cache c = caches.remove(cacheName);
       if (c != null) c.stop();
    }
 
+   protected TreeCache createTreeCache()
+   {
+      Configuration cfg = null;
+      try
+      {
+         cfg = c.clone();
+      }
+      catch (CloneNotSupportedException e)
+      {
+
+      }
+      cfg.setInvocationBatchingEnabled(true); // needed for the TreeCache
+      DefaultCacheFactory dcf = new DefaultCacheFactory();
+      return new TreeCacheImpl(dcf.createCache(cfg));
+   }
+
    protected Cache createNewCache()
    {
       // for now latch on to the existing cache creation mechanisms

Added: core/branches/flat/src/main/java/org/jboss/starobrno/tree/Fqn.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/tree/Fqn.java	                        (rev 0)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/tree/Fqn.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -0,0 +1,615 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * 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.
+ *
+ * This software 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.starobrno.tree;
+
+
+import net.jcip.annotations.Immutable;
+import org.jboss.cache.util.Immutables;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A Fully Qualified Name (Fqn) is a list of names (typically Strings but can be any Object),
+ * which represent a path to a particular {@link Node} or sometimes a {@link Region} in a {@link Cache}.
+ * <p/>
+ * This name can be absolute (i.e., relative from the root node - {@link #ROOT}), or relative to any node in the cache.  Reading the
+ * documentation on each API call that makes use of {@link org.jboss.cache.Fqn}s will tell you whether the API expects a
+ * relative or absolute Fqn.
+ * <p/>
+ * For instance, using this class to fetch a particular node might look like
+ * this.  (Here data on "Joe" is kept under the "Smith" surname node, under
+ * the "people" tree.)
+ * <pre>
+ * Fqn<String> abc = Fqn.fromString("/people/Smith/Joe/");
+ * Node joesmith = Cache.getRoot().getChild(abc);
+ * </pre>
+ * Alternatively, the same Fqn could be constructed using a List<Object> or varargs:
+ * <pre>
+ * Fqn<String> abc = Fqn.fromElements("people", "Smith", "Joe");
+ * </pre>
+ * This is a bit more efficient to construct.
+ * <p/>
+ * Note that<br>
+ * <p/>
+ * <code>Fqn<String> f = Fqn.fromElements("/a/b/c");</code>
+ * <p/>
+ * is <b>not</b> the same as
+ * <p/>
+ * <code>Fqn<String> f = Fqn.fromString("/a/b/c");</code>
+ * <p/>
+ * The former will result in a single Fqn, called "/a/b/c" which hangs directly under Fqn.ROOT.
+ * <p/>
+ * The latter will result in 3 Fqns, called "a", "b" and "c", where "c" is a child of "b", "b" is a child of "a", and "a" hangs off Fqn.ROOT.
+ * <p/>
+ * Another way to look at it is that the "/" separarator is only parsed when it forms
+ * part of a String passed in to Fqn.fromString() and not otherwise.
+ * <p/>
+ * <B>Best practices</B>: Always creating Fqns - even when using some factory methods - can be expensive in the long run,
+ * and as far as possible we recommend that client code holds on to their Fqn references and reuse them.  E.g.:
+ * <code>
+ * // BAD!!
+ * for (int i=0; i<someBigNumber; i++)
+ * {
+ * cache.get(Fqn.fromString("/a/b/c"), "key" + i);
+ * }
+ * </code>
+ * instead, do:
+ * <code>
+ * // Much better
+ * Fqn f = Fqn.fromString("/a/b/c");
+ * for (int i=0; i<someBigNumber; i++)
+ * {
+ * cache.get(f, "key" + i);
+ * }
+ * </code>
+ *
+ * @version $Revision$
+ */
+ at Immutable
+public class Fqn implements Comparable<Fqn>, Externalizable
+{
+   /**
+    * Separator between FQN elements.
+    */
+   public static final String SEPARATOR = "/";
+
+   protected List<Object> elements;
+   private transient int hash_code = 0;
+   protected int size = 0;
+
+   /**
+    * Immutable root Fqn.
+    */
+   public static final Fqn ROOT = new Fqn();
+
+   /**
+    * A cached string representation of this Fqn, used by toString to it isn't calculated again every time.
+    */
+   protected String stringRepresentation;
+
+   // ----------------- START: Private constructors for use by factory methods only. ----------------------
+
+   /**
+    * Public to satisfy Externalization.  // TODO: Remove this ctor as well as Externalization!!
+    */
+   public Fqn()
+   {
+      elements = Collections.emptyList();
+      size = 0;
+   }
+
+   /**
+    * If safe is false, Collections.unmodifiableList() is used to wrap the list passed in.  This is an optimisation so
+    * Fqn.fromString(), probably the most frequently used factory method, doesn't end up needing to use the unmodifiableList()
+    * since it creates the list internally.
+    *
+    * @param names List of names
+    * @param safe  whether this list is referenced externally (safe = false) or not (safe = true).
+    * @deprecated use {@link #fromList(java.util.List)} instead.  The boolean "safety" hint is calculated internally.  This constructor will be removed in 3.0.0.
+    */
+   @SuppressWarnings("unchecked")
+   private Fqn(List names, boolean safe)
+   {
+      if (names != null)
+      {
+         // if not safe make a defensive copy
+         elements = safe ? names : Immutables.immutableListCopy(names);
+         size = elements.size();
+      }
+      else
+      {
+         elements = Collections.emptyList();
+         size = 0;
+      }
+   }
+
+   private Fqn(Fqn base, List relative)
+   {
+      elements = Immutables.immutableListMerge(base.elements, relative);
+      size = elements.size();
+   }
+
+   // ----------------- END: Private constructors for use by factory methods only. ----------------------
+
+   /**
+    * Retrieves an Fqn that represents the list of elements passed in.
+    *
+    * @param names list of elements that comprise the Fqn
+    * @return an Fqn
+    * @since 2.2.0
+    */
+   @SuppressWarnings("unchecked")
+   public static Fqn fromList(List names)
+   {
+      return new Fqn(names, false);
+   }
+
+   /**
+    * Retrieves an Fqn that represents the list of elements passed in.
+    *
+    * @param names list of elements that comprise the Fqn
+    * @param safe  if true, the list passed in is not defensively copied but used directly.  <b>Use with care.</b>  Make sure
+    *              you know what you are doing before you pass in a <tt>true</tt> value to <tt>safe</tt>, as it can have adverse effects on
+    *              performance or correctness.  The defensive copy of list elements is not just for safety but also for performance as
+    *              an appropriare List implementation is used, which works well with Fqn operations.
+    * @return an Fqn
+    */
+   @SuppressWarnings("unchecked")
+   public static Fqn fromList(List names, boolean safe)
+   {
+      return new Fqn(names, safe);
+   }
+
+   /**
+    * Retrieves an Fqn that represents the array of elements passed in.
+    *
+    * @param elements array of elements that comprise the Fqn
+    * @return an Fqn
+    * @since 2.2.0
+    */
+   public static Fqn fromElements(Object... elements)
+   {
+      return new Fqn(Arrays.asList(elements), true);
+   }
+
+   /**
+    * Retrieves an Fqn that represents the absolute Fqn of the relative Fqn passed in.
+    *
+    * @param base     base Fqn
+    * @param relative relative Fqn
+    * @return an Fqn
+    * @since 2.2.0
+    */
+   public static Fqn fromRelativeFqn(Fqn base, Fqn relative)
+   {
+      return new Fqn(base, relative.elements);
+   }
+
+   /**
+    * Retrieves an Fqn that represents the List<Object> of elements passed in, relative to the base Fqn.
+    *
+    * @param base             base Fqn
+    * @param relativeElements relative List<Object> of elements
+    * @return an Fqn
+    * @since 2.2.0
+    */
+   public static Fqn fromRelativeList(Fqn base, List relativeElements)
+   {
+      return new Fqn(base, relativeElements);
+   }
+
+   /**
+    * Retrieves an Fqn that represents the array of elements passed in, relative to the base Fqn.
+    *
+    * @param base             base Fqn
+    * @param relativeElements relative elements
+    * @return an Fqn
+    * @since 2.2.0
+    */
+   public static Fqn fromRelativeElements(Fqn base, Object... relativeElements)
+   {
+      return new Fqn(base, Arrays.asList(relativeElements));
+   }
+
+   /**
+    * Returns a new Fqn from a string, where the elements are deliminated by
+    * one or more separator ({@link #SEPARATOR}) characters.<br><br>
+    * Example use:<br>
+    * <pre>
+    * Fqn.fromString("/a/b/c/");
+    * </pre><br>
+    * is equivalent to:<br>
+    * <pre>
+    * Fqn.fromElements("a", "b", "c");
+    * </pre>
+    *
+    * @param stringRepresentation String representation of the Fqn
+    * @return an Fqn<String> constructed from the string representation passed in
+    */
+   @SuppressWarnings("unchecked")
+   public static Fqn fromString(String stringRepresentation)
+   {
+      if (stringRepresentation == null || stringRepresentation.equals(SEPARATOR) || stringRepresentation.equals(""))
+         return root();
+
+      String toMatch = stringRepresentation.startsWith(SEPARATOR) ? stringRepresentation.substring(1) : stringRepresentation;
+      Object[] el = toMatch.split(SEPARATOR);
+      return new Fqn(Immutables.immutableListWrap(el), true);
+   }
+
+   /**
+    * Retrieves an Fqn read from an object input stream, typically written to using {@link #writeExternal(java.io.ObjectOutput)}.
+    *
+    * @param in input stream
+    * @return an Fqn
+    * @throws IOException            in the event of a problem reading the stream
+    * @throws ClassNotFoundException in the event of classes that comprise the element list of this Fqn not being found
+    * @since 2.2.0
+    */
+   public static Fqn fromExternalStream(ObjectInput in) throws IOException, ClassNotFoundException
+   {
+      Fqn f = new Fqn();
+      f.readExternal(in);
+      return f;
+   }
+
+
+   /**
+    * Obtains an ancestor of the current Fqn.  Literally performs <code>elements.subList(0, generation)</code>
+    * such that if
+    * <code>
+    * generation == Fqn.size()
+    * </code>
+    * then the return value is the Fqn itself (current generation), and if
+    * <code>
+    * generation == Fqn.size() - 1
+    * </code>
+    * then the return value is the same as
+    * <code>
+    * Fqn.getParent()
+    * </code>
+    * i.e., just one generation behind the current generation.
+    * <code>
+    * generation == 0
+    * </code>
+    * would return Fqn.ROOT.
+    *
+    * @param generation the generation of the ancestor to retrieve
+    * @return an ancestor of the current Fqn
+    */
+   public Fqn getAncestor(int generation)
+   {
+      if (generation == 0) return root();
+      return getSubFqn(0, generation);
+   }
+
+   /**
+    * Obtains a sub-Fqn from the given Fqn.  Literally performs <code>elements.subList(startIndex, endIndex)</code>
+    *
+    * @param startIndex starting index
+    * @param endIndex   end index
+    * @return a subFqn
+    */
+   public Fqn getSubFqn(int startIndex, int endIndex)
+   {
+      List el = elements.subList(startIndex, endIndex);
+      return new Fqn(el, true);
+   }
+
+   /**
+    * @return the number of elements in the Fqn.  The root node contains zero.
+    */
+   public int size()
+   {
+      return size;
+   }
+
+   /**
+    * @param n index of the element to return
+    * @return Returns the nth element in the Fqn.
+    */
+   public Object get(int n)
+   {
+      return elements.get(n);
+   }
+
+   /**
+    * @return the last element in the Fqn.
+    * @see #getLastElementAsString
+    */
+   public Object getLastElement()
+   {
+      if (isRoot()) return null;
+      return elements.get(size - 1);
+   }
+
+   /**
+    * @param element element to find
+    * @return true if the Fqn contains this element, false otherwise.
+    */
+   public boolean hasElement(Object element)
+   {
+      return elements.indexOf(element) != -1;
+   }
+
+   /**
+    * Returns true if obj is a Fqn with the same elements.
+    */
+   @Override
+   public boolean equals(Object obj)
+   {
+      if (this == obj)
+      {
+         return true;
+      }
+      if (!(obj instanceof Fqn))
+      {
+         return false;
+      }
+      Fqn other = (Fqn) obj;
+      return size == other.size() && elements.equals(other.elements);
+   }
+
+   /**
+    * Returns a hash code with Fqn elements.
+    */
+   @Override
+   public int hashCode()
+   {
+      if (hash_code == 0)
+      {
+         hash_code = calculateHashCode();
+      }
+      return hash_code;
+   }
+
+   /**
+    * Returns this Fqn as a string, prefixing the first element with a {@link Fqn#SEPARATOR} and
+    * joining each subsequent element with a {@link Fqn#SEPARATOR}.
+    * If this is the root Fqn, returns {@link Fqn#SEPARATOR}.
+    * Example:
+    * <pre>
+    * new Fqn(new Object[] { "a", "b", "c" }).toString(); // "/a/b/c"
+    * Fqn.ROOT.toString(); // "/"
+    * </pre>
+    */
+   @Override
+   public String toString()
+   {
+      if (stringRepresentation == null)
+      {
+         stringRepresentation = getStringRepresentation(elements);
+      }
+      return stringRepresentation;
+   }
+
+   public void writeExternal(ObjectOutput out) throws IOException
+   {
+      out.writeShort(size);
+      for (Object element : elements)
+      {
+         out.writeObject(element);
+      }
+   }
+
+   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
+   {
+      size = in.readShort();
+      this.elements = new ArrayList(size);
+      for (int i = 0; i < size; i++) elements.add(in.readObject());
+   }
+
+
+   /**
+    * Returns true if this Fqn is child of parentFqn.
+    * Example usage:
+    * <pre>
+    * Fqn<String> f1 = Fqn.fromString("/a/b");
+    * Fqn<String> f2 = Fqn.fromString("/a/b/c");
+    * assertTrue(f1.isChildOf(f2));
+    * assertFalse(f1.isChildOf(f1));
+    * assertFalse(f2.isChildOf(f1));
+    * </pre>
+    *
+    * @param parentFqn candidate parent to test against
+    * @return true if the target is a child of parentFqn
+    */
+   public boolean isChildOf(Fqn parentFqn)
+   {
+      return parentFqn.size() != size && isChildOrEquals(parentFqn);
+   }
+
+
+   /**
+    * Returns true if this Fqn is a <i>direct</i> child of a given Fqn.
+    *
+    * @param parentFqn parentFqn to compare with
+    * @return true if this is a direct child, false otherwise.
+    */
+   public boolean isDirectChildOf(Fqn parentFqn)
+   {
+      return size == parentFqn.size() + 1 && isChildOf(parentFqn);
+   }
+
+   /**
+    * Returns true if this Fqn is equals or the child of parentFqn.
+    * Example usage:
+    * <pre>
+    * Fqn<String> f1 = Fqn.fromString("/a/b");
+    * Fqn<String> f2 = Fqn.fromString("/a/b/c");
+    * assertTrue(f1.isChildOrEquals(f2));
+    * assertTrue(f1.isChildOrEquals(f1));
+    * assertFalse(f2.isChildOrEquals(f1));
+    * </pre>
+    *
+    * @param parentFqn candidate parent to test against
+    * @return true if this Fqn is equals or the child of parentFqn.
+    */
+   public boolean isChildOrEquals(Fqn parentFqn)
+   {
+      List parentList = parentFqn.elements;
+      if (parentList.size() > size)
+      {
+         return false;
+      }
+      for (int i = parentList.size() - 1; i >= 0; i--)
+      {
+         if (!parentList.get(i).equals(elements.get(i)))
+         {
+            return false;
+         }
+      }
+      return true;
+   }
+
+   /**
+    * Calculates a hash code by summing the hash code of all elements.
+    *
+    * @return a calculated hashcode
+    */
+   protected int calculateHashCode()
+   {
+      int hashCode = 19;
+      for (Object o : elements) hashCode = 31 * hashCode + (o == null ? 0 : o.hashCode());
+      if (hashCode == 0) hashCode = 0xDEADBEEF; // degenerate case
+      return hashCode;
+   }
+
+   protected String getStringRepresentation(List elements)
+   {
+      StringBuilder builder = new StringBuilder();
+      for (Object e : elements)
+      {
+         // incase user element 'e' does not implement equals() properly, don't rely on their implementation.
+         if (!SEPARATOR.equals(e) && !"".equals(e))
+         {
+            builder.append(SEPARATOR);
+            builder.append(e);
+         }
+      }
+      return builder.length() == 0 ? SEPARATOR : builder.toString();
+   }
+
+
+   /**
+    * Returns the parent of this Fqn.
+    * The parent of the root node is {@link #ROOT}.
+    * Examples:
+    * <pre>
+    * Fqn<String> f1 = Fqn.fromString("/a");
+    * Fqn<String> f2 = Fqn.fromString("/a/b");
+    * assertEquals(f1, f2.getParent());
+    * assertEquals(Fqn.ROOT, f1.getParent().getParent());
+    * assertEquals(Fqn.ROOT, Fqn.ROOT.getParent());
+    * </pre>
+    *
+    * @return the parent Fqn
+    */
+   public Fqn getParent()
+   {
+      switch (size)
+      {
+         case 0:
+         case 1:
+            return root();
+         default:
+            return new Fqn(elements.subList(0, size - 1), true);
+      }
+   }
+
+   public static final Fqn root()  // declared final so compilers can optimise and in-line.
+   {
+      return ROOT;
+   }
+
+   /**
+    * Returns true if this is a root Fqn.
+    *
+    * @return true if the Fqn is Fqn.ROOT.
+    */
+   public boolean isRoot()
+   {
+      return size == 0;
+   }
+
+   /**
+    * If this is the root, returns {@link Fqn#SEPARATOR}.
+    *
+    * @return a String representation of the last element that makes up this Fqn.
+    */
+   public String getLastElementAsString()
+   {
+      if (isRoot())
+      {
+         return SEPARATOR;
+      }
+      else
+      {
+         Object last = getLastElement();
+         if (last instanceof String)
+            return (String) last;
+         else
+            return String.valueOf(getLastElement());
+      }
+   }
+
+   /**
+    * Peeks into the elements that build up this Fqn.  The list returned is
+    * read-only, to maintain the immutable nature of Fqn.
+    *
+    * @return an unmodifiable list
+    */
+   public List peekElements()
+   {
+      return elements;
+   }
+
+   /**
+    * Compares this Fqn to another using {@link FqnComparator}.
+    */
+   public int compareTo(Fqn fqn)
+   {
+      return FqnComparator.INSTANCE.compare(this, fqn);
+   }
+
+   /**
+    * Creates a new Fqn whose ancestor has been replaced with the new ancestor passed in.
+    *
+    * @param oldAncestor old ancestor to replace
+    * @param newAncestor nw ancestor to replace with
+    * @return a new Fqn with ancestors replaced.
+    */
+   public Fqn replaceAncestor(Fqn oldAncestor, Fqn newAncestor)
+   {
+      if (!isChildOf(oldAncestor))
+         throw new IllegalArgumentException("Old ancestor must be an ancestor of the current Fqn!");
+      Fqn subFqn = this.getSubFqn(oldAncestor.size(), size());
+      return Fqn.fromRelativeFqn(newAncestor, subFqn);
+   }
+}
\ No newline at end of file

Copied: core/branches/flat/src/main/java/org/jboss/starobrno/tree/FqnComparator.java (from rev 7085, core/branches/flat/src/main/java/org/jboss/cache/FqnComparator.java)
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/tree/FqnComparator.java	                        (rev 0)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/tree/FqnComparator.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -0,0 +1,121 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * 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.
+ *
+ * This software 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.starobrno.tree;
+
+import net.jcip.annotations.Immutable;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * Compares the order of two FQN.
+ * Sorts by name, then by depth, e.g.
+ * <pre>
+ * aaa/bbb
+ * xxx
+ * xxx/ccc
+ * </pre>
+ *
+ * @author Manik Surtani (<a href="mailto:manik at jboss.org">manik at jboss.org</a>)
+ * @author Steve Woodcock (<a href="mailto:stevew at jofti.com">stevew at jofti.com</a>)
+ */
+ at Immutable
+public class FqnComparator implements Comparator<Fqn>, Serializable
+{
+   public static final FqnComparator INSTANCE = new FqnComparator();
+
+   /**
+    * Returns -1 if the first comes before; 0 if they are the same; 1 if the
+    * second Fqn comes before.  <code>null</code> always comes first.
+    */
+   public int compare(Fqn fqn1, Fqn fqn2)
+   {
+      int s1 = fqn1.size();
+      int s2 = fqn2.size();
+
+      if (s1 == 0)
+      {
+         return (s2 == 0) ? 0 : -1;
+      }
+
+      if (s2 == 0)
+      {
+         return 1;
+      }
+
+//      if (fqn1.getClass().equals(StringFqn.class) && fqn2.getClass().equals(StringFqn.class))
+//      {
+//         StringFqn sfqn1 = (StringFqn) fqn1;
+//         StringFqn sfqn2 = (StringFqn) fqn2;
+//         return sfqn1.stringRepresentation.compareTo(sfqn2.stringRepresentation);
+//      }
+      int size = Math.min(s1, s2);
+
+      for (int i = 0; i < size; i++)
+      {
+         Object e1 = fqn1.get(i);
+         Object e2 = fqn2.get(i);
+         if (e1 == e2)
+         {
+            continue;
+         }
+         if (e1 == null)
+         {
+            return 0;
+         }
+         if (e2 == null)
+         {
+            return 1;
+         }
+         if (!e1.equals(e2))
+         {
+            int c = compareElements(e1, e2);
+            if (c != 0)
+            {
+               return c;
+            }
+         }
+      }
+
+      return s1 - s2;
+   }
+
+   /**
+    * Compares two Fqn elements.
+    * If e1 and e2 are the same class and e1 implements Comparable,
+    * returns e1.compareTo(e2).
+    * Otherwise, returns e1.toString().compareTo(e2.toString()).
+    */
+   private int compareElements(Object e1, Object e2)
+   {
+      if (e1.getClass() == e2.getClass() && e1 instanceof Comparable)
+      {
+         return ((Comparable<Object>) e1).compareTo(e2);
+      }
+      else
+      {
+         return e1.toString().compareTo(e2.toString());
+      }
+   }
+
+
+}
\ No newline at end of file

Added: core/branches/flat/src/main/java/org/jboss/starobrno/tree/Node.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/tree/Node.java	                        (rev 0)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/tree/Node.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -0,0 +1,299 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * 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.
+ *
+ * This software 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.starobrno.tree;
+
+import net.jcip.annotations.ThreadSafe;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Node is a {@link Fqn named} logical grouping of data in the JBoss {@link Cache}.
+ * A node should be used to contain data for a single data record, for example
+ * information about a particular person or account.
+ * <p/>
+ * One purpose of grouping cache data into separate nodes is to minimize transaction
+ * locking interference, and increase concurrency.  So for example, when multiple threads or
+ * possibly distributed caches are acccessing different accounts simultaneously.
+ * <p/>
+ * Another is that when making changes to this node, its data might be kept in a single
+ * database row or file on disk. (Persisted via the use of a {@link org.jboss.cache.loader.CacheLoader}.)
+ * <p/>
+ * A node has references to its children, parent (each node except the root - defined by {@link Fqn#ROOT} - has
+ * a single parent) and data contained within the node (as key/value pairs).  The
+ * data access methods are similar to the collections {@link Map} interface,
+ * but some are read-only or return copies of the underlying the data.
+ * <p/>
+ *
+ * @author <a href="mailto:manik AT jboss DOT org">Manik Surtani (manik AT jboss DOT org)</a>
+ * @see Cache
+ * @since 2.0.0
+ */
+ at ThreadSafe
+public interface Node<K, V>
+{
+   /**
+    * Returns the parent node.
+    * If this is the root node, this method returns <code>null</code>.
+    *
+    * @return the parent node, or null if this is the root node
+    */
+   Node<K, V> getParent();
+
+   /**
+    * Returns an immutable set of children nodes.
+    *
+    * @return an immutable {@link Set} of child nodes.  Empty {@link Set} if there aren't any children.
+    */
+   Set<Node<K, V>> getChildren();
+
+   /**
+    * Returns an immutable set of children node names.
+    *
+    * @return an immutable {@link Set} of child node names.  Empty {@link Set} if there aren't any children.
+    */
+   Set<Object> getChildrenNames();
+
+   /**
+    * Returns a map containing the data in this {@link Node}.
+    *
+    * @return a {@link Map} containing the data in this {@link Node}.  If there is no data, an empty {@link Map} is returned.  The {@link Map} returned is always immutable.
+    */
+   Map<K, V> getData();
+
+   /**
+    * Returns a {@link Set} containing the data in this {@link Node}.
+    *
+    * @return a {@link Set} containing the data in this {@link Node}.  If there is no data, an empty {@link Set} is returned.  The {@link Set} returned is always immutable.
+    */
+   Set<K> getKeys();
+
+   /**
+    * Returns the {@link Fqn} which represents the location of this {@link Node} in the cache structure.  The {@link Fqn} returned is absolute.
+    *
+    * @return The {@link Fqn} which represents the location of this {@link Node} in the cache structure.  The {@link Fqn} returned is absolute.
+    */
+   Fqn getFqn();
+
+   /**
+    * Adds a child node with the given {@link Fqn} under the current node.  Returns the newly created node.
+    * <p/>
+    * If the child exists returns the child node anyway.  Guaranteed to return a non-null node.
+    * <p/>
+    * The {@link Fqn} passed in is relative to the current node.  The new child node will have an absolute fqn
+    * calculated as follows: <pre>new Fqn(getFqn(), f)</pre>.  See {@link Fqn} for the operation of this constructor.
+    *
+    * @param f {@link Fqn} of the child node, relative to the current node.
+    * @return the newly created node, or the existing node if one already exists.
+    */
+   Node<K, V> addChild(Fqn f);
+
+   /**
+    * Removes a child node specified by the given relative {@link Fqn}.
+    * <p/>
+    * If you wish to remove children based on absolute {@link Fqn}s, use the {@link Cache} interface instead.
+    *
+    * @param f {@link Fqn} of the child node, relative to the current node.
+    * @return true if the node was found and removed, false otherwise
+    */
+   boolean removeChild(Fqn f);
+
+   /**
+    * Removes a child node specified by the given name.
+    *
+    * @param childName name of the child node, directly under the current node.
+    * @return true if the node was found and removed, false otherwise
+    */
+   boolean removeChild(Object childName);
+
+
+   /**
+    * Returns the child node
+    *
+    * @param f {@link Fqn} of the child node
+    * @return null if the child does not exist.
+    */
+   Node<K, V> getChild(Fqn f);
+
+   /**
+    * @param name name of the child
+    * @return a direct child of the current node.
+    */
+   Node<K, V> getChild(Object name);
+
+   /**
+    * Associates the specified value with the specified key for this node.
+    * If this node previously contained a mapping for this key, the old value is replaced by the specified value.
+    *
+    * @param key   key with which the specified value is to be associated.
+    * @param value value to be associated with the specified key.
+    * @return Returns the old value contained under this key.  Null if key doesn't exist.
+    */
+   V put(K key, V value);
+
+   /**
+    * If the specified key is not already associated with a value, associate it with the given value, and returns the
+    * Object (if any) that occupied the space, or null.
+    * <p/>
+    * Equivalent to calling
+    * <pre>
+    *   if (!node.getKeys().contains(key))
+    *     return node.put(key, value);
+    *   else
+    *     return node.get(key);
+    * </pre>
+    * <p/>
+    * except that this is atomic.
+    *
+    * @param key   key with which the specified value is to be associated.
+    * @param value value to be associated with the specified key.
+    * @return previous value associated with specified key, or null if there was no mapping for key.
+    */
+   V putIfAbsent(K key, V value);
+
+   /**
+    * Replace entry for key only if currently mapped to some value.
+    * Acts as
+    * <pre>
+    * if ((node.getKeys().contains(key))
+    * {
+    *     return node.put(key, value);
+    * }
+    * else
+    *     return null;
+    * </pre>
+    * <p/>
+    * except that this is atomic.
+    *
+    * @param key   key with which the specified value is associated.
+    * @param value value to be associated with the specified key.
+    * @return previous value associated with specified key, or <tt>null</tt>
+    *         if there was no mapping for key.
+    */
+   V replace(K key, V value);
+
+   /**
+    * Replace entry for key only if currently mapped to given value.
+    * Acts as
+    * <pre>
+    * if (node.get(key).equals(oldValue))
+    * {
+    *     node.put(key, newValue);
+    *     return true;
+    * }
+    * else
+    *     return false;
+    * </pre>
+    * <p/>
+    * except that this is atomic.
+    *
+    * @param key      key with which the specified value is associated.
+    * @param oldValue value expected to be associated with the specified key.
+    * @param newValue value to be associated with the specified key.
+    * @return true if the value was replaced
+    */
+   boolean replace(K key, V oldValue, V newValue);
+
+
+   /**
+    * Copies all of the mappings from the specified map to this node's map.
+    * If any data exists, existing keys are overwritten with the keys in the new map.
+    * The behavior is equivalent to:
+    * <pre>
+    * Node node;
+    * for (Map.Entry me : map.entrySet())
+    *   node.put(me.getKey(), me.getValue());
+    * </pre>
+    *
+    * @param map map to copy from
+    */
+   void putAll(Map<? extends K, ? extends V> map);
+
+   /**
+    * Similar to {@link #putAll(java.util.Map)} except that it removes any entries that exists in
+    * the data map first.  Note that this happens atomically, under a single lock.  This is the analogous
+    * to doing a {@link #clearData()} followed by a {@link #putAll(java.util.Map)} in the same transaction.
+    *
+    * @param map map to copy from
+    */
+   void replaceAll(Map<? extends K, ? extends V> map);
+
+
+   /**
+    * Returns the value to which this node maps the specified key.
+    * Returns <code>null</code> if the node contains no mapping for this key.
+    *
+    * @param key key of the data to return
+    * @return the value to which this node maps the specified key, or <code>null</code> if the map contains no mapping for this key
+    */
+   V get(K key);
+
+   /**
+    * Removes the mapping for this key from this node if it is present.
+    * Returns the value to which the node previously associated the key,
+    * or <code>null</code> if the node contained no mapping for this key
+    *
+    * @param key key whose mapping is to be removed
+    * @return previous value associated with specified key, or <code>null</code>
+    *         if there was no mapping for key
+    */
+   V remove(K key);
+
+   /**
+    * Removes all mappings from the node's data map.
+    */
+   void clearData();
+
+   /**
+    * @return the number of elements (key/value pairs) in the node's data map.
+    */
+   int dataSize();
+
+   /**
+    * Returns true if the child node denoted by the relative {@link Fqn} passed in exists.
+    *
+    * @param f {@link Fqn} relative to the current node of the child you are testing the existence of.
+    * @return true if the child node denoted by the relative {@link Fqn} passed in exists.
+    */
+   boolean hasChild(Fqn f);
+
+   /**
+    * Returns true if the child node denoted by the Object name passed in exists.
+    *
+    * @param o name of the child, relative to the current node
+    * @return true if the child node denoted by the name passed in exists.
+    */
+   boolean hasChild(Object o);
+
+   /**
+    * Tests if a node reference is still valid.  A node reference may become invalid if it has been removed, invalidated
+    * or moved, either locally or remotely.  If a node is invalid, it should be fetched again from the cache or a valid
+    * parent node.  Operations on invalid nodes will throw a {@link org.jboss.cache.NodeNotValidException}.
+    *
+    * @return true if the node is valid.
+    */
+   boolean isValid();
+
+   void evict();
+
+   void removeChildren();
+}

Added: core/branches/flat/src/main/java/org/jboss/starobrno/tree/NodeImpl.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/tree/NodeImpl.java	                        (rev 0)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/tree/NodeImpl.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -0,0 +1,398 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * 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.
+ *
+ * This software 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.starobrno.tree;
+
+import org.jboss.starobrno.Cache;
+import org.jboss.starobrno.util.Immutables;
+import org.jboss.starobrno.util.Util;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * // TODO: MANIK: Document this
+ *
+ * @author Manik Surtani (<a href="mailto:manik AT jboss DOT org">manik AT jboss DOT org</a>)
+ * @since 3.0
+ */
+public class NodeImpl<K, V> extends TreeStructureSupport implements Node<K, V>
+{
+   Fqn fqn;
+   Key dataKey, structureKey;
+
+   public NodeImpl(Fqn fqn, Cache cache, ThreadLocal<Boolean> atomicityContainer)
+   {
+      super(atomicityContainer, cache);
+      this.fqn = fqn;
+      dataKey = new Key(fqn, Type.DATA);
+      structureKey = new Key(fqn, Type.STRUCTURE);
+   }
+
+   public Node<K, V> getParent()
+   {
+      if (fqn.isRoot()) return this;
+      return new NodeImpl(fqn.getParent(), cache, batchStarted);
+   }
+
+   public Set<Node<K, V>> getChildren()
+   {
+      startAtomic();
+      try
+      {
+         Set set = new HashSet();
+         for (Fqn f : getStructure().children.values())
+         {
+            NodeImpl n = new NodeImpl(f, cache, batchStarted);
+            set.add(n);
+         }
+         return Immutables.immutableSetWrap(set);
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public Set<Object> getChildrenNames()
+   {
+      return Immutables.immutableSetCopy(getStructure().children.keySet());
+   }
+
+   public Map<K, V> getData()
+   {
+      return Immutables.immutableMapCopy(getDataInternal());
+   }
+
+   public Set<K> getKeys()
+   {
+      startAtomic();
+      try
+      {
+         return getData().keySet();
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public Fqn getFqn()
+   {
+      return fqn;
+   }
+
+   public Node<K, V> addChild(Fqn f)
+   {
+      startAtomic();
+      try
+      {
+         Fqn absoluteChildFqn = Fqn.fromRelativeFqn(fqn, f);
+         NodeImpl child = new NodeImpl(absoluteChildFqn, cache, batchStarted);
+         Structure s = getStructure();
+         s.children.put(f.getLastElement(), absoluteChildFqn);
+         updateStructure(s);
+         createNodeInCache(absoluteChildFqn);
+         return child;
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public boolean removeChild(Fqn f)
+   {
+      return removeChild(f.getLastElement());
+   }
+
+   public boolean removeChild(Object childName)
+   {
+      startAtomic();
+      try
+      {
+         Structure s = (Structure) cache.get(structureKey);
+         Fqn childFqn = s.children.remove(childName);
+         if (childFqn != null)
+         {
+            Node child = new NodeImpl(childFqn, cache, batchStarted);
+            child.removeChildren();
+            cache.remove(new Key(childFqn, Type.DATA));
+            cache.remove(new Key(childFqn, Type.STRUCTURE));
+            updateStructure(s);
+            return true;
+         }
+
+         return false;
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public Node<K, V> getChild(Fqn f)
+   {
+      startAtomic();
+      try
+      {
+         if (hasChild(f))
+            return new NodeImpl(Fqn.fromRelativeFqn(fqn, f), cache, batchStarted);
+         else
+            return null;
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public Node<K, V> getChild(Object name)
+   {
+      startAtomic();
+      try
+      {
+         if (hasChild(name))
+            return new NodeImpl(Fqn.fromRelativeElements(fqn, name), cache, batchStarted);
+         else
+            return null;
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public V put(K key, V value)
+   {
+      startAtomic();
+      try
+      {
+         Map data = getDataInternal();
+         Object result = data.put(key, value);
+         putAll(data);
+         return (V) result;
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public V putIfAbsent(K key, V value)
+   {
+      startAtomic();
+      try
+      {
+         Map data = getData();
+         Object result = null;
+         if (!data.containsKey(key))
+         {
+            result = data.put(key, value);
+            putAll(data);
+         }
+
+         return (V) result;
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public V replace(K key, V value)
+   {
+      startAtomic();
+      try
+      {
+         Map data = getData();
+         V old = (V) data.put(key, value);
+         putData(data);
+         return old;
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public boolean replace(K key, V oldValue, V newValue)
+   {
+      startAtomic();
+      try
+      {
+         Map data = getData();
+         V old = (V) data.get(key);
+         if (Util.safeEquals(oldValue, old))
+         {
+            data.put(key, newValue);
+            return true;
+         }
+         return false;
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public void putAll(Map<? extends K, ? extends V> map)
+   {
+      startAtomic();
+      try
+      {
+         Map data = getDataInternal();
+         data.putAll(map);
+         putData(data);
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public void replaceAll(Map<? extends K, ? extends V> map)
+   {
+      putData(map);
+   }
+
+   public V get(K key)
+   {
+      return getData().get(key);
+   }
+
+   public V remove(K key)
+   {
+      startAtomic();
+      try
+      {
+         Map data = getDataInternal();
+         V old = (V) data.remove(key);
+         putData(data);
+         return old;
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public void clearData()
+   {
+      putData(Collections.emptyMap());
+   }
+
+   public int dataSize()
+   {
+      return getData().size();
+   }
+
+   public boolean hasChild(Fqn f)
+   {
+      if (f.size() > 1)
+      {
+         // indirect child.
+         Fqn absoluteFqn = Fqn.fromRelativeFqn(fqn, f);
+         return exists(absoluteFqn);
+      }
+      else
+      {
+         return hasChild(f.getLastElement());
+      }
+   }
+
+   public boolean hasChild(Object o)
+   {
+      return getStructure().children.containsKey(o);
+   }
+
+   public boolean isValid()
+   {
+      return cache.containsKey(dataKey);
+   }
+
+   public void removeChildren()
+   {
+      startAtomic();
+      try
+      {
+         Structure s = getStructure();
+         for (Object o : Immutables.immutableSetCopy(s.children.keySet())) removeChild(o);
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public void evict()
+   {
+      startAtomic();
+      try
+      {
+         cache.evict(structureKey);
+         cache.evict(dataKey);
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   void putData(Map m)
+   {
+      cache.put(dataKey, m);
+   }
+
+   Map<K, V> getDataInternal()
+   {
+      return (Map<K, V>) cache.get(dataKey);
+   }
+
+   void updateStructure(Structure s)
+   {
+      cache.put(structureKey, s);
+   }
+
+   Structure getStructure()
+   {
+      return (Structure) cache.get(structureKey);
+   }
+
+   public boolean equals(Object o)
+   {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+
+      NodeImpl node = (NodeImpl) o;
+
+      if (fqn != null ? !fqn.equals(node.fqn) : node.fqn != null) return false;
+
+      return true;
+   }
+
+   public int hashCode()
+   {
+      return (fqn != null ? fqn.hashCode() : 0);
+   }
+}

Added: core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeCache.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeCache.java	                        (rev 0)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeCache.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -0,0 +1,495 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * 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.
+ *
+ * This software 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.starobrno.tree;
+
+import org.jboss.cache.CacheStatus;
+import org.jboss.cache.NodeNotExistsException;
+import org.jboss.cache.Region;
+import org.jboss.starobrno.Cache;
+import org.jboss.starobrno.CacheException;
+import org.jboss.starobrno.config.Configuration;
+import org.jboss.starobrno.context.InvocationContext;
+import org.jboss.starobrno.interceptors.base.CommandInterceptor;
+import org.jboss.starobrno.lifecycle.Lifecycle;
+import org.jgroups.Address;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * // TODO: MANIK: Document this
+ *
+ * @author Manik Surtani (<a href="mailto:manik AT jboss DOT org">manik AT jboss DOT org</a>)
+ * @since 3.0
+ */
+public interface TreeCache<K, V> extends Lifecycle
+{
+   /**
+    * Returns the root node of this cache.
+    *
+    * @return the root node
+    */
+   Node<K, V> getRoot();
+
+   /**
+    * Adds a {@link org.jboss.cache.notifications.annotation.CacheListener}-annotated object to the entire cache.  The object passed in needs to be properly annotated with the
+    * {@link org.jboss.cache.notifications.annotation.CacheListener} annotation otherwise an {@link org.jboss.cache.notifications.IncorrectCacheListenerException} will be thrown.
+    *
+    * @param listener listener to add
+    */
+   void addCacheListener(Object listener);
+
+   /**
+    * Removes a {@link org.jboss.cache.notifications.annotation.CacheListener}-annotated object from the cache.  The object passed in needs to be properly annotated with the
+    * {@link org.jboss.cache.notifications.annotation.CacheListener} annotation otherwise an {@link org.jboss.cache.notifications.IncorrectCacheListenerException} will be thrown.
+    *
+    * @param listener listener to remove
+    */
+   void removeCacheListener(Object listener);
+
+   /**
+    * Retrieves an immutable {@link List} of objects annotated as {@link org.jboss.cache.notifications.annotation.CacheListener}s attached to the cache.
+    *
+    * @return an immutable {@link List} of objects annotated as {@link org.jboss.cache.notifications.annotation.CacheListener}s attached to the cache.
+    */
+   Set<Object> getCacheListeners();
+
+   /**
+    * Associates the specified value with the specified key for a {@link Node} in this cache.
+    * If the {@link Node} previously contained a mapping for this key, the old value is replaced by the specified value.
+    *
+    * @param fqn   <b><i>absolute</i></b> {@link Fqn} to the {@link Node} to be accessed.
+    * @param key   key with which the specified value is to be associated.
+    * @param value value to be associated with the specified key.
+    * @return previous value associated with specified key, or <code>null</code> if there was no mapping for key.
+    *         A <code>null</code> return can also indicate that the Node previously associated <code>null</code> with the specified key, if the implementation supports null values.
+    * @throws IllegalStateException if the cache is not in a started state.
+    */
+   V put(Fqn fqn, K key, V value);
+
+   /**
+    * Convenience method that takes a string representation of an Fqn.  Otherwise identical to {@link #put(Fqn, Object, Object)}
+    *
+    * @param fqn   String representation of the Fqn
+    * @param key   key with which the specified value is to be associated.
+    * @param value value to be associated with the specified key.
+    * @return previous value associated with specified key, or <code>null</code> if there was no mapping for key.
+    *         A <code>null</code> return can also indicate that the Node previously associated <code>null</code> with the specified key, if the implementation supports null values.
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+
+   V put(String fqn, K key, V value);
+
+   /**
+    * Under special operating behavior, associates the value with the specified key for a node identified by the Fqn passed in.
+    * <ul>
+    * <li> Only goes through if the node specified does not exist; no-op otherwise.</i>
+    * <li> Force asynchronous mode for replication to prevent any blocking.</li>
+    * <li> invalidation does not take place. </li>
+    * <li> 0ms lock timeout to prevent any blocking here either. If the lock is not acquired, this method is a no-op, and swallows the timeout exception.</li>
+    * <li> Ongoing transactions are suspended before this call, so failures here will not affect any ongoing transactions.</li>
+    * <li> Errors and exceptions are 'silent' - logged at a much lower level than normal, and this method does not throw exceptions</li>
+    * </ul>
+    * This method is for caching data that has an external representation in storage, where, concurrent modification and
+    * transactions are not a consideration, and failure to put the data in the cache should be treated as a 'suboptimal outcome'
+    * rather than a 'failing outcome'.
+    * <p/>
+    * An example of when this method is useful is when data is read from, for example, a legacy datastore, and is cached before
+    * returning the data to the caller.  Subsequent calls would prefer to get the data from the cache and if the data doesn't exist
+    * in the cache, fetch again from the legacy datastore.
+    * <p/>
+    * See <a href="http://jira.jboss.com/jira/browse/JBCACHE-848">JBCACHE-848</a> for details around this feature.
+    * <p/>
+    *
+    * @param fqn   <b><i>absolute</i></b> {@link Fqn} to the {@link Node} to be accessed.
+    * @param key   key with which the specified value is to be associated.
+    * @param value value to be associated with the specified key.
+    * @throws IllegalStateException if {@link #getCacheStatus()} would not return {@link org.jboss.cache.CacheStatus#STARTED}.
+    */
+   void putForExternalRead(Fqn fqn, K key, V value);
+
+   /**
+    * Copies all of the mappings from the specified map to a {@link Node}.
+    *
+    * @param fqn  <b><i>absolute</i></b> {@link Fqn} to the {@link Node} to copy the data to
+    * @param data mappings to copy
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   void put(Fqn fqn, Map<? extends K, ? extends V> data);
+
+   /**
+    * Convenience method that takes a string representation of an Fqn.  Otherwise identical to {@link #put(Fqn, java.util.Map)}
+    *
+    * @param fqn  String representation of the Fqn
+    * @param data data map to insert
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   void put(String fqn, Map<? extends K, ? extends V> data);
+
+   /**
+    * Removes the mapping for this key from a Node.
+    * Returns the value to which the Node previously associated the key, or
+    * <code>null</code> if the Node contained no mapping for this key.
+    *
+    * @param fqn <b><i>absolute</i></b> {@link Fqn} to the {@link Node} to be accessed.
+    * @param key key whose mapping is to be removed from the Node
+    * @return previous value associated with specified Node's key
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   V remove(Fqn fqn, K key);
+
+   /**
+    * Convenience method that takes a string representation of an Fqn.  Otherwise identical to {@link #remove(Fqn, Object)}
+    *
+    * @param fqn string representation of the Fqn to retrieve
+    * @param key key to remove
+    * @return old value removed, or null if the fqn does not exist
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   V remove(String fqn, K key);
+
+   /**
+    * Removes a {@link Node} indicated by absolute {@link Fqn}.
+    *
+    * @param fqn {@link Node} to remove
+    * @return true if the node was removed, false if the node was not found
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   boolean removeNode(Fqn fqn);
+
+   /**
+    * Convenience method that takes a string representation of an Fqn.  Otherwise identical to {@link #removeNode(Fqn)}
+    *
+    * @param fqn string representation of the Fqn to retrieve
+    * @return true if the node was found and removed, false otherwise
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   boolean removeNode(String fqn);
+
+   /**
+    * A convenience method to retrieve a node directly from the cache.  Equivalent to calling cache.getRoot().getChild(fqn).
+    *
+    * @param fqn fqn of the node to retrieve
+    * @return a Node object, or a null if the node does not exist.
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   Node<K, V> getNode(Fqn fqn);
+
+   /**
+    * Convenience method that takes a string representation of an Fqn.  Otherwise identical to {@link #getNode(Fqn)}
+    *
+    * @param fqn string representation of the Fqn to retrieve
+    * @return node, or null if the node does not exist
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   Node<K, V> getNode(String fqn);
+
+
+   /**
+    * Convenience method that allows for direct access to the data in a {@link Node}.
+    *
+    * @param fqn <b><i>absolute</i></b> {@link Fqn} to the {@link Node} to be accessed.
+    * @param key key under which value is to be retrieved.
+    * @return returns data held under specified key in {@link Node} denoted by specified Fqn.
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   V get(Fqn fqn, K key);
+
+   /**
+    * Convenience method that takes a string representation of an Fqn.  Otherwise identical to {@link #get(Fqn, Object)}
+    *
+    * @param fqn string representation of the Fqn to retrieve
+    * @param key key to fetch
+    * @return value, or null if the fqn does not exist.
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   V get(String fqn, K key);
+
+   /**
+    * Eviction call that evicts the specified {@link Node} from memory.
+    *
+    * @param fqn       <b><i>absolute</i></b> {@link Fqn} to the {@link Node} to be evicted.
+    * @param recursive evicts children as well
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   void evict(Fqn fqn, boolean recursive);
+
+   /**
+    * Eviction call that evicts the specified {@link Node} from memory.  Not recursive.
+    *
+    * @param fqn <b><i>absolute</i></b> {@link Fqn} to the {@link Node} to be evicted.
+    * @throws IllegalStateException if the cache is not in a started state
+    */
+   void evict(Fqn fqn);
+
+   /**
+    * Retrieves a {@link Region} for a given {@link Fqn}.  If the region does not exist,
+    * and <li>createIfAbsent</li> is true, then one is created.
+    * <p/>
+    * If not, parent Fqns will be consulted in turn for registered regions, gradually working up to
+    * Fqn.ROOT.  If no regions are defined in any of the parents either, a null is returned.
+    *
+    * @param fqn            Fqn that is contained in a region.
+    * @param createIfAbsent If true, will create a new associated region if not found.
+    * @return a MarshRegion. Null if none is found.
+    * @see Region
+    */
+   Region getRegion(Fqn fqn, boolean createIfAbsent);
+
+   /**
+    * Removes a region denoted by the Fqn passed in.
+    *
+    * @param fqn of the region to remove
+    * @return true if a region did exist and was removed; false otherwise.
+    */
+   boolean removeRegion(Fqn fqn);
+
+   /**
+    * Gets where the cache currently is its lifecycle transitions.
+    *
+    * @return the CacheStatus. Will not return <code>null</code>.
+    */
+   CacheStatus getCacheStatus();
+
+   /**
+    * @return the current invocation context for the current invocation and cache instance.
+    * @throws IllegalStateException if the cache has been destroyed.
+    */
+   InvocationContext getInvocationContext();
+
+   /**
+    * Sets the passed in {@link org.jboss.cache.InvocationContext} as current.
+    *
+    * @param ctx invocation context to use
+    * @throws IllegalStateException if the cache has been destroyed.
+    */
+   void setInvocationContext(InvocationContext ctx);
+
+   /**
+    * Returns the local address of this cache in a cluster, or <code>null</code>
+    * if running in local mode.
+    *
+    * @return the local address of this cache in a cluster, or <code>null</code>
+    *         if running in local mode.
+    */
+   Address getLocalAddress();
+
+   /**
+    * Returns a list of members in the cluster, or <code>null</code>
+    * if running in local mode.
+    *
+    * @return a {@link List} of members in the cluster, or <code>null</code>
+    *         if running in local mode.
+    */
+   List<Address> getMembers();
+
+   /**
+    * Moves a part of the cache to a different subtree.
+    * <p/>
+    * E.g.:
+    * <p/>
+    * assume a cache structure such as:
+    * <p/>
+    * <pre>
+    *  /a/b/c
+    *  /a/b/d
+    *  /a/b/e
+    * <p/>
+    * <p/>
+    *  Fqn f1 = Fqn.fromString("/a/b/c");
+    *  Fqn f2 = Fqn.fromString("/a/b/d");
+    * <p/>
+    *  cache.move(f1, f2);
+    * </pre>
+    * <p/>
+    * Will result in:
+    * <pre>
+    * <p/>
+    * /a/b/d/c
+    * /a/b/e
+    * <p/>
+    * </pre>
+    * <p/>
+    * and now
+    * <p/>
+    * <pre>
+    *  Fqn f3 = Fqn.fromString("/a/b/e");
+    *  Fqn f4 = Fqn.fromString("/a");
+    *  cache.move(f3, f4);
+    * </pre>
+    * <p/>
+    * will result in:
+    * <pre>
+    * /a/b/d/c
+    * /a/e
+    * </pre>
+    * No-op if the node to be moved is the root node.
+    * <p/>
+    * <b>Note</b>: As of 3.0.0 and when using MVCC locking, more specific behaviour is defined as follows:
+    * <ul>
+    * <li>A no-op if the node is moved unto itself.  E.g., <tt>move(fqn, fqn.getParent())</tt> will not do anything.</li>
+    * <li>If a target node does not exist it will be created silently, to be more consistent with other APIs such as <tt>put()</tt> on a nonexistent node.</li>
+    * <li>If the source node does not exist this is a no-op, to be more consistent with other APIs such as <tt>get()</tt> on a nonexistent node.</li>
+    * </ul>
+    *
+    * @param nodeToMove the Fqn of the node to move.
+    * @param newParent  new location under which to attach the node being moved.
+    * @throws NodeNotExistsException may throw one of these if the target node does not exist or if a different thread has moved this node elsewhere already.
+    * @throws IllegalStateException  if {@link #getCacheStatus()} would not return {@link CacheStatus#STARTED}.
+    */
+   void move(Fqn nodeToMove, Fqn newParent) throws NodeNotExistsException;
+
+   /**
+    * Convenience method that takes in string representations of Fqns.  Otherwise identical to {@link #move(Fqn, Fqn)}
+    *
+    * @throws IllegalStateException if {@link #getCacheStatus()} would not return {@link CacheStatus#STARTED}.
+    */
+   void move(String nodeToMove, String newParent) throws NodeNotExistsException;
+
+   /**
+    * Returns the version of the cache as a string.
+    *
+    * @return the version string of the cache.
+    * @see Version#printVersion
+    */
+   String getVersion();
+
+   /**
+    * Retrieves a defensively copied data map of the underlying node.  A convenience method to retrieving a node and
+    * getting data from the node directly.
+    *
+    * @param fqn
+    * @return map of data, or an empty map
+    * @throws CacheException
+    * @throws IllegalStateException if {@link #getCacheStatus()} would not return {@link CacheStatus#STARTED}.
+    */
+   Map<K, V> getData(Fqn fqn);
+
+   /**
+    * Convenience method that takes in a String represenation of the Fqn.  Otherwise identical to {@link #getKeys(Fqn)}.
+    */
+   Set<K> getKeys(String fqn);
+
+   /**
+    * Returns a set of attribute keys for the Fqn.
+    * Returns null if the node is not found, otherwise a Set.
+    * The set is a copy of the actual keys for this node.
+    * <p/>
+    * A convenience method to retrieving a node and
+    * getting keys from the node directly.
+    *
+    * @param fqn name of the node
+    * @throws IllegalStateException if {@link #getCacheStatus()} would not return {@link CacheStatus#STARTED}.
+    */
+   Set<K> getKeys(Fqn fqn);
+
+   /**
+    * Convenience method that takes in a String represenation of the Fqn.  Otherwise identical to {@link #clearData(Fqn)}.
+    *
+    * @throws IllegalStateException if {@link #getCacheStatus()} would not return {@link CacheStatus#STARTED}.
+    */
+   void clearData(String fqn);
+
+   /**
+    * Removes the keys and properties from a named node.
+    * <p/>
+    * A convenience method to retrieving a node and
+    * getting keys from the node directly.
+    *
+    * @param fqn name of the node
+    * @throws IllegalStateException if {@link #getCacheStatus()} would not return {@link CacheStatus#STARTED}.
+    */
+   void clearData(Fqn fqn);
+
+   /**
+    * Starts a batch.  This is a lightweight batching mechanism that groups cache writes together and finally performs the
+    * write, persistence and/or replication when {@link #endBatch(boolean)} is called rather than for each invocation on the
+    * cache.
+    * <p/>
+    * Note that if there is an existing transaction in scope and the cache has been configured to use a JTA compliant
+    * transaction manager, calls to {@link #startBatch()} and {@link #endBatch(boolean)} are ignored and treated as no-ops.
+    * <p/>
+    *
+    * @see #endBatch(boolean)
+    * @since 3.0
+    */
+   void startBatch();
+
+   /**
+    * Ends an existing ongoing batch.  A no-op if a batch has not been started yet.
+    * <p/>
+    * Note that if there is an existing transaction in scope and the cache has been configured to use a JTA compliant
+    * transaction manager, calls to {@link #startBatch()} and {@link #endBatch(boolean)} are ignored and treated as no-ops.
+    * <p/>
+    *
+    * @param successful if <tt>true</tt>, changes made in the batch are committed.  If <tt>false</tt>, they are discarded.
+    * @see #startBatch()
+    * @since 3.0
+    */
+   void endBatch(boolean successful);
+
+   /**
+    * Adds a custom interceptor to the interceptor chain, at specified position, where the first interceptor in the chain
+    * is at position 0 and the last one at getInterceptorChain().size() - 1.
+    *
+    * @param i        the interceptor to add
+    * @param position the position to add the interceptor
+    * @since 3.0
+    */
+   void addInterceptor(CommandInterceptor i, int position);
+
+   /**
+    * Adds a custom interceptor to the interceptor chain, after an instance of the specified interceptor type.  Throws a
+    * cache exception if it cannot find an interceptor of the specified type.
+    *
+    * @param i                interceptor to add
+    * @param afterInterceptor interceptor type after which to place custom interceptor
+    * @since 3.0
+    */
+   void addInterceptor(CommandInterceptor i, Class<? extends CommandInterceptor> afterInterceptor);
+
+   /**
+    * Removes the interceptor at a specified position, where the first interceptor in the chain
+    * is at position 0 and the last one at getInterceptorChain().size() - 1.
+    *
+    * @param position the position at which to remove an interceptor
+    * @since 3.0
+    */
+   void removeInterceptor(int position);
+
+   /**
+    * Removes the interceptor of specified type.
+    *
+    * @param interceptorType type of interceptor to remove
+    * @since 3.0
+    */
+   void removeInterceptor(Class<? extends CommandInterceptor> interceptorType);
+
+   Configuration getConfiguration();
+
+   Cache getCache();
+
+   boolean exists(String fqn);
+
+   boolean exists(Fqn fqn);
+}

Added: core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeCacheImpl.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeCacheImpl.java	                        (rev 0)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeCacheImpl.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -0,0 +1,379 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * 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.
+ *
+ * This software 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.starobrno.tree;
+
+import org.jboss.cache.CacheStatus;
+import org.jboss.cache.NodeNotExistsException;
+import org.jboss.cache.Region;
+import org.jboss.starobrno.Cache;
+import org.jboss.starobrno.CacheException;
+import org.jboss.starobrno.config.Configuration;
+import org.jboss.starobrno.context.InvocationContext;
+import org.jboss.starobrno.interceptors.base.CommandInterceptor;
+import org.jgroups.Address;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Manik Surtani (<a href="mailto:manik AT jboss DOT org">manik AT jboss DOT org</a>)
+ */
+public class TreeCacheImpl<K, V> extends TreeStructureSupport implements TreeCache<K, V>
+{
+   public TreeCacheImpl(Cache<K, V> cache)
+   {
+      super(new ThreadLocal<Boolean>()
+      {
+         @Override
+         protected Boolean initialValue()
+         {
+            return false;
+         }
+      }, cache);
+      createRoot();
+   }
+
+   public Node<K, V> getRoot()
+   {
+      return new NodeImpl(Fqn.ROOT, cache, batchStarted);
+   }
+
+   public V put(String fqn, K key, V value)
+   {
+      return put(Fqn.fromString(fqn), key, value);
+   }
+
+   public void putForExternalRead(Fqn fqn, K key, V value)
+   {
+      put(fqn, key, value); // TODO implement this properly
+   }
+
+   public void put(Fqn fqn, Map<? extends K, ? extends V> data)
+   {
+      startAtomic();
+      try
+      {
+         getNode(fqn).putAll(data);
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public void put(String fqn, Map<? extends K, ? extends V> data)
+   {
+      put(Fqn.fromString(fqn), data);
+   }
+
+   public V remove(Fqn fqn, K key)
+   {
+      startAtomic();
+      try
+      {
+         return getNode(fqn).remove(key);
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public V remove(String fqn, K key)
+   {
+      return remove(Fqn.fromString(fqn), key);
+   }
+
+   public boolean removeNode(Fqn fqn)
+   {
+      if (fqn.isRoot()) return false;
+      startAtomic();
+      try
+      {
+         Node n = getNode(fqn.getParent());
+         return n == null ? false : n.removeChild(fqn.getLastElement());
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public boolean removeNode(String fqn)
+   {
+      return removeNode(Fqn.fromString(fqn));
+   }
+
+   public Node<K, V> getNode(Fqn fqn)
+   {
+      startAtomic();
+      try
+      {
+         if (exists(fqn))
+            return new NodeImpl(fqn, cache, batchStarted);
+         else return null;
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public Node<K, V> getNode(String fqn)
+   {
+      return getNode(Fqn.fromString(fqn));
+   }
+
+   public V get(Fqn fqn, K key)
+   {
+      startAtomic();
+      try
+      {
+         Node n = getNode(fqn);
+
+         return (V) (n == null ? null : n.get(key));
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public boolean exists(String f)
+   {
+      return exists(Fqn.fromString(f));
+   }
+
+   public V get(String fqn, K key)
+   {
+      return get(Fqn.fromString(fqn), key);
+   }
+
+   public void evict(Fqn fqn, boolean recursive)
+   {
+      //TODO: Autogenerated.  Implement me properly
+   }
+
+   public void evict(Fqn fqn)
+   {
+      startAtomic();
+      try
+      {
+         getNode(fqn).evict();
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public Region getRegion(Fqn fqn, boolean createIfAbsent)
+   {
+      return null;  //TODO: Autogenerated.  Implement me properly
+   }
+
+   public boolean removeRegion(Fqn fqn)
+   {
+      return false;  //TODO: Autogenerated.  Implement me properly
+   }
+
+   public void move(Fqn nodeToMove, Fqn newParent) throws NodeNotExistsException
+   {
+      //TODO: Autogenerated.  Implement me properly
+   }
+
+   public void move(String nodeToMove, String newParent) throws NodeNotExistsException
+   {
+      move(Fqn.fromString(nodeToMove), Fqn.fromString(newParent));
+   }
+
+   public Map<K, V> getData(Fqn fqn)
+   {
+      startAtomic();
+      try
+      {
+         return getNode(fqn).getData();
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public Set<K> getKeys(String fqn)
+   {
+      return getKeys(Fqn.fromString(fqn));
+   }
+
+   public Set<K> getKeys(Fqn fqn)
+   {
+      startAtomic();
+      try
+      {
+         return getNode(fqn).getKeys();
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public void clearData(String fqn)
+   {
+      clearData(Fqn.fromString(fqn));
+   }
+
+   public void clearData(Fqn fqn)
+   {
+      startAtomic();
+      try
+      {
+         getNode(fqn).clearData();
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   public V put(Fqn fqn, K key, V value)
+   {
+      startAtomic();
+      try
+      {
+         createNodeInCache(fqn);
+         return getNode(fqn).put(key, value);
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   // ------------------ nothing different; just delegate to the cache
+   public void addCacheListener(Object listener)
+   {
+      cache.addCacheListener(listener);
+   }
+
+   public void removeCacheListener(Object listener)
+   {
+      cache.removeCacheListener(listener);
+   }
+
+   public Set<Object> getCacheListeners()
+   {
+      return cache.getCacheListeners();
+   }
+
+   public void startBatch()
+   {
+      cache.startBatch();
+   }
+
+   public void endBatch(boolean successful)
+   {
+      cache.endBatch(successful);
+   }
+
+   public void addInterceptor(CommandInterceptor i, int position)
+   {
+      cache.addInterceptor(i, position);
+   }
+
+   public void addInterceptor(CommandInterceptor i, Class<? extends CommandInterceptor> afterInterceptor)
+   {
+      cache.addInterceptor(i, afterInterceptor);
+   }
+
+   public void removeInterceptor(int position)
+   {
+      cache.removeInterceptor(position);
+   }
+
+   public void removeInterceptor(Class<? extends CommandInterceptor> interceptorType)
+   {
+      cache.removeInterceptor(interceptorType);
+   }
+
+   public Configuration getConfiguration()
+   {
+      return cache.getConfiguration();
+   }
+
+   public Cache getCache()
+   {
+      return cache;
+   }
+
+   public String getVersion()
+   {
+      return cache.getVersion();
+   }
+
+   public CacheStatus getCacheStatus()
+   {
+      return cache.getCacheStatus();
+   }
+
+   public InvocationContext getInvocationContext()
+   {
+      return cache.getInvocationContext();
+   }
+
+   public void setInvocationContext(InvocationContext ctx)
+   {
+      cache.setInvocationContext(ctx);
+   }
+
+   public Address getLocalAddress()
+   {
+      return cache.getLocalAddress();
+   }
+
+   public List<Address> getMembers()
+   {
+      return cache.getMembers();
+   }
+
+   public void start() throws CacheException
+   {
+      cache.start();
+      createRoot();
+   }
+
+   public void stop()
+   {
+      cache.stop();
+   }
+
+   private void createRoot()
+   {
+      createNodeInCache(Fqn.ROOT);
+   }
+
+   public String toString()
+   {
+      return cache.toString();
+   }
+}

Added: core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeStructureSupport.java
===================================================================
--- core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeStructureSupport.java	                        (rev 0)
+++ core/branches/flat/src/main/java/org/jboss/starobrno/tree/TreeStructureSupport.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -0,0 +1,161 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * 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.
+ *
+ * This software 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.starobrno.tree;
+
+import org.jboss.starobrno.Cache;
+import org.jboss.starobrno.util.Util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class TreeStructureSupport
+{
+   ThreadLocal<Boolean> batchStarted;
+   Cache cache;
+   boolean batchOwner;
+
+   public TreeStructureSupport(ThreadLocal<Boolean> batchStarted, Cache cache)
+   {
+      this.batchStarted = batchStarted;
+      this.cache = cache;
+   }
+
+   void startAtomic()
+   {
+      if (!batchStarted.get())
+      {
+         cache.startBatch();
+         batchStarted.set(true);
+         batchOwner = true;
+      }
+   }
+
+   void endAtomic()
+   {
+      if (batchOwner)
+      {
+         cache.endBatch(true);
+         batchStarted.set(false);
+      }
+   }
+
+   public boolean exists(Fqn f)
+   {
+      startAtomic();
+      try
+      {
+         return cache.containsKey(new Key(f, Type.DATA)) && cache.containsKey(new Key(f, Type.STRUCTURE));
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   void createNodeInCache(Fqn fqn)
+   {
+      startAtomic();
+      try
+      {
+         Fqn parent = fqn.getParent();
+         if (!fqn.isRoot())
+         {
+            if (!exists(parent)) createNodeInCache(parent);
+            Structure parentStructure = getStructure(parent);
+            parentStructure.children.put(fqn.getLastElement(), fqn);
+            updateStructure(parent, parentStructure);
+         }
+         Key dataKey = new Key(fqn, Type.DATA);
+         Key structureKey = new Key(fqn, Type.STRUCTURE);
+         if (cache.containsKey(dataKey) && cache.containsKey(structureKey)) return;
+         Structure s = new Structure();
+         s.parent = fqn.getParent();
+         cache.put(dataKey, new HashMap());
+         cache.put(structureKey, s);
+      }
+      finally
+      {
+         endAtomic();
+      }
+   }
+
+   enum Type
+   {
+      DATA, STRUCTURE
+   }
+
+   static class Key
+   {
+      Type contents;
+      Fqn fqn;
+
+      Key(Fqn fqn, Type contents)
+      {
+         this.contents = contents;
+         this.fqn = fqn;
+      }
+
+      public boolean equals(Object o)
+      {
+         if (this == o) return true;
+         if (o == null || getClass() != o.getClass()) return false;
+
+         Key key = (Key) o;
+
+         if (contents != key.contents) return false;
+         if (!Util.safeEquals(fqn, key.fqn)) return false;
+
+         return true;
+      }
+
+      public int hashCode()
+      {
+         int hc = (contents != null ? contents.hashCode() : 1);
+         hc = 31 * hc + fqn.hashCode();
+         return hc;
+      }
+
+      public String toString()
+      {
+         return "Key{" +
+               "contents=" + contents +
+               ", fqn=" + fqn +
+               '}';
+      }
+   }
+
+   static class Structure
+   {
+      Map<Object, Fqn> children = new HashMap<Object, Fqn>();
+      Fqn parent;
+   }
+
+   Structure getStructure(Fqn fqn)
+   {
+      return (Structure) cache.get(new Key(fqn, Type.STRUCTURE));
+   }
+
+   void updateStructure(Fqn fqn, Structure s)
+   {
+      cache.put(new Key(fqn, Type.STRUCTURE), s);
+   }
+}

Added: core/branches/flat/src/test/java/org/jboss/starobrno/tree/api/NodeAPITest.java
===================================================================
--- core/branches/flat/src/test/java/org/jboss/starobrno/tree/api/NodeAPITest.java	                        (rev 0)
+++ core/branches/flat/src/test/java/org/jboss/starobrno/tree/api/NodeAPITest.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -0,0 +1,391 @@
+package org.jboss.cache.api;
+
+import org.jboss.starobrno.CacheSPI;
+import org.jboss.starobrno.UnitTestCacheFactory;
+import org.jboss.starobrno.tree.Fqn;
+import org.jboss.starobrno.tree.Node;
+import org.jboss.starobrno.tree.TreeCache;
+import org.jboss.starobrno.tree.TreeCacheImpl;
+import org.jboss.starobrno.util.TestingUtil;
+import static org.testng.AssertJUnit.*;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import javax.transaction.TransactionManager;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests {@link org.jboss.cache.Node}-centric operations
+ *
+ * @author <a href="mailto:manik AT jboss DOT org">Manik Surtani</a>
+ * @since 2.0.0
+ */
+ at Test(groups = {"functional", "pessimistic"})
+public class NodeAPITest
+{
+   protected ThreadLocal<TreeCache<Object, Object>> cacheTL = new ThreadLocal<TreeCache<Object, Object>>();
+   protected static final Fqn A = Fqn.fromString("/a"), B = Fqn.fromString("/b"), C = Fqn.fromString("/c"), D = Fqn.fromString("/d");
+   protected Fqn A_B = Fqn.fromRelativeFqn(A, B);
+   protected Fqn A_C = Fqn.fromRelativeFqn(A, C);
+   protected TransactionManager tm;
+
+   @BeforeMethod(alwaysRun = true)
+   public void setUp() throws Exception
+   {
+      // start a single cache instance
+      CacheSPI<Object, Object> cache = (CacheSPI<Object, Object>) new UnitTestCacheFactory<Object, Object>().createCache("configs/local-tx.xml", false);
+      cache.getConfiguration().setInvocationBatchingEnabled(true);
+      cache.start();
+      cacheTL.set(new TreeCacheImpl(cache));
+      tm = cache.getTransactionManager();
+   }
+
+   @AfterMethod(alwaysRun = true)
+   public void tearDown()
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      TestingUtil.killCaches(cache.getCache());
+      cacheTL.set(null);
+   }
+
+   public void testAddingData()
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+      Node<Object, Object> nodeA = rootNode.addChild(A);
+      nodeA.put("key", "value");
+
+      assertEquals("value", nodeA.get("key"));
+   }
+
+   public void testAddingDataTx() throws Exception
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+      tm.begin();
+      Node<Object, Object> nodeA = rootNode.addChild(A);
+      nodeA.put("key", "value");
+
+      assertEquals("value", nodeA.get("key"));
+      tm.commit();
+   }
+
+   public void testOverwritingDataTx() throws Exception
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      Node<Object, Object> nodeA = rootNode.addChild(A);
+      nodeA.put("key", "value");
+      assertEquals("value", nodeA.get("key"));
+      tm.begin();
+      rootNode.removeChild(A);
+      cache.put(A, "k2", "v2");
+      tm.commit();
+      assertNull(nodeA.get("key"));
+      assertEquals("v2", nodeA.get("k2"));
+   }
+
+
+   /**
+    * Remember, Fqns are relative!!
+    */
+   public void testParentsAndChildren()
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      Node<Object, Object> nodeA = rootNode.addChild(A);
+      Node<Object, Object> nodeB = nodeA.addChild(B);
+      Node<Object, Object> nodeC = nodeA.addChild(C);
+      Node<Object, Object> nodeD = rootNode.addChild(D);
+
+      assertEquals(rootNode, nodeA.getParent());
+      assertEquals(nodeA, nodeB.getParent());
+      assertEquals(nodeA, nodeC.getParent());
+      assertEquals(rootNode, nodeD.getParent());
+
+      assertTrue(rootNode.hasChild(A));
+      assertFalse(rootNode.hasChild(B));
+      assertFalse(rootNode.hasChild(C));
+      assertTrue(rootNode.hasChild(D));
+
+      assertTrue(nodeA.hasChild(B));
+      assertTrue(nodeA.hasChild(C));
+
+      assertEquals(nodeA, rootNode.getChild(A));
+      assertEquals(nodeD, rootNode.getChild(D));
+      assertEquals(nodeB, nodeA.getChild(B));
+      assertEquals(nodeC, nodeA.getChild(C));
+
+      assertTrue(nodeA.getChildren().contains(nodeB));
+      assertTrue(nodeA.getChildren().contains(nodeC));
+      assertEquals(2, nodeA.getChildren().size());
+
+      assertTrue(rootNode.getChildren().contains(nodeA));
+      assertTrue(rootNode.getChildren().contains(nodeD));
+      assertEquals(2, rootNode.getChildren().size());
+
+      assertEquals(true, rootNode.removeChild(A));
+      assertFalse(rootNode.getChildren().contains(nodeA));
+      assertTrue(rootNode.getChildren().contains(nodeD));
+      assertEquals(1, rootNode.getChildren().size());
+
+      assertEquals("double remove", false, rootNode.removeChild(A));
+      assertEquals("double remove", false, rootNode.removeChild(A.getLastElement()));
+   }
+
+
+   public void testImmutabilityOfData()
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      rootNode.put("key", "value");
+      Map<Object, Object> m = rootNode.getData();
+      try
+      {
+         m.put("x", "y");
+         fail("Map should be immutable!!");
+      }
+      catch (Exception e)
+      {
+         // expected
+      }
+
+      try
+      {
+         rootNode.getKeys().add(new Object());
+         fail("Key set should be immutable");
+      }
+      catch (Exception e)
+      {
+         // expected
+      }
+   }
+
+   public void testDefensiveCopyOfData()
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      rootNode.put("key", "value");
+      Map<Object, Object> data = rootNode.getData();
+      Set<Object> keys = rootNode.getKeys();
+
+      assert keys.size() == 1;
+      assert keys.contains("key");
+
+      assert data.size() == 1;
+      assert data.containsKey("key");
+
+      // now change stuff.
+
+      rootNode.put("key2", "value2");
+
+      // assert that the collections we initially got have not changed.
+      assert keys.size() == 1;
+      assert keys.contains("key");
+
+      assert data.size() == 1;
+      assert data.containsKey("key");
+   }
+
+   public void testDefensiveCopyOfChildren()
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      Fqn childFqn = Fqn.fromString("/child");
+      rootNode.addChild(childFqn).put("k", "v");
+      Set<Node<Object, Object>> children = rootNode.getChildren();
+      Set<Object> childrenNames = rootNode.getChildrenNames();
+
+      assert childrenNames.size() == 1;
+      assert childrenNames.contains(childFqn.getLastElement());
+
+      assert children.size() == 1;
+      assert children.iterator().next().getFqn().equals(childFqn);
+
+      // now change stuff.
+
+      rootNode.addChild(Fqn.fromString("/child2"));
+
+      // assert that the collections we initially got have not changed.
+      assert childrenNames.size() == 1;
+      assert childrenNames.contains(childFqn.getLastElement());
+
+      assert children.size() == 1;
+      assert children.iterator().next().getFqn().equals(childFqn);
+   }
+
+
+   public void testImmutabilityOfChildren()
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      rootNode.addChild(A);
+
+      try
+      {
+         rootNode.getChildren().clear();
+         fail("Collection of child nodes returned in getChildren() should be immutable");
+      }
+      catch (Exception e)
+      {
+         // expected
+      }
+   }
+
+   public void testGetChildAPI()
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      // creates a Node<Object, Object> with fqn /a/b/c
+      Node childA = rootNode.addChild(A);
+      childA.addChild(B).addChild(C);
+
+      rootNode.getChild(A).put("key", "value");
+      rootNode.getChild(A).getChild(B).put("key", "value");
+      rootNode.getChild(A).getChild(B).getChild(C).put("key", "value");
+
+      assertEquals("value", rootNode.getChild(A).get("key"));
+      assertEquals("value", rootNode.getChild(A).getChild(B).get("key"));
+      assertEquals("value", rootNode.getChild(A).getChild(B).getChild(C).get("key"));
+
+      assertNull(rootNode.getChild(Fqn.fromElements("nonexistent")));
+   }
+
+   public void testClearingData()
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      rootNode.put("k", "v");
+      rootNode.put("k2", "v2");
+      assertEquals(2, rootNode.getKeys().size());
+      rootNode.clearData();
+      assertEquals(0, rootNode.getKeys().size());
+      assertTrue(rootNode.getData().isEmpty());
+   }
+
+   public void testClearingDataTx() throws Exception
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      tm.begin();
+      rootNode.put("k", "v");
+      rootNode.put("k2", "v2");
+      assertEquals(2, rootNode.getKeys().size());
+      rootNode.clearData();
+      assertEquals(0, rootNode.getKeys().size());
+      assertTrue(rootNode.getData().isEmpty());
+      tm.commit();
+      assertTrue(rootNode.getData().isEmpty());
+   }
+
+   public void testPutData()
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      assertTrue(rootNode.getData().isEmpty());
+
+      Map<Object, Object> map = new HashMap<Object, Object>();
+      map.put("k1", "v1");
+      map.put("k2", "v2");
+
+      rootNode.putAll(map);
+
+      assertEquals(2, rootNode.getData().size());
+      assertEquals("v1", rootNode.get("k1"));
+      assertEquals("v2", rootNode.get("k2"));
+
+      map.clear();
+      map.put("k3", "v3");
+
+      rootNode.putAll(map);
+      assertEquals(3, rootNode.getData().size());
+      assertEquals("v1", rootNode.get("k1"));
+      assertEquals("v2", rootNode.get("k2"));
+      assertEquals("v3", rootNode.get("k3"));
+
+      map.clear();
+      map.put("k4", "v4");
+      map.put("k5", "v5");
+
+      rootNode.replaceAll(map);
+      assertEquals(2, rootNode.getData().size());
+      assertEquals("v4", rootNode.get("k4"));
+      assertEquals("v5", rootNode.get("k5"));
+   }
+
+   public void testGetChildrenNames() throws Exception
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+      Node<Object, Object> rootNode = cache.getRoot();
+
+      rootNode.addChild(A).put("k", "v");
+      rootNode.addChild(B).put("k", "v");
+
+      Set<Object> childrenNames = new HashSet<Object>();
+      childrenNames.add(A.getLastElement());
+      childrenNames.add(B.getLastElement());
+
+      assertEquals(childrenNames, rootNode.getChildrenNames());
+
+      // now delete a child, within a tx
+      tm.begin();
+      rootNode.removeChild(B);
+      assertFalse(rootNode.hasChild(B));
+      childrenNames.remove(B.getLastElement());
+      assertEquals(childrenNames, rootNode.getChildrenNames());
+      tm.commit();
+      assertEquals(childrenNames, rootNode.getChildrenNames());
+   }
+
+   public void testDoubleRemovalOfData() throws Exception
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+
+      cache.put("/foo/1/2/3", "item", 1);
+      assert 1 == (Integer) cache.get("/foo/1/2/3", "item");
+      tm.begin();
+      assert 1 == (Integer) cache.get("/foo/1/2/3", "item");
+      cache.removeNode("/foo/1");
+      assertNull(cache.getNode("/foo/1"));
+      assertNull(cache.get("/foo/1", "item"));
+      cache.removeNode("/foo/1/2/3");
+      System.out.println("Cache: " + cache);
+      assertNull(cache.get("/foo/1/2/3", "item"));
+      assertNull(cache.get("/foo/1", "item"));
+      tm.commit();
+      assertFalse(cache.exists("/foo/1"));
+      assertNull(cache.get("/foo/1/2/3", "item"));
+      assertNull(cache.get("/foo/1", "item"));
+   }
+
+   public void testDoubleRemovalOfData2() throws Exception
+   {
+      TreeCache<Object, Object> cache = cacheTL.get();
+
+      cache.put("/foo/1/2", "item", 1);
+      tm.begin();
+      assertEquals(cache.get("/foo/1", "item"), null);
+      cache.removeNode("/foo/1");
+      assertNull(cache.get("/foo/1", "item"));
+      cache.removeNode("/foo/1/2");
+      assertNull(cache.get("/foo/1", "item"));
+      tm.commit();
+      assertFalse(cache.exists("/foo/1"));
+      assertNull(cache.get("/foo/1/2", "item"));
+      assertNull(cache.get("/foo/1", "item"));
+   }
+}

Added: core/branches/flat/src/test/java/org/jboss/starobrno/tree/api/TreeCacheAPITest.java
===================================================================
--- core/branches/flat/src/test/java/org/jboss/starobrno/tree/api/TreeCacheAPITest.java	                        (rev 0)
+++ core/branches/flat/src/test/java/org/jboss/starobrno/tree/api/TreeCacheAPITest.java	2008-11-06 00:31:36 UTC (rev 7089)
@@ -0,0 +1,236 @@
+package org.jboss.starobrno.tree.api;
+
+import org.jboss.starobrno.CacheSPI;
+import org.jboss.starobrno.config.Configuration;
+import org.jboss.starobrno.config.parsing.XmlConfigurationParser;
+import org.jboss.starobrno.manager.CacheManager;
+import org.jboss.starobrno.tree.Fqn;
+import org.jboss.starobrno.tree.Node;
+import org.jboss.starobrno.tree.TreeCache;
+import org.jboss.starobrno.util.TestingUtil;
+import static org.testng.AssertJUnit.*;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import javax.transaction.TransactionManager;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests the {@link org.jboss.cache.Cache} public API at a high level
+ *
+ * @author <a href="mailto:manik AT jboss DOT org">Manik Surtani</a>
+ */
+
+ at Test(groups = {"functional", "pessimistic"}, sequential = true)
+public class TreeCacheAPITest
+{
+   private TreeCache<String, String> cache;
+   private TransactionManager tm;
+
+   @BeforeMethod(alwaysRun = true)
+   public void setUp() throws Exception
+   {
+      // start a single cache instance
+      XmlConfigurationParser parser = new XmlConfigurationParser();
+      Configuration c;
+      c = parser.parseFile("configs/local-tx.xml");
+      c.setEvictionConfig(null);
+      CacheManager cm = new CacheManager(c);
+
+      cache = cm.createTreeCache(getClass().getSimpleName());
+      tm = ((CacheSPI) cache.getCache()).getTransactionManager();
+   }
+
+   @AfterMethod(alwaysRun = true)
+   public void tearDown()
+   {
+      if (cache != null) TestingUtil.killCaches(cache.getCache());
+      cache = null;
+   }
+
+   /**
+    * All cache operations should happen on a {@link Node} - I.e., you look up a {@link Node} and perform data operations
+    * on this {@link Node}.  For convenience and familiarity with JBoss Cache 1.x, we provide some helpers in {@link Cache}
+    * which dives you direct data access to nodes.
+    * <p/>
+    * This test exercises these.
+    */
+   public void testConvenienceMethods()
+   {
+      Fqn fqn = Fqn.fromString("/test/fqn");
+      String key = "key", value = "value";
+      Map<String, String> data = new HashMap<String, String>();
+      data.put(key, value);
+
+      assertNull(cache.get(fqn, key));
+
+      cache.put(fqn, key, value);
+
+      assertEquals(value, cache.get(fqn, key));
+
+      cache.remove(fqn, key);
+
+      assertNull(cache.get(fqn, key));
+
+      cache.put(fqn, data);
+
+      assertEquals(value, cache.get(fqn, key));
+   }
+
+
+   /**
+    * Another convenience method that tests node removal
+    */
+   public void testNodeConvenienceNodeRemoval()
+   {
+      // this fqn is relative, but since it is from the root it may as well be absolute
+      Fqn fqn = Fqn.fromString("/test/fqn");
+      cache.getRoot().addChild(fqn);
+      assertTrue(cache.getRoot().hasChild(fqn));
+
+      assertEquals(true, cache.removeNode(fqn));
+      assertFalse(cache.getRoot().hasChild(fqn));
+      // remove should REALLY remove though and not just mark as deleted/invalid.
+      Node n = cache.getNode(fqn);
+      assert n == null;
+
+      assertEquals(false, cache.removeNode(fqn));
+
+      // remove should REALLY remove though and not just mark as deleted/invalid.
+      n = cache.getNode(fqn);
+      assert n == null;
+
+      // Check that it's removed if it has a child
+      Fqn child = Fqn.fromString("/test/fqn/child");
+      cache.getRoot().addChild(child);
+      assertTrue(cache.getRoot().hasChild(child));
+
+      assertEquals(true, cache.removeNode(fqn));
+      assertFalse(cache.getRoot().hasChild(fqn));
+      assertEquals(false, cache.removeNode(fqn));
+   }
+
+   /**
+    * Tests basic eviction
+    */
+   public void testEvict()
+   {
+      Fqn one = Fqn.fromString("/one");
+      Fqn two = Fqn.fromString("/one/two");
+      String key = "key", value = "value";
+
+      cache.getRoot().addChild(one).put(key, value);
+      cache.getRoot().addChild(two).put(key, value);
+
+      assertTrue(cache.getRoot().hasChild(one));
+      assertFalse(cache.getRoot().getChild(one).getData().isEmpty());
+      assertTrue(cache.getRoot().hasChild(two));
+      assertFalse(cache.getRoot().getChild(two).getData().isEmpty());
+
+      // evict two
+      cache.evict(two, false);
+
+      assertTrue(cache.getRoot().hasChild(one));
+      assertTrue(cache.getRoot().getChild(one).getKeys().contains(key));
+      assertFalse(cache.getRoot().hasChild(two));
+
+      // now add 2 again...
+      cache.getRoot().addChild(two).put(key, value);
+
+      // now evict one, NOT recursive
+      cache.evict(one, false);
+
+      // one will NOT be removed, just emptied.
+      assertTrue(cache.getRoot().hasChild(one));
+      assertFalse(cache.getRoot().getChild(one).getKeys().contains(key));
+
+      // two will be unaffected
+      assertTrue(cache.getRoot().hasChild(two));
+      assertTrue(cache.getRoot().getChild(two).getKeys().contains(key));
+   }
+
+
+   /**
+    * Tests recursive eviction
+    */
+   public void testEvictRecursive()
+   {
+      Fqn one = Fqn.fromString("/one");
+      Fqn two = Fqn.fromString("/one/two");
+      String key = "key", value = "value";
+
+      cache.getRoot().addChild(one).put(key, value);
+      cache.getRoot().addChild(two).put(key, value);
+
+      assertTrue(cache.getRoot().hasChild(one));
+      assertFalse(cache.getRoot().getChild(one).getData().isEmpty());
+      assertTrue(cache.getRoot().hasChild(two));
+      assertFalse(cache.getRoot().getChild(two).getData().isEmpty());
+
+      // evict two
+      cache.evict(two, true);
+
+      assertTrue(cache.getRoot().hasChild(one));
+      assertFalse(cache.getRoot().getChild(one).getData().isEmpty());
+      assertFalse(cache.getRoot().hasChild(two));
+
+      // now add 2 again...
+      cache.getRoot().addChild(two).put(key, value);
+
+      // now evict one, recursive
+      cache.evict(one, true);
+
+      assertFalse(cache.getRoot().hasChild(one));
+      assertFalse(cache.getRoot().hasChild(two));
+   }
+
+   public void testStopClearsData() throws Exception
+   {
+      Fqn a = Fqn.fromString("/a");
+      Fqn b = Fqn.fromString("/a/b");
+      String key = "key", value = "value";
+      cache.getRoot().addChild(a).put(key, value);
+      cache.getRoot().addChild(b).put(key, value);
+      cache.getRoot().put(key, value);
+
+      assertEquals(value, cache.getRoot().get(key));
+      assertEquals(value, cache.getRoot().getChild(a).get(key));
+      assertEquals(value, cache.getRoot().getChild(b).get(key));
+
+      cache.stop();
+
+      cache.start();
+
+      assertNull(cache.getRoot().get(key));
+      assertTrue(cache.getRoot().getData().isEmpty());
+      assertTrue(cache.getRoot().getChildren().isEmpty());
+   }
+
+   public void testPhantomStructuralNodesOnRemove()
+   {
+      assert cache.getNode(Fqn.fromString("/a/b/c")) == null;
+      assert !cache.removeNode("/a/b/c");
+      assert cache.getNode(Fqn.fromString("/a/b/c")) == null;
+      assert cache.getNode(Fqn.fromString("/a/b")) == null;
+      assert cache.getNode(Fqn.fromString("/a")) == null;
+   }
+
+   public void testPhantomStructuralNodesOnRemoveTransactional() throws Exception
+   {
+      assert cache.getNode(Fqn.fromString("/a/b/c")) == null;
+      tm.begin();
+      assert !cache.removeNode("/a/b/c");
+      tm.commit();
+      assert cache.getNode(Fqn.fromString("/a/b/c")) == null;
+      assert cache.getNode(Fqn.fromString("/a/b")) == null;
+      assert cache.getNode(Fqn.fromString("/a")) == null;
+   }
+
+   public void testRpcManagerElements()
+   {
+      assertEquals("CacheMode.LOCAL cache has no address", null, cache.getLocalAddress());
+      assertEquals("CacheMode.LOCAL cache has no members list", null, cache.getMembers());
+   }
+}

Modified: core/branches/flat/src/test/resources/configs/local-tx.xml
===================================================================
--- core/branches/flat/src/test/resources/configs/local-tx.xml	2008-11-05 20:34:07 UTC (rev 7088)
+++ core/branches/flat/src/test/resources/configs/local-tx.xml	2008-11-06 00:31:36 UTC (rev 7089)
@@ -7,18 +7,4 @@
    <serialization useRegionBasedMarshalling="false"/>
    <stateRetrieval timeout="20000"/>
    <transport clusterName="JBossCache-Cluster"/>
-   <eviction wakeUpInterval="5000">
-      <default algorithmClass="org.jboss.starobrno.eviction.algorithms.lru.LRUAlgorithm" eventQueueSize="200000">
-         <attribute name="maxNodes">5000</attribute>
-         <attribute name="timeToLive">1000000</attribute>
-      </default>
-      <region name="/org/jboss/data">
-         <attribute name="maxNodes">5000</attribute>
-         <attribute name="timeToLive">1000000</attribute>
-      </region>
-      <region name="/org/jboss/test/data">
-         <attribute name="maxNodes">5</attribute>
-         <attribute name="timeToLive">4000</attribute>
-      </region>
-   </eviction>
 </jbosscache>




More information about the jbosscache-commits mailing list