Author: manik.surtani(a)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(a)jboss.com
*/
-@DefaultFactoryFor (classes = {EvictionManager.class})
+@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$
+ */
+@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@jboss.org">manik@jboss.org</a>)
+ * @author Steve Woodcock (<a
href="mailto:stevew@jofti.com">stevew@jofti.com</a>)
+ */
+@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
+ */
+@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...
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
+ */
+@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>
+ */
+
+@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>