io.atlassian.util.concurrent.SettableFuture Maven / Gradle / Ivy
Show all versions of atlassian-util-concurrent Show documentation
/**
* Copyright 2008 Atlassian Pty Ltd
*
* 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 io.atlassian.util.concurrent;
import net.jcip.annotations.ThreadSafe;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
/**
* {@link io.atlassian.util.concurrent.SettableFuture} is a
* {@link java.util.concurrent.Future} implementation where the responsibility
* for producing the result is external to the future instance, unlike
* {@link java.util.concurrent.FutureTask} where the future holds the operation
* (a {@link java.util.concurrent.Callable} or {@link java.lang.Runnable}
* instance) and the first thread that calls
* {@link java.util.concurrent.FutureTask#run()} executes the operation.
*
* This is useful in situations where all the inputs may not be available at
* construction time.
*/
@ThreadSafe public class SettableFuture implements Future {
private final AtomicReference> ref = new AtomicReference>();
private final CountDownLatch latch = new CountDownLatch(1);
/**
* Set the value returned by {@link #get()} and {@link #get(long, TimeUnit)}
*
* Note that this can only be done once unless the value of the second set
* equals the first value otherwise an exception will be thrown. It also
* cannot be set if this future has been cancelled or an exception has been
* set.
*
* @param value the value to be set.
* @return a {@link io.atlassian.util.concurrent.SettableFuture} object.
*/
public final SettableFuture set(final T value) {
setAndCheckValue(new ReferenceValue(value));
return this;
}
/**
* Set the exception thrown as the causal exception of an ExecutionException
* by {@link #get()} and {@link #get(long, TimeUnit)}
*
* Note that this can only be done once unless the value of the second
* {@link #setException(Throwable)} equals the first value otherwise an
* exception will be thrown (as most exceptions do not implement equals this
* effectively means the same reference). It also cannot be set if this future
* has been cancelled or a a value has been set.
*
* @param throwable a {@link java.lang.Throwable}.
* @return a {@link io.atlassian.util.concurrent.SettableFuture}.
*/
public final SettableFuture setException(final Throwable throwable) {
setAndCheckValue(new ThrowableValue(throwable));
return this;
}
/**
* Get the value of the future. Blocking
*
* @return a T.
* @throws java.lang.InterruptedException if any.
* @throws java.util.concurrent.ExecutionException if any.
*/
public final T get() throws InterruptedException, ExecutionException {
latch.await();
return ref.get().get();
}
/** {@inheritDoc} */
public final T get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
if (!latch.await(timeout, unit)) {
throw new TimedOutException(timeout, unit);
}
return ref.get().get();
}
/** {@inheritDoc} */
public final boolean isDone() {
return ref.get() != null;
}
/** {@inheritDoc} */
public final boolean isCancelled() {
return isDone() && (ref.get() instanceof CancelledValue>);
}
/** {@inheritDoc} */
public final boolean cancel(final boolean mayInterruptIfRunning) {
return setValue(new CancelledValue()) == null;
}
/**
* Set the inner value and check that if there is an old value it equals the
* one we are setting.
*
* @param value to set.
*/
private void setAndCheckValue(final Value value) {
final Value oldValue = setValue(value);
if ((oldValue != null) && !value.equals(oldValue)) {
throw new IllegalStateException("cannot change value after it has been set");
}
}
/**
* Set the inner value.
*
* @param value to set.
* @return the old value if set or null.
*/
private Value setValue(final Value value) {
while (true) {
final Value oldValue = ref.get();
if (oldValue != null) {
return oldValue;
}
// /CLOVER:OFF
if (!ref.compareAndSet(null, value)) {
continue;
}
// /CLOVER:ON
latch.countDown();
return null;
}
}
/** the inner value */
private static interface Value {
T get() throws ExecutionException;
}
/** holds a reference */
private static class ReferenceValue implements Value {
private final T value;
ReferenceValue(final T value) {
this.value = value;
}
public T get() {
return value;
}
@Override public boolean equals(final Object obj) {
// no need to check for reference equality, not possible
if (!(obj instanceof ReferenceValue>)) {
return false;
}
final ReferenceValue> other = (ReferenceValue>) obj;
return (value == null) ? (other.value == null) : value.equals(other.value);
}
// /CLOVER:OFF
@Override public int hashCode() {
throw new UnsupportedOperationException();
}
// /CLOVER:ON
}
/** holds an exception */
private static class ThrowableValue implements Value {
private final Throwable throwable;
ThrowableValue(final Throwable throwable) {
this.throwable = throwable;
}
public T get() throws ExecutionException {
throw new ExecutionException(throwable);
}
@Override public boolean equals(final Object obj) {
// no need to check for reference equality, not possible
if (!(obj instanceof ThrowableValue>)) {
return false;
}
return throwable.equals(((ThrowableValue>) obj).throwable);
}
// /CLOVER:OFF
@Override public int hashCode() {
throw new UnsupportedOperationException();
}
// /CLOVER:ON
}
// doesn't need to implement equals as cancel doesn't check
private static class CancelledValue implements Value {
public T get() throws ExecutionException {
throw new CancellationException();
}
}
}