Author: manik.surtani(a)jboss.com
Date: 2008-07-31 11:55:10 -0400 (Thu, 31 Jul 2008)
New Revision: 6471
Added:
core/trunk/src/main/java/org/jboss/cache/util/Caches.java
core/trunk/src/test/java/org/jboss/cache/util/CachesTest.java
Log:
Preliminary support for JBCACHE-941
Added: core/trunk/src/main/java/org/jboss/cache/util/Caches.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/util/Caches.java (rev
0)
+++ core/trunk/src/main/java/org/jboss/cache/util/Caches.java 2008-07-31 15:55:10 UTC (rev
6471)
@@ -0,0 +1,816 @@
+package org.jboss.cache.util;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.Node;
+import org.jboss.cache.loader.CacheLoader;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Adaptors for {@link Cache} classes, such as {@link Node}.
+ * This is useful for integration of JBoss Cache into existing applications.
+ * <p/>
+ * Example use:
+ * <pre>
+ * Cache c = ...;
+ * Map m = Caches.asMap(c);
+ * m.put("a", "b"); // null
+ * m.containsKey("a"); // true
+ * m.remove("a"); // "b"
+ * </pre>
+ */
+public class Caches
+{
+
+ private Caches()
+ {
+ }
+
+ /**
+ * Returns a {@link Map} from the root node.
+ *
+ * @see #asMap(Node)
+ */
+ public static Map asMap(Cache cache)
+ {
+ if (cache == null)
+ throw new NullPointerException("cache");
+ return asMap(cache.getRoot());
+ }
+
+ /**
+ * Returns a {@link Map}, where map keys are named children of the given Node,
+ * and values are kept under a single key for this node.
+ * The map may be safely concurrently modified through this Map or externally,
+ * and its contents reflect the cache state and
+ * existing data of the Node.
+ * This means that {@link ConcurrentModificationException} is never thrown
+ * and all methods are thread safe.
+ * <p/>
+ * The map is not serializable.
+ * <p/>
+ * Usage note: As a single node is used for every key, it is most efficient to store
+ * data for a single entity (e.g. Person) in a single object.
+ * <p/>
+ * Also, when using a {@link CacheLoader} for storage, keys used must be valid as
+ * part of the {@link Fqn} used in calls. Generally speaking, simple string values
are
+ * preferred.
+ */
+ public static Map asMap(Node node)
+ {
+ return new MapNode(node);
+ }
+
+ /**
+ * Returns a {@link Map}, where map data is put and returned directly from a single
Node.
+ * This method is "simple" as data is kept under a single node.
+ * Note that storing all data in a single Node can be inefficient when using
persistence,
+ * replication, or transactions.
+ * The map may be safely concurrently modified through this Map or externally.
+ * This means that {@link ConcurrentModificationException} is never thrown
+ * and all methods are thread safe.
+ * <p/>
+ * The methods {@link Map#entrySet} and {@link Map#values} and {@link Map#keySet}
+ * do not allow for modification of the Node.
+ * Further all these methods return a collection which is a snapshot (copy)
+ * of the data at time of calling. This may be very inefficient.
+ * <p/>
+ * The map is not serializable.
+ */
+ public static Map asSimpleMap(Node node)
+ {
+ return new SimpleMapNode(node);
+ }
+
+ /**
+ * Returns a {@link Set}, where set entries are data entries of the given Node.
+ * This method is "simple" as data is kept under a single node.
+ * <p/>
+ * Note that storing all data in a single Node can be inefficient when using
persistence,
+ * replication, or transactions.
+ * The set may be safely concurrently modified through this Map or externally.
+ * This means that {@link ConcurrentModificationException} is never thrown
+ * and all methods are thread safe.
+ * <p/>
+ * The set is not serializable.
+ */
+ public static Set asSimpleSet(Node node)
+ {
+ return new SimpleSetNode(node);
+ }
+
+ /**
+ * Returns a {@link Map}, where map entries are partitioned into
+ * children nodes, within a cache node.
+ * The default child selector divides the data into 16 child nodes based on hash
code.
+ * Note that for large data sets, the number of child nodes should be increased.
+ *
+ * @param node node to cache under
+ */
+ public static Map asPartitionedMap(Node node)
+ {
+ return new PartitionedMapNode(node, HashKeySelector.DEFAULT);
+ }
+
+ /**
+ * Returns a {@link Map}, where map entries are partitioned
+ * into children, within a cache node, by key hash code.
+ * <p/>
+ * The map is not serializable.
+ * <p/>
+ * Usage note: This is a performance (and size) compromise between {@link
#asMap(Node)}
+ * and {@link #asSimpleMap(Node)}. For applications using a {@link
org.jboss.cache.loader.CacheLoader},
+ * {@link #asMap(Node)} is a better choice.
+ * <p/>
+ *
+ * @param node node to cache under
+ * @param ss selector strategy that chooses a segment based on key
+ */
+ public static Map asPartitionedMap(Node node, ChildSelector ss)
+ {
+ return new PartitionedMapNode(node, ss);
+ }
+
+ /**
+ * Returns a {@link Map}, where map entries are partitioned into child nodes,
+ * within the cache root, by key hash code.
+ *
+ * @param cache cache to use
+ * @return
+ */
+ public static Map asPartitionedMap(Cache cache)
+ {
+ return asPartitionedMap(cache.getRoot());
+ }
+
+ /**
+ * Returns a segment ({@link Node#getChild(Object) child node name})
+ * to use based on the characteristics of a key.
+ * <p/>
+ * Here is an example class which selects a child based on a person's department:
+ * <pre>
+ * public static class DepartmentSelector implements ChildSelector<Person>
+ * {
+ * <p/>
+ * public Object childName(Person key)
+ * {
+ * return key.getDepartment();
+ * }
+ * <p/>
+ * }
+ * </pre>
+ *
+ * @
+ */
+ public interface ChildSelector<T>
+ {
+ /**
+ * Returns a child node name for a key.
+ *
+ * @param key for calls to {@link Map#put}, {@link Map#get} etc.
+ * @return node name
+ */
+ Object childName(T key);
+ }
+
+ /**
+ * Class that returns a child name to use based on the hash code of a key.
+ */
+ public static class HashKeySelector implements ChildSelector
+ {
+
+ static ChildSelector DEFAULT = new HashKeySelector(16);
+
+ protected int segments;
+
+ /**
+ * Constructs with N segments, where N must be a power of 2.
+ */
+ public HashKeySelector(int segments)
+ {
+ this.segments = segments;
+ if (Integer.bitCount(segments) != 1)
+ throw new IllegalArgumentException();
+ if (segments <= 0)
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Computes an improved hash code from an object's hash code.
+ */
+ protected int hashCode(int i)
+ {
+ i ^= i >>> 20 ^ i >>> 12;
+ return i ^ i >>> 7 ^ i >>> 4;
+ }
+
+ /**
+ * Returns the segment for this key, in the inclusive range 0 to {@link #segments}
- 1.
+ */
+ protected int segmentFor(Object key)
+ {
+ if (key == null)
+ return 0;
+ int hc = key.hashCode();
+ return hashCode(hc) & (segments - 1);
+ }
+
+ /**
+ * Returns the node name for this segment.
+ */
+ protected Object childName(int segment)
+ {
+ return "" + segment;//IntegerCache.toString(segment);
+ }
+
+ /**
+ * Returns the node name for this key.
+ * By default, returns a String containing the segment.
+ */
+ public Object childName(Object key)
+ {
+ return childName(segmentFor(key));
+ }
+
+ public String toString()
+ {
+ return super.toString() + " segments=" + segments;
+ }
+
+ }
+
+ static class MapNode extends AbstractMap
+ {
+
+ public static final String KEY = "K";
+
+ private Node node;
+
+ public MapNode(Node node)
+ {
+ if (node == null)
+ throw new NullPointerException("node");
+ this.node = node;
+ }
+
+ @Override
+ public Set entrySet()
+ {
+ return new AbstractSet()
+ {
+
+ @Override
+ public Iterator iterator()
+ {
+ final Iterator<Node> i = set().iterator();
+ return new Iterator()
+ {
+
+ Object name;
+
+ boolean next = false;
+
+ public boolean hasNext()
+ {
+ return i.hasNext();
+ }
+
+ public Object next()
+ {
+ Node n = i.next();
+ this.name = n.getFqn().getLastElement();
+ this.next = true;
+ Object key = n.get(KEY);
+ return new SimpleEntry(name, key);
+ }
+
+ public void remove()
+ {
+ if (!next)
+ throw new IllegalStateException();
+ node.removeChild(name);
+ }
+
+ public String toString()
+ {
+ return "Itr name=" + name;
+ }
+
+ };
+ }
+
+ private Set<Node> set()
+ {
+ return node.getChildren();
+ }
+
+ @Override
+ public int size()
+ {
+ return set().size();
+ }
+
+ };
+ }
+
+ @Override
+ public void clear()
+ {
+ for (Object o : node.getChildrenNames())
+ node.removeChild(o);
+ }
+
+ @Override
+ public boolean containsKey(Object arg0)
+ {
+ return node.getChild(arg0) != null;
+ }
+
+ @Override
+ public Object get(Object arg0)
+ {
+ Node child = node.getChild(arg0);
+ if (child == null)
+ return null;
+ return child.get(KEY);
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return node.getChildrenNames().isEmpty();
+ }
+
+ @Override
+ public Set keySet()
+ {
+
+ return new AbstractSet()
+ {
+
+ private Set set()
+ {
+ return node.getChildrenNames();
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ final Iterator i = set().iterator();
+ return new Iterator()
+ {
+
+ Object child;
+
+ public boolean hasNext()
+ {
+ return i.hasNext();
+ }
+
+ public Object next()
+ {
+ child = i.next();
+ return child;
+ }
+
+ public void remove()
+ {
+ if (child == null)
+ throw new IllegalStateException();
+ node.removeChild(child);
+ // since set is read-only, invalidate
+ }
+
+ };
+ }
+
+ @Override
+ public boolean remove(Object key)
+ {
+ return node.removeChild(key);
+ }
+
+ @Override
+ public int size()
+ {
+ return set().size();
+ }
+
+ };
+
+ }
+
+ @Override
+ public Object put(Object arg0, Object arg1)
+ {
+ return node.addChild(Fqn.fromElements(arg0)).put(KEY, arg1);
+ }
+
+ @Override
+ public Object remove(Object arg0)
+ {
+ Node child = node.getChild(arg0);
+ if (child == null)
+ return null;
+ Object o = child.remove(KEY);
+ node.removeChild(arg0);
+ return o;
+ }
+
+ @Override
+ public int size()
+ {
+ return node.getChildrenNames().size();
+ }
+
+ }
+
+ static class SimpleMapNode extends AbstractMap implements java.util.Map
+ {
+
+ private Node node;
+
+ public SimpleMapNode(Node node)
+ {
+ if (node == null)
+ throw new NullPointerException("node");
+ this.node = node;
+ }
+
+ @Override
+ public void clear()
+ {
+ node.clearData();
+ }
+
+ @Override
+ public boolean containsKey(Object key)
+ {
+ return node.getKeys().contains(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value)
+ {
+ return node.getData().containsValue(value);
+ }
+
+ /**
+ * getData returns a snapshot of the data.
+ */
+ public Set entrySet()
+ {
+ return node.getData().entrySet();
+ }
+
+ @Override
+ public Object get(Object key)
+ {
+ return node.get(key);
+ }
+
+ @Override
+ public Set keySet()
+ {
+ return node.getKeys();
+ }
+
+ @Override
+ public Object put(Object key, Object value)
+ {
+ return node.put(key, value);
+ }
+
+ @Override
+ public void putAll(Map map)
+ {
+ node.putAll(map);
+ }
+
+ @Override
+ public Object remove(Object key)
+ {
+ return node.remove(key);
+ }
+
+ @Override
+ public int size()
+ {
+ return node.dataSize();
+ }
+
+ }
+
+ static class SimpleSetNode extends AbstractSet implements java.util.Set
+ {
+
+ private Node node;
+
+ private static final String VALUE = "V";
+
+ public SimpleSetNode(Node node)
+ {
+ if (node == null)
+ throw new NullPointerException("node");
+ this.node = node;
+ }
+
+ @Override
+ public void clear()
+ {
+ node.clearData();
+ }
+
+ @Override
+ public boolean contains(Object key)
+ {
+ return node.getKeys().contains(key);
+ }
+
+ @Override
+ public boolean remove(Object key)
+ {
+ return node.remove(key) != null;
+ }
+
+ @Override
+ public int size()
+ {
+ return node.dataSize();
+ }
+
+ @Override
+ public boolean add(Object arg0)
+ {
+ return node.put(arg0, VALUE) == null;
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ final Iterator i = node.getKeys().iterator();
+ return new Iterator()
+ {
+ Object key;
+
+ boolean next = false;
+
+ public boolean hasNext()
+ {
+ return i.hasNext();
+ }
+
+ public Object next()
+ {
+ key = i.next();
+ next = true;
+ return key;
+ }
+
+ public void remove()
+ {
+ if (!next)
+ throw new IllegalStateException();
+ node.remove(key);
+ }
+
+ };
+ }
+
+ }
+
+ static class PartitionedMapNode extends AbstractMap
+ {
+
+ private Node node;
+
+ private ChildSelector selector;
+
+ public PartitionedMapNode(Node node, ChildSelector selector)
+ {
+ this.node = node;
+ this.selector = selector;
+ }
+
+ @Override
+ public Set entrySet()
+ {
+ return new AbstractSet<Map.Entry>()
+ {
+
+ Iterator<Node> ci = node.getChildren().iterator();
+
+ @Override
+ public Iterator<Entry> iterator()
+ {
+ return new Iterator<Entry>()
+ {
+
+ Iterator ni;
+
+ {
+ nextChild();
+ findNext();
+ }
+
+ private void nextChild()
+ {
+ ni = new SimpleMapNode(ci.next()).entrySet().iterator();
+ }
+
+ private void findNext()
+ {
+ while (!ni.hasNext())
+ {
+ if (!ci.hasNext())
+ return;
+ nextChild();
+ }
+ }
+
+ public boolean hasNext()
+ {
+ return ni.hasNext();
+ }
+
+ public Entry next()
+ {
+ Entry n = (Entry) ni.next();
+ findNext();
+ return n;
+ }
+
+ public void remove()
+ {
+ ni.remove();
+ }
+
+ };
+ }
+
+ @Override
+ public int size()
+ {
+ return PartitionedMapNode.this.size();
+ }
+
+ };
+ }
+
+ @Override
+ public Set keySet()
+ {
+ return new AbstractSet<Map.Entry>()
+ {
+
+ @Override
+ public Iterator<Entry> iterator()
+ {
+ return PartitionedMapNode.super.keySet().iterator();
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ boolean key = PartitionedMapNode.this.containsKey(o);
+ PartitionedMapNode.this.remove(o);
+ return key;
+ }
+
+ @Override
+ public boolean contains(Object o)
+ {
+ return PartitionedMapNode.this.containsKey(o);
+ }
+
+ @Override
+ public int size()
+ {
+ return PartitionedMapNode.super.keySet().size();
+ }
+
+ };
+ }
+
+ @Override
+ public void clear()
+ {
+ for (Object o : node.getChildrenNames())
+ node.getChild(o).clearData();
+ }
+
+ private Node nodeFor(Object o)
+ {
+ return node.getChild(selector.childName(o));
+ }
+
+ @Override
+ public boolean containsKey(Object o)
+ {
+ Node n = nodeFor(o);
+ if (n == null) return false;
+ return n.getKeys().contains(o);
+ }
+
+ @Override
+ public Object get(Object o)
+ {
+ Node n = nodeFor(o);
+ if (n == null) return null;
+ return n.get(o);
+ }
+
+ public Object put(Object key, Object value)
+ {
+ Object name = selector.childName(key);
+ Node n = node.getChild(name);
+ if (n == null)
+ n = node.addChild(Fqn.fromElements(name));
+ return n.put(key, value);
+ }
+
+ public Object remove(Object o)
+ {
+ Node n = nodeFor(o);
+ return n.remove(o);
+ }
+
+ public int size()
+ {
+ int size = 0;
+ for (Object o : node.getChildrenNames())
+ {
+ Node child = node.getChild(o);
+ size += child.dataSize();
+ }
+ return size;
+ }
+
+ }
+
+ public static class SimpleEntry<K, V> implements Entry<K, V>
+ {
+ K key;
+ V value;
+
+ public SimpleEntry(K key, V value)
+ {
+ this.key = key;
+ this.value = value;
+ }
+
+ public SimpleEntry(Entry<K, V> e)
+ {
+ this.key = e.getKey();
+ this.value = e.getValue();
+ }
+
+ public K getKey()
+ {
+ return key;
+ }
+
+ public V getValue()
+ {
+ return value;
+ }
+
+ public V setValue(V value)
+ {
+ V oldValue = this.value;
+ this.value = value;
+ return oldValue;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (!(o instanceof Map.Entry))
+ return false;
+ Map.Entry e = (Map.Entry) o;
+ return eq(key, e.getKey()) && eq(value, e.getValue());
+ }
+
+ public int hashCode()
+ {
+ return ((key == null) ? 0 : key.hashCode()) ^
+ ((value == null) ? 0 : value.hashCode());
+ }
+
+ public String toString()
+ {
+ return key + "=" + value;
+ }
+
+ private static boolean eq(Object o1, Object o2)
+ {
+ return (o1 == null ? o2 == null : o1.equals(o2));
+ }
+ }
+}
Added: core/trunk/src/test/java/org/jboss/cache/util/CachesTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/util/CachesTest.java
(rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/util/CachesTest.java 2008-07-31 15:55:10 UTC
(rev 6471)
@@ -0,0 +1,212 @@
+package org.jboss.cache.util;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.DefaultCacheFactory;
+import org.jboss.cache.Node;
+import org.jboss.cache.util.Caches.ChildSelector;
+import org.jboss.cache.util.Caches.SimpleEntry;
+import static org.testng.AssertJUnit.*;
+import org.testng.annotations.Test;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests {@link Caches}.
+ */
+@Test(groups = "functional")
+public class CachesTest
+{
+ String a = "a";
+
+ String b = "b";
+
+ String c = "c";
+
+ Cache<Object, Object> cache = new DefaultCacheFactory<Object,
Object>().createCache();
+
+ public void testSegment()
+ {
+ Map<Object, Object> m = Caches.asPartitionedMap(cache);
+ // m.put(a, b);
+ testMap(m);
+ m.clear();
+ int c = 100;
+ for (int i = 0; i < c; i++)
+ {
+ m.put(Integer.toHexString(i), "foo " + i);
+ }
+ for (int i = 0; i < c; i++)
+ {
+ assertEquals("foo " + i, m.get(Integer.toHexString(i)));
+ }
+ System.out.println(CachePrinter.printCacheDetails(cache));
+ }
+
+ public void testAsMap()
+ {
+ Map<Object, Object> m = Caches.asMap(cache);
+ testMap(m);
+ for (Node n : cache.getRoot().getChildren())
+ {
+ assertEquals("/a", n.getFqn().toString());
+ assertEquals(c, n.get("K"));
+ }
+ m.clear();
+
+ m.put(a, a);
+ testCollectionRemove(m.keySet());
+ m.put(a, a);
+ testCollectionRemove(m.values());
+ m.put(a, a);
+ testCollectionRemove(m.entrySet());
+ }
+
+ private void testCollectionRemove(Collection c)
+ {
+ Iterator i;
+ i = c.iterator();
+ assertEquals(true, i.hasNext());
+ try
+ {
+ i.remove();
+ fail("no next");
+ }
+ catch (IllegalStateException e)
+ {
+ }
+ i.next();
+ i.remove();
+ assertEquals(false, i.hasNext());
+ assertEquals("C " + c, 0, c.size());
+ }
+
+ public void testAsSimpleSet()
+ {
+ Set s = Caches.asSimpleSet(cache.getRoot());
+ testSet(s);
+ }
+
+ private void testSet(Set s)
+ {
+ assertEquals(0, s.size());
+ assertEquals(true, s.add(a));
+ assertEquals(false, s.add(a));
+ assertEquals(1, s.size());
+ assertEquals("[a]", s.toString());
+ assertEquals(true, s.contains(a));
+ assertEquals(true, s.remove(a));
+ assertEquals(false, s.remove(a));
+ assertEquals(false, s.contains(a));
+ s.add(b);
+ s.clear();
+ assertEquals(false, s.contains(b));
+ s.add(c);
+ Iterator i = s.iterator();
+ s.add(a);
+ s.remove(a); // stable iterator
+ assertTrue(i.hasNext());
+ assertEquals(c, i.next());
+ i.remove();
+ assertEquals(false, i.hasNext());
+ assertEquals(0, s.size());
+ assertEquals(true, s.isEmpty());
+ }
+
+ private void testMap(Map m)
+ {
+ assertEquals(null, m.put(a, b));
+ assertEquals(b, m.put(a, c));
+ assertEquals("{a=c}", m.toString());
+ assertEquals(1, m.size());
+ assertEquals("a", m.keySet().iterator().next());
+ assertEquals(true, m.containsKey(a));
+ assertEquals(true, m.containsValue(c));
+ assertEquals(false, m.containsValue(b));
+ assertEquals(c, m.remove(a));
+ assertEquals(null, m.remove(a));
+ assertEquals(0, m.size());
+ assertEquals(false, m.keySet().iterator().hasNext());
+ m.put(c, a);
+ assertEquals(1, m.keySet().size());
+ assertEquals(1, m.entrySet().size());
+ assertEquals(1, m.values().size());
+ Iterator i = m.keySet().iterator();
+ m.put(b, b);
+ m.remove(b); // stable iterator
+ assertEquals(true, i.hasNext());
+ assertEquals(c, i.next());
+ assertEquals(false, i.hasNext());
+
+ assertEquals(true, m.keySet().contains(c));
+ assertEquals(true, m.entrySet().contains(new SimpleEntry(c, a)));
+ assertEquals(true, m.values().contains(a));
+ assertEquals(false, m.keySet().contains(a));
+ assertEquals(false, m.entrySet().contains(new SimpleEntry(a, c)));
+ assertEquals(false, m.values().contains(c));
+ assertEquals(false, m.isEmpty());
+ m.clear();
+ assertEquals(0, m.size());
+ m.put(a, a);
+ m.clear();
+ assertEquals(0, m.size());
+ assertEquals(true, m.isEmpty());
+ }
+
+ public void testAsSimpleMap()
+ {
+ Map m = Caches.asSimpleMap(cache.getRoot());
+ testMap(m);
+ }
+
+ public void testSelector()
+ {
+ Map m = Caches.asPartitionedMap(cache.getRoot(), new DepartmentSelector());
+ Person f = new Person("Fred", a);
+ Person g = new Person("George", b);
+ Person h = new Person("Harry", b);
+ // associate person with a number
+ m.put(f, 42);
+ m.put(g, 69);
+ m.put(h, 21);
+ assertEquals(42, m.get(f));
+ assertEquals(69, m.get(g));
+ System.out.println(CachePrinter.printCacheDetails(cache));
+ }
+
+ public static class Person
+ {
+ String name;
+
+ String department;
+
+ public Person(String name, String department)
+ {
+ super();
+ this.name = name;
+ this.department = department;
+ }
+
+ public String getDepartment()
+ {
+ return department;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+ }
+
+ public static class DepartmentSelector implements ChildSelector<Person>
+ {
+
+ public Object childName(Person key)
+ {
+ return key.getDepartment();
+ }
+
+ }
+}