/** * */ package com.trendrr.oss.networking; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map.Entry; import java.util.TreeMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.trendrr.oss.HashFunctions; /** * @author Dustin Norlander * @created Feb 27, 2012 * */ public class ConsistentHashRing<T> { protected static Log log = LogFactory.getLog(ConsistentHashRing.class); protected TreeMap<Integer, T> connections = new TreeMap<Integer, T>(); public static interface Hasher { public int hash(String key); } protected Hasher hashFunction = new Hasher() { @Override public int hash(String key) { try { return HashFunctions.murmurhash3(key.getBytes("utf8"), 8008); } catch (UnsupportedEncodingException e) { log.error("Caught", e); } return 0; } }; /** * sets a new hashfunction. Default hashfunction is murmur3_32 with seed 8008 * @param hashFunction */ public synchronized void setHashFunction(ConsistentHashRing.Hasher hashFunction) { this.hashFunction = hashFunction; } public synchronized int size() { return this.connections.size(); } public synchronized boolean isEmpty() { return this.connections.isEmpty(); } /** * gets all the items in the ring. this returns a copy. * @return */ public synchronized List<T> getAll() { return new ArrayList<T>(this.connections.values()); } /** * will return the requested number of connections, with no duplicate connections. * @param key * @param num * @return */ public synchronized List<T> get(String key, int num) { int hash = this.hash(key); List<T> ret = new ArrayList<T>(); int cur = hash; for (int i=0; i < connections.size(); i++) { Entry<Integer, T> entry = connections.higherEntry(cur); if (entry == null) { entry = connections.firstEntry(); } cur = entry.getKey(); T item = entry.getValue(); if (!ret.contains(item)) { ret.add(item); } if (ret.size() == num) { return ret; } } return ret; } /** * removes a connection from the ring * @param connection */ public synchronized void remove(T connection) { List<Integer> removeKeys = new ArrayList<Integer>(); for (Integer key : connections.keySet()) { if (connections.get(key) == connection) { removeKeys.add(key); } } for (Integer k : removeKeys) { connections.remove(k); } } /** * Adds a new connection to this hashring. * * @param identifier * @param connection * @param points */ public synchronized void add(String identifier, T connection, int points) { for (int i=0; i < points; i++) { int hash = this.hash(identifier + ":" + i); connections.put(hash, connection); } } /* * hash the key to an signed integer. */ protected int hash(String key) { return this.hashFunction.hash(key); } }