/* * Copyright (c) 2013-2017 Cinchapi Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cinchapi.concourse.cache; import javax.annotation.Nullable; import com.google.common.base.Preconditions; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; /** * <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 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 Jeff Nelson * @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 Cache<HashCode, T> cache = CacheBuilder.newBuilder() .initialCapacity(INITIAL_CAPACITY) .concurrencyLevel(CONCURRENCY_LEVEL).softValues().build(); /** * 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) { HashCode id = getCacheKey(args); return cache.getIfPresent(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 */ 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"); HashCode id = getCacheKey(args); cache.put(id, value); } /** * Remove the value associated with the group of {@code args} from the * cache. * * @param args */ public void remove(Object... args) { cache.invalidate(getCacheKey(args)); } /** * Return a unique identifier for a group of {@code args}. * * @param args * @return the identifier. */ private HashCode getCacheKey(Object... args) { StringBuilder key = new StringBuilder(); for (Object o : args) { key.append(o.hashCode()); key.append(o.getClass().getName()); } return Hashing.md5().hashUnencodedChars(key.toString()); } }