blob: 8de78a9f1ff707fe757e5cefa0c747c1c7404796 [file] [log] [blame]
/**
* Copyright (C) 2006 Google 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.google.inject.util;
import static com.google.inject.util.ReferenceType.STRONG;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* Extends {@link ReferenceMap} to support lazy loading values by overriding
* {@link #create(Object)}.
*
* @author crazybob@google.com (Bob Lee)
*/
public abstract class ReferenceCache<K, V> extends ReferenceMap<K, V> {
private static final long serialVersionUID = 0;
transient ConcurrentMap<Object, Future<V>> futures =
new ConcurrentHashMap<Object, Future<V>>();
transient ThreadLocal<Future<V>> localFuture = new ThreadLocal<Future<V>>();
public ReferenceCache(ReferenceType keyReferenceType,
ReferenceType valueReferenceType) {
super(keyReferenceType, valueReferenceType);
}
/**
* Equivalent to {@code new ReferenceCache(STRONG, STRONG)}.
*/
public ReferenceCache() {
super(STRONG, STRONG);
}
/**
* Override to lazy load values. Use as an alternative to {@link
* #put(Object,Object)}. Invoked by getter if value isn't already cached.
* Must not return {@code null}. This method will not be called again until
* the garbage collector reclaims the returned value.
*/
protected abstract V create(K key);
V internalCreate(K key) {
try {
FutureTask<V> futureTask = new FutureTask<V>(
new CallableCreate(key));
// use a reference so we get the same equality semantics.
Object keyReference = referenceKey(key);
Future<V> future = futures.putIfAbsent(keyReference, futureTask);
if (future == null) {
// winning thread.
try {
if (localFuture.get() != null) {
throw new IllegalStateException(
"Nested creations within the same cache are not allowed.");
}
localFuture.set(futureTask);
futureTask.run();
V value = futureTask.get();
putStrategy().execute(this,
keyReference, referenceValue(keyReference, value));
return value;
} finally {
localFuture.remove();
futures.remove(keyReference);
}
} else {
// wait for winning thread.
return future.get();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException(cause);
}
}
/**
* {@inheritDoc}
*
* If this map does not contain an entry for the given key and {@link
* #create(Object)} has been overridden, this method will create a new
* value, put it in the map, and return it.
*
* @throws NullPointerException if {@link #create(Object)} returns null.
* @throws java.util.concurrent.CancellationException if the creation is
* cancelled. See {@link #cancel()}.
*/
@SuppressWarnings("unchecked")
@Override public V get(final Object key) {
V value = super.get(key);
return (value == null)
? internalCreate((K) key)
: value;
}
/**
* Cancels the current {@link #create(Object)}. Throws {@link
* java.util.concurrent.CancellationException} to all clients currently
* blocked on {@link #get(Object)}.
*/
protected void cancel() {
Future<V> future = localFuture.get();
if (future == null) {
throw new IllegalStateException("Not in create().");
}
future.cancel(false);
}
class CallableCreate implements Callable<V> {
K key;
public CallableCreate(K key) {
this.key = key;
}
public V call() {
// try one more time (a previous future could have come and gone.)
V value = internalGet(key);
if (value != null) {
return value;
}
// create value.
value = create(key);
if (value == null) {
throw new NullPointerException(
"create(K) returned null for: " + key);
}
return value;
}
}
/**
* Returns a {@code ReferenceCache} delegating to the specified {@code
* function}. The specified function must not return {@code null}.
*/
public static <K, V> ReferenceCache<K, V> of(
ReferenceType keyReferenceType,
ReferenceType valueReferenceType,
final Function<? super K, ? extends V> function) {
ensureNotNull(function);
return new ReferenceCache<K, V>(keyReferenceType, valueReferenceType) {
protected V create(K key) {
return function.apply(key);
}
private static final long serialVersionUID = 0;
};
}
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
this.futures = new ConcurrentHashMap<Object, Future<V>>();
this.localFuture = new ThreadLocal<Future<V>>();
}
}