1 /**
2 * Copyright (C) 2006 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.opensymphony.xwork2.inject.util;
18
19 import static com.opensymphony.xwork2.inject.util.ReferenceType.STRONG;
20
21 import java.io.IOException;
22 import java.io.ObjectInputStream;
23 import java.util.concurrent;
24
25 /**
26 * Extends {@link ReferenceMap} to support lazy loading values by overriding
27 * {@link #create(Object)}.
28 *
29 * @author crazybob@google.com (Bob Lee)
30 */
31 public abstract class ReferenceCache<K, V> extends ReferenceMap<K, V> {
32
33 private static final long serialVersionUID = 0;
34
35 transient ConcurrentMap<Object, Future<V>> futures =
36 new ConcurrentHashMap<Object, Future<V>>();
37
38 transient ThreadLocal<Future<V>> localFuture = new ThreadLocal<Future<V>>();
39
40 public ReferenceCache(ReferenceType keyReferenceType,
41 ReferenceType valueReferenceType) {
42 super(keyReferenceType, valueReferenceType);
43 }
44
45 /**
46 * Equivalent to {@code new ReferenceCache(STRONG, STRONG)}.
47 */
48 public ReferenceCache() {
49 super(STRONG, STRONG);
50 }
51
52 /**
53 * Override to lazy load values. Use as an alternative to {@link
54 * #put(Object,Object)}. Invoked by getter if value isn't already cached.
55 * Must not return {@code null}. This method will not be called again until
56 * the garbage collector reclaims the returned value.
57 */
58 protected abstract V create(K key);
59
60 V internalCreate(K key) {
61 try {
62 FutureTask<V> futureTask = new FutureTask<V>(
63 new CallableCreate(key));
64
65 // use a reference so we get the same equality semantics.
66 Object keyReference = referenceKey(key);
67 Future<V> future = futures.putIfAbsent(keyReference, futureTask);
68 if (future == null) {
69 // winning thread.
70 try {
71 if (localFuture.get() != null) {
72 throw new IllegalStateException(
73 "Nested creations within the same cache are not allowed.");
74 }
75 localFuture.set(futureTask);
76 futureTask.run();
77 V value = futureTask.get();
78 putStrategy().execute(this,
79 keyReference, referenceValue(keyReference, value));
80 return value;
81 } finally {
82 localFuture.remove();
83 futures.remove(keyReference);
84 }
85 } else {
86 // wait for winning thread.
87 return future.get();
88 }
89 } catch (InterruptedException e) {
90 throw new RuntimeException(e);
91 } catch (ExecutionException e) {
92 Throwable cause = e.getCause();
93 if (cause instanceof RuntimeException) {
94 throw (RuntimeException) cause;
95 } else if (cause instanceof Error) {
96 throw (Error) cause;
97 }
98 throw new RuntimeException(cause);
99 }
100 }
101
102 /**
103 * {@inheritDoc}
104 *
105 * If this map does not contain an entry for the given key and {@link
106 * #create(Object)} has been overridden, this method will create a new
107 * value, put it in the map, and return it.
108 *
109 * @throws NullPointerException if {@link #create(Object)} returns null.
110 * @throws java.util.concurrent.CancellationException if the creation is
111 * cancelled. See {@link #cancel()}.
112 */
113 @SuppressWarnings("unchecked")
114 @Override public V get(final Object key) {
115 V value = super.get(key);
116 return (value == null)
117 ? internalCreate((K) key)
118 : value;
119 }
120
121 /**
122 * Cancels the current {@link #create(Object)}. Throws {@link
123 * java.util.concurrent.CancellationException} to all clients currently
124 * blocked on {@link #get(Object)}.
125 */
126 protected void cancel() {
127 Future<V> future = localFuture.get();
128 if (future == null) {
129 throw new IllegalStateException("Not in create().");
130 }
131 future.cancel(false);
132 }
133
134 class CallableCreate implements Callable<V> {
135
136 K key;
137
138 public CallableCreate(K key) {
139 this.key = key;
140 }
141
142 public V call() {
143 // try one more time (a previous future could have come and gone.)
144 V value = internalGet(key);
145 if (value != null) {
146 return value;
147 }
148
149 // create value.
150 value = create(key);
151 if (value == null) {
152 throw new NullPointerException(
153 "create(K) returned null for: " + key);
154 }
155 return value;
156 }
157 }
158
159 /**
160 * Returns a {@code ReferenceCache} delegating to the specified {@code
161 * function}. The specified function must not return {@code null}.
162 */
163 public static <K, V> ReferenceCache<K, V> of(
164 ReferenceType keyReferenceType,
165 ReferenceType valueReferenceType,
166 final Function<? super K, ? extends V> function) {
167 ensureNotNull(function);
168 return new ReferenceCache<K, V>(keyReferenceType, valueReferenceType) {
169 @Override
170 protected V create(K key) {
171 return function.apply(key);
172 }
173 private static final long serialVersionUID = 0;
174 };
175 }
176
177 private void readObject(ObjectInputStream in) throws IOException,
178 ClassNotFoundException {
179 in.defaultReadObject();
180 this.futures = new ConcurrentHashMap<Object, Future<V>>();
181 this.localFuture = new ThreadLocal<Future<V>>();
182 }
183
184 }