[jbosscache-commits] JBoss Cache SVN: r6471 - in core/trunk/src: test/java/org/jboss/cache/util and 1 other directory.

jbosscache-commits at lists.jboss.org jbosscache-commits at lists.jboss.org
Thu Jul 31 11:55:10 EDT 2008


Author: manik.surtani at 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}.
+ */
+ at 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();
+      }
+
+   }
+}




More information about the jbosscache-commits mailing list