1 /**
2 * Copyright 2008 Atlassian Pty Ltd
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.atlassian.util.concurrent;
18
19 import java.lang.ref.Reference;
20 import java.lang.ref.WeakReference;
21 import java.util.concurrent.Callable;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.FutureTask;
24
25 import net.jcip.annotations.ThreadSafe;
26
27 /**
28 * Lazily loaded reference that is not constructed until required. This class is
29 * used to maintain a reference to an object that is expensive to create and
30 * must be constructed once and once only. This reference behaves as though the
31 * <code>final</code> keyword has been used (you cannot reset it once it has
32 * been constructed).
33 * <p>
34 * Usage: clients need to implement the {@link #create()} method to return the
35 * object this reference will hold.
36 * <p>
37 * For instance:
38 * <p>
39 *
40 * <pre>
41 * final LazyReference<MyObject> ref = new LazyReference() {
42 * protected MyObject create() throws Exception {
43 * // Do expensive object construction here
44 * return new MyObject();
45 * }
46 * };
47 * </pre>
48 *
49 * Then call {@link #get()} to get a reference to the referenced object:
50 *
51 * <pre>
52 * MyObject myLazyLoadedObject = ref.get()
53 * </pre>
54 *
55 * NOTE: Interruption policy is that if you want to be cancellable while waiting
56 * for another thread to create the value, instead of calling {@link #get()}
57 * call {@link #getInterruptibly()}. However, If your {@link #create()} method
58 * is interrupted and throws an {@link InterruptedException}, it is treated as
59 * an application exception and will be the causal exception inside the runtime
60 * {@link InitializationException} that {@link #get()} or
61 * {@link #getInterruptibly()} throws and your {@link #create()} will not be
62 * called again.
63 * <p>
64 * Implementation note. This class extends {@link WeakReference} as
65 * {@link Reference} does not have a public constructor. WeakReference is
66 * preferable as it does not have any members and therefore doesn't increase the
67 * memory footprint. As we never pass a referent through to the super-class and
68 * override {@link #get()}, the garbage collection semantics of WeakReference
69 * are irrelevant. The referenced object will not become eligible for GC unless
70 * the object holding the reference to this object is collectible.
71 */
72 @ThreadSafe
73 public abstract class LazyReference<T> extends WeakReference<T> {
74 private final FutureTask<T> future = new FutureTask<T>(new Callable<T>() {
75 public T call() throws Exception {
76 return create();
77 }
78 });
79
80 public LazyReference() {
81 super(null);
82 }
83
84 /**
85 * The object factory method, guaranteed to be called once and only once.
86 *
87 * @return the object that {@link #get()} and {@link #getInterruptibly()}
88 * will return.
89 * @throws Exception if anything goes wrong, rethrown as an
90 * InitializationException from {@link #get()} and
91 * {@link #getInterruptibly()}
92 */
93 protected abstract T create() throws Exception;
94
95 /**
96 * Get the lazily loaded reference in a non-cancellable manner. If your
97 * <code>create()</code> method throws an Exception calls to
98 * <code>get()</code> will throw an InitializationException which wraps the
99 * previously thrown exception.
100 *
101 * @return the object that {@link #create()} created.
102 * @throws InitializationException if the {@link #create()} method throws an
103 * exception. The {@link InitializationException#getCause()} will contain
104 * the exception thrown by the {@link #create()} method
105 */
106 @Override
107 public final T get() {
108 boolean interrupted = false;
109 try {
110 while (true) {
111 try {
112 return getInterruptibly();
113 } catch (final InterruptedException ignore) {
114 // ignore and try again
115 interrupted = true;
116 }
117 }
118 } finally {
119 if (interrupted) {
120 Thread.currentThread().interrupt();
121 }
122 }
123 }
124
125 /**
126 * Get the lazily loaded reference in a cancellable manner. If your
127 * <code>create()</code> method throws an Exception, calls to
128 * <code>get()</code> will throw a RuntimeException which wraps the
129 * previously thrown exception.
130 *
131 * @return the object that {@link #create()} created.
132 * @throws InitializationException if the {@link #create()} method throws an
133 * exception. The {@link InitializationException#getCause()} will contain
134 * the exception thrown by the {@link #create()} method
135 * @throws InterruptedException If the calling thread is Interrupted while
136 * waiting for another thread to create the value (if the creating thread is
137 * interrupted while blocking on something, the {@link InterruptedException}
138 * will be thrown as the causal exception of the
139 * {@link InitializationException} to everybody calling this method).
140 */
141 public final T getInterruptibly() throws InterruptedException {
142 if (!future.isDone()) {
143 future.run();
144 }
145
146 try {
147 return future.get();
148 } catch (final ExecutionException e) {
149 throw new InitializationException(e);
150 }
151 }
152
153 /**
154 * Has the {@link #create()} reference been initialized.
155 *
156 * @return true if the task is complete
157 */
158 public boolean isInitialized() {
159 return future.isDone();
160 }
161
162 /**
163 * Cancel the initializing operation if it has not already run. Will try and
164 * interrupt if it is currently running.
165 */
166 public void cancel() {
167 future.cancel(true);
168 }
169
170 /**
171 * If the factory {@link LazyReference#create()} method threw an exception,
172 * this wraps it.
173 */
174 public static class InitializationException extends RuntimeException {
175 private static final long serialVersionUID = 3638376010285456759L;
176
177 InitializationException(final ExecutionException e) {
178 super((e.getCause() != null) ? e.getCause() : e);
179 }
180 }
181 }