001: /**
002: * Copyright (C) 2006 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */package com.bm.ejb3guice.internal;
016:
017: import java.io.IOException;
018: import java.io.ObjectInputStream;
019: import java.util.concurrent.ConcurrentHashMap;
020: import java.util.concurrent.ConcurrentMap;
021:
022: /**
023: * Supports cache implementations.
024: *
025: * @author crazybob@google.com (Bob Lee)
026: */
027: abstract class AbstractReferenceCache<K, V> extends ReferenceMap<K, V> {
028:
029: private static final long serialVersionUID = 0;
030:
031: transient ConcurrentMap<Object, FutureValue<V>> futures = new ConcurrentHashMap<Object, FutureValue<V>>();
032:
033: AbstractReferenceCache(ReferenceType keyReferenceType,
034: ReferenceType valueReferenceType) {
035: super (keyReferenceType, valueReferenceType);
036: }
037:
038: V internalCreate(K key) {
039: FutureValue<V> futureValue = new FutureValue<V>();
040:
041: // use a reference so we get the correct equality semantics.
042: Object keyReference = referenceKey(key);
043: FutureValue<V> previous = futures.putIfAbsent(keyReference,
044: futureValue);
045: if (previous == null) {
046: // winning thread.
047: try {
048: // check one more time (a previous future could have come and gone.)
049: V value = internalGet(key);
050: if (value != null) {
051: futureValue.setValue(value);
052: return value;
053: }
054:
055: try {
056: value = create(futureValue, key);
057: if (value == null) {
058: throw new NullPointerException(
059: "create() returned null for: " + key);
060: }
061: futureValue.setValue(value);
062: } catch (Throwable t) {
063: futureValue.setThrowable(t);
064: rethrow(t);
065: }
066:
067: putStrategy().execute(this , keyReference,
068: referenceValue(keyReference, value));
069:
070: return value;
071: } finally {
072: futures.remove(keyReference);
073: }
074: } else {
075: if (previous.winningThread() == Thread.currentThread()) {
076: throw new RuntimeException("Circular reference: " + key);
077: }
078:
079: // wait for winning thread.
080: return previous.get();
081: }
082: }
083:
084: private static void rethrow(Throwable t) {
085: if (t instanceof RuntimeException) {
086: throw (RuntimeException) t;
087: }
088: if (t instanceof Error) {
089: throw (Error) t;
090: }
091: throw new RuntimeException(t);
092: }
093:
094: /**
095: * Creates a value for the given key.
096: */
097: abstract V create(FutureValue<V> futureValue, K key);
098:
099: /**
100: * {@inheritDoc}
101: *
102: * If this map does not contain an entry for the given key, this method will
103: * create a new value, put it in the map, and return it. The value
104: * is canonical (i.e. only one value will be created for each key).
105: *
106: * @throws NullPointerException if the value is {@code null}
107: * @throws java.util.concurrent.CancellationException if the value creation
108: * is cancelled.
109: */
110: @SuppressWarnings("unchecked")
111: @Override
112: public V get(final Object key) {
113: V value = super .get(key);
114: return (value == null) ? internalCreate((K) key) : value;
115: }
116:
117: private void readObject(ObjectInputStream in) throws IOException,
118: ClassNotFoundException {
119: in.defaultReadObject();
120: this .futures = new ConcurrentHashMap<Object, FutureValue<V>>();
121: }
122:
123: /**
124: * Synchronizes threads waiting for the same value.
125: */
126: static class FutureValue<V> {
127:
128: /** True if the result has been set. */
129: private boolean set = false;
130:
131: /** The result is a value. */
132: private V value;
133:
134: /** The result is a throwable. */
135: private Throwable t;
136:
137: private final Thread winningThread = Thread.currentThread();
138:
139: Thread winningThread() {
140: return winningThread;
141: }
142:
143: V get() {
144: if (!set) {
145: boolean interrupted = waitUntilSet();
146:
147: if (interrupted) {
148: Thread.currentThread().interrupt();
149: }
150: }
151:
152: if (t != null) {
153: rethrow(t);
154: }
155: return value;
156: }
157:
158: /**
159: * Waits until a value is set.
160: *
161: * @return {@code true} if the thread was interrupted while waiting
162: */
163: private synchronized boolean waitUntilSet() {
164: boolean interrupted = false;
165: while (!set) {
166: try {
167: wait();
168: } catch (InterruptedException e) {
169: interrupted = true;
170: }
171: }
172: return interrupted;
173: }
174:
175: synchronized void setValue(V v) {
176: set();
177: value = v;
178: }
179:
180: synchronized void setThrowable(Throwable t) {
181: set();
182: this .t = t;
183: }
184:
185: private void set() {
186: if (set) {
187: throw new IllegalStateException("Value is already set.");
188: }
189: set = true;
190: notifyAll();
191: }
192: }
193: }
|