| /** |
| * 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.internal; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| /** |
| * Supports cache implementations. |
| * |
| * @author crazybob@google.com (Bob Lee) |
| */ |
| abstract class AbstractReferenceCache<K, V> extends ReferenceMap<K, V> { |
| |
| private static final long serialVersionUID = 0; |
| |
| transient ConcurrentMap<Object, FutureValue<V>> futures = |
| new ConcurrentHashMap<Object, FutureValue<V>>(); |
| |
| AbstractReferenceCache(ReferenceType keyReferenceType, |
| ReferenceType valueReferenceType) { |
| super(keyReferenceType, valueReferenceType); |
| } |
| |
| V internalCreate(K key) { |
| FutureValue<V> futureValue = new FutureValue<V>(); |
| |
| // use a reference so we get the correct equality semantics. |
| Object keyReference = referenceKey(key); |
| FutureValue<V> previous = |
| futures.putIfAbsent(keyReference, futureValue); |
| if (previous == null) { |
| // winning thread. |
| try { |
| // check one more time (a previous future could have come and gone.) |
| V value = internalGet(key); |
| if (value != null) { |
| futureValue.setValue(value); |
| return value; |
| } |
| |
| try { |
| value = create(futureValue, key); |
| if (value == null) { |
| throw new NullPointerException( |
| "create() returned null for: " + key); |
| } |
| futureValue.setValue(value); |
| } catch (Throwable t) { |
| futureValue.setThrowable(t); |
| rethrow(t); |
| } |
| |
| putStrategy().execute( |
| this, keyReference, referenceValue(keyReference, value)); |
| |
| return value; |
| } finally { |
| futures.remove(keyReference); |
| } |
| } else { |
| if (previous.winningThread() == Thread.currentThread()) { |
| throw new RuntimeException("Circular reference: " + key); |
| } |
| |
| // wait for winning thread. |
| return previous.get(); |
| } |
| } |
| |
| private static void rethrow(Throwable t) { |
| if (t instanceof RuntimeException) { |
| throw (RuntimeException) t; |
| } |
| if (t instanceof Error) { |
| throw (Error) t; |
| } |
| throw new RuntimeException(t); |
| } |
| |
| /** |
| * Creates a value for the given key. |
| */ |
| abstract V create(FutureValue<V> futureValue, K key); |
| |
| /** |
| * {@inheritDoc} |
| * |
| * If this map does not contain an entry for the given key, this method will |
| * create a new value, put it in the map, and return it. The value |
| * is canonical (i.e. only one value will be created for each key). |
| * |
| * @throws NullPointerException if the value is {@code null} |
| * @throws java.util.concurrent.CancellationException if the value creation |
| * is cancelled. |
| */ |
| @SuppressWarnings("unchecked") |
| @Override public V get(final Object key) { |
| V value = super.get(key); |
| return (value == null) |
| ? internalCreate((K) key) |
| : value; |
| } |
| |
| private void readObject(ObjectInputStream in) throws IOException, |
| ClassNotFoundException { |
| in.defaultReadObject(); |
| this.futures = new ConcurrentHashMap<Object, FutureValue<V>>(); |
| } |
| |
| /** |
| * Synchronizes threads waiting for the same value. |
| */ |
| static class FutureValue<V> { |
| |
| /** True if the result has been set. */ |
| private boolean set = false; |
| |
| /** The result is a value. */ |
| private V value; |
| |
| /** The result is a throwable. */ |
| private Throwable t; |
| |
| private final Thread winningThread = Thread.currentThread(); |
| |
| Thread winningThread() { |
| return winningThread; |
| } |
| |
| synchronized V get() { |
| if (!set) { |
| boolean interrupted = waitUntilSet(); |
| |
| if (interrupted) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| |
| if (t != null) { |
| rethrow(t); |
| } |
| return value; |
| } |
| |
| /** |
| * Waits until a value is set. |
| * |
| * @return {@code true} if the thread was interrupted while waiting |
| */ |
| private synchronized boolean waitUntilSet() { |
| boolean interrupted = false; |
| while (!set) { |
| try { |
| wait(); |
| } catch (InterruptedException e) { |
| interrupted = true; |
| } |
| } |
| return interrupted; |
| } |
| |
| synchronized void setValue(V v) { |
| set(); |
| value = v; |
| } |
| |
| synchronized void setThrowable(Throwable t) { |
| set(); |
| this.t = t; |
| } |
| |
| private void set() { |
| if (set) { |
| throw new IllegalStateException("Value is already set."); |
| } |
| set = true; |
| notifyAll(); |
| } |
| } |
| } |