/*
* 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.cache.util;
import org.jboss.cache.Cache;
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
import org.jboss.cache.NodeSPI;
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.Set;
/**
* Adaptors for {@link Cache} classes, such as {@link Node}.
* This is useful for integration of JBoss Cache into existing applications.
*
* Example use:
*
* Cache c = ...;
* Map m = Caches.asMap(c);
* m.put("a", "b"); // null
* m.containsKey("a"); // true
* m.remove("a"); // "b"
*
*/
public class Caches
{
private Caches()
{
}
/**
* Returns a {@link Map} from the root node.
*
* @param cache cache to wrap as a map
* @return a map representation of the cache
* @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.
*
* The map is not serializable.
*
* 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.
*
* 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.
*
* @param node node in a cache to wrap
* @return a Map representation of the cache
*/
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.
*
* 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.
*
* The map is not serializable.
*
* @param node node to wrap
* @return Map representation of the cache
*/
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.
*
* 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.
*
* The set is not serializable.
*
* @param node node to wrap
* @return a Set representation of the values in a node
*/
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 128 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
* @return a Map representation of the cache
*/
@SuppressWarnings("unchecked")
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.
*
* The map is not serializable.
*
* 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.
*
*
* @param node node to cache under
* @param ss selector strategy that chooses a segment based on key
* @return a Map representation of the cache
*/
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 a Map representation of the cache
*/
public static Map asPartitionedMap(Cache cache)
{
return asPartitionedMap(cache.getRoot());
}
/**
* Computes an improved hash code from an object's hash code.
*/
static protected final int hashCode(int i)
{
i ^= i >>> 20 ^ i >>> 12;
return i ^ i >>> 7 ^ i >>> 4;
}
/**
* Returns a segment ({@link Node#getChild(Object) child node name})
* to use based on the characteristics of a key.
*
* Here is an example class which selects a child based on a person's department:
*
* public static class DepartmentSelector implements ChildSelector<Person>
* {
* public Object childName(Person key)
* {
* return key.getDepartment();
* }
* }
*
*/
public interface ChildSelector
{
/**
* Returns a child node name for a key.
*
* @param key for calls to {@link Map#put}, {@link Map#get} etc.
* @return node name
*/
Fqn 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(128);
protected int segments;
/**
* Constructs with N segments, where N must be a power of 2.
*
* @param segments Number of hash segments
*/
public HashKeySelector(int segments)
{
this.segments = segments;
if (Integer.bitCount(segments) != 1)
throw new IllegalArgumentException();
if (segments <= 0)
throw new IllegalArgumentException();
}
/**
* Returns the segment for this key, in the inclusive range 0 to {@link #segments} - 1.
*/
protected final int segmentFor(T key)
{
if (key == null)
return 0;
int hc = key.hashCode();
return Caches.hashCode(hc) & (segments - 1);
}
/**
* Returns the node name for this segment.
*/
protected final Fqn childName(int segment)
{
return Fqn.fromElements(Integer.toString(segment));
}
/**
* Returns the node name for this key.
* By default, returns a String containing the segment.
*/
public final Fqn childName(T key)
{
return childName(segmentFor(key));
}
@Override
public String toString()
{
return super.toString() + " segments=" + segments;
}
}
static class MapNode extends AbstractMap
{
public static final String KEY = "K";
private Node node; // purposefully un-genericized
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> i = set().iterator();
return new Iterator>()
{
Object name;
boolean next = false;
public boolean hasNext()
{
return i.hasNext();
}
@SuppressWarnings("unchecked")
public Entry next()
{
Node n = i.next();
this.name = n.getFqn().getLastElement();
this.next = true;
Object key = n.get(KEY);
return new SimpleImmutableEntry(name, key);
}
public void remove()
{
if (!next)
throw new IllegalStateException();
node.removeChild(name);
}
@Override
public String toString()
{
return "Itr name=" + name;
}
};
}
@SuppressWarnings("unchecked")
private Set> 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
@SuppressWarnings("unchecked")
public V get(Object arg0)
{
Node child = node.getChild(arg0);
if (child == null)
return null;
return (V) 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()
{
K child;
public boolean hasNext()
{
return i.hasNext();
}
@SuppressWarnings("unchecked")
public K next()
{
child = (K) 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
@SuppressWarnings("unchecked")
public V put(K arg0, V arg1)
{
return (V) node.addChild(Fqn.fromElements(arg0)).put(KEY, arg1);
}
@Override
@SuppressWarnings("unchecked")
public V remove(Object arg0)
{
Node child = node.getChild(arg0);
if (child == null)
return null;
V o = (V) child.remove(KEY);
node.removeChild(arg0);
return o;
}
@Override
public int size()
{
return node.getChildrenNames().size();
}
}
static class SimpleMapNode extends AbstractMap
{
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.
*/
@Override
public Set> entrySet()
{
return node.getData().entrySet();
}
@Override
@SuppressWarnings("unchecked")
public V get(Object key)
{
return node.get((K) key);
}
@Override
public Set keySet()
{
return node.getKeys();
}
@Override
public V put(K key, V value)
{
return node.put(key, value);
}
@Override
@SuppressWarnings("unchecked")
public void putAll(Map extends K, ? extends V> map)
{
node.putAll((Map) map);
}
@Override
@SuppressWarnings("unchecked")
public V remove(Object key)
{
return node.remove((K) 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
@SuppressWarnings("unchecked")
public boolean remove(Object key)
{
return node.remove(key) != null;
}
@Override
public int size()
{
return node.dataSize();
}
@Override
@SuppressWarnings("unchecked")
public boolean add(K arg0)
{
return node.put(arg0, VALUE) == null;
}
@Override
public Iterator iterator()
{
final Iterator i = node.getKeys().iterator();
return new Iterator()
{
K key;
boolean next = false;
public boolean hasNext()
{
return i.hasNext();
}
@SuppressWarnings("unchecked")
public K next()
{
key = (K) i.next();
next = true;
return key;
}
@SuppressWarnings("unchecked")
public void remove()
{
if (!next)
throw new IllegalStateException();
node.remove(key);
}
};
}
}
static class PartitionedMapNode extends AbstractMap
{
private NodeSPI node;
private ChildSelector selector;
public PartitionedMapNode(Node node, ChildSelector selector)
{
this.node = (NodeSPI) node;
this.selector = selector;
}
@Override
public Set> entrySet()
{
return new AbstractSet>()
{
Iterator ci = node.getChildren().iterator();
@Override
public Iterator> iterator()
{
return new Iterator>()
{
Iterator ni;
{
nextChild();
findNext();
}
@SuppressWarnings("unchecked")
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();
}
@SuppressWarnings("unchecked")
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
@SuppressWarnings("unchecked")
public Set keySet()
{
return new AbstractSet>()
{
@Override
@SuppressWarnings("unchecked")
public Iterator> iterator()
{
return (Iterator>) 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();
}
@SuppressWarnings("unchecked")
private Fqn fqnFor(Object o)
{
return Fqn.fromRelativeFqn(node.getFqn(), selector.childName((K) o));
}
@Override
@SuppressWarnings("unchecked")
public boolean containsKey(Object o)
{
Fqn fqn = fqnFor(o);
Set keys = node.getCache().getKeys(fqn);
return keys != null && keys.contains(o);
}
@Override
@SuppressWarnings("unchecked")
public V get(Object key)
{
return (V) node.getCache().get(fqnFor(key), key);
}
@SuppressWarnings("unchecked")
public Object put(Object key, Object value)
{
return node.getCache().put(fqnFor(key), key, value);
}
@SuppressWarnings("unchecked")
public V remove(Object key)
{
return (V) node.getCache().remove(fqnFor(key), key);
}
public int size()
{
int size = 0;
for (Object o : node.getChildrenNames())
{
Node child = node.getChild(o);
size += child.dataSize();
}
return size;
}
}
}