/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2013 Jeff Nelson, Cinchapi Software Collective
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.cinchapi.concourse.cache;

import java.util.Map;

import javax.annotation.Nullable;

import org.apache.commons.codec.digest.DigestUtils;

import com.google.common.base.Preconditions;
import com.google.common.collect.MapMaker;

/**
 * <p>
 * A concurrent cache that holds references to objects so as to prevent
 * unnecessary constructor calls. The cache does not have a maximum capacity or
 * expulsion policy, but it uses a {@link SoftReference} for each stored object
 * so that the garbage collector will clear the cache only before an OOM occurs.
 * </p>
 * <h2>Example:</h2>
 * 
 * <code><pre>
 *  private class Foo {
 *    
 *    int id;
 *    String label;
 *    Bar bar;
 *    
 *    private static final ReferenceCache{@literal <Foo>} cache = new ReferenceCache{@literal <Foo>}();
 *    
 *    public static Foo newInstance(int id, String label, Bar bar){
 *    	Foo foo = cache.get(id, label, bar); // use the combination of id, label and bar as a cacheKey
 *    	if(foo == null){
 *    	    foo = new Foo(id, label, bar);
 *    		cache.put(foo, id, label, bar);
 *    	}
 *    	return foo;
 *    }
 *    
 *    private Foo(int id, String label, Bar bar){
 *    	this.id = id;
 *    	this.label = label;
 *    	this.bar = bar;
 *    }
 * }
 * </pre></code>
 * 
 * @author jnelson
 * @param <T> - the cached object type.
 */
public class ReferenceCache<T> {

	private static final int INITIAL_CAPACITY = 500000;
	private static final int CONCURRENCY_LEVEL = 16;

	private final Map<String, T> cache = new MapMaker()
			.initialCapacity(INITIAL_CAPACITY)
			.concurrencyLevel(CONCURRENCY_LEVEL).softValues().makeMap();

	/**
	 * Return {@code true} if the cache contains a value identified by the
	 * group of {@code args}.
	 * 
	 * @param args
	 * @return {@code true} if a value is found.
	 */
	public boolean contains(Object... args) {
		String id = getCacheKey(args);
		return cache.containsKey(id);
	}

	/**
	 * Return the cache value associated with the group of {@code args} or
	 * {@code null} if not value is found.
	 * 
	 * @param args
	 * @return the cached value.
	 */
	@Nullable
	public T get(Object... args) {
		String id = getCacheKey(args);
		return cache.get(id);
	}

	/**
	 * Cache {@code value} and associated it with the group of {@code args}.
	 * Each arg should be a value that is used to construct
	 * the object.
	 * 
	 * @param value
	 * @param args
	 * @return {@code true} if {@code value} is successfully cached.
	 */
	public void put(T value, Object... args) {
		Preconditions.checkNotNull(value);
		Preconditions.checkNotNull(args);
		Preconditions.checkArgument(args.length > 0,
				"You must specify at least one key");
		String id = getCacheKey(args);
		cache.put(id, value);
	}

	/**
	 * Remove the value associated with the group of {@code args} from the
	 * cache.
	 * 
	 * @param args
	 * @return {@code true} if the associated value is successfully removed.
	 */
	public void remove(Object... args) {
		String id = getCacheKey(args);
		cache.remove(id);
	}

	/**
	 * Return a unique identifier for a group of {@code args}.
	 * 
	 * @param args
	 * @return the identifier.
	 */
	private String getCacheKey(Object... args) {
		StringBuilder key = new StringBuilder();
		for (Object o : args) {
			key.append(o.hashCode());
			key.append(o.getClass().getName());
		}
		return DigestUtils.md5Hex(key.toString());
	}

}

@@ -1214,31 +1214,8 @@
 e;%0A%0A
-import java.util.Map;%0A%0A
 impo
@@ -1244,16 +1244,16 @@
 llable;%0A
+
 %0Aimport 
@@ -1373,21 +1373,61 @@
 on.c
-ollect.MapMak
+ache.Cache;%0Aimport com.google.common.cache.CacheBuild
 er;%0A
@@ -2687,19 +2687,21 @@
 e final 
-Map
+Cache
 %3CString,
@@ -2716,26 +2716,37 @@
 e = 
-new MapMaker()%0A%09%09%09
+CacheBuilder%0A%09%09%09.newBuilder()
 .ini
@@ -2833,307 +2833,16 @@
 s().
-makeMap();%0A%0A%09/**%0A%09 * Return %7B@code true%7D if the cache contains a value identified by the%0A%09 * group of %7B@code args%7D.%0A%09 * %0A%09 * @param args%0A%09 * @return %7B@code true%7D if a value is found.%0A%09 */%0A%09public boolean contains(Object... args) %7B%0A%09%09String id = getCacheKey(args);%0A%09%09return cache.containsKey(id);%0A%09%7D%0A
+build();
 %0A%09/*
@@ -3105,16 +3105,25 @@
 ache.get
+IfPresent
 (id);%0A%09%7D
@@ -3884,56 +3884,42 @@
 %7B%0A%09%09
-String id = getCacheKey(args);%0A%09%09cache.remove(id
+cache.invalidate(getCacheKey(args)
 );%0A%09

Use Guava CacheBuilder instead of MapMaker