blob: 96691b8a7a7049935975320404d22491345651d9 [file] [log] [blame]
/*
* Copyright (C) 2015 The Guava Authors
*
* 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.common.util.concurrent;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.util.concurrent.Futures.getDone;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import com.google.common.util.concurrent.internal.InternalFutureFailureAccess;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
/** Emulation for AbstractFuture in GWT. */
public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
implements ListenableFuture<V> {
/**
* Tag interface marking trusted subclasses. This enables some optimizations. The implementation
* of this interface must also be an AbstractFuture and must not override or expose for overriding
* any of the public methods of ListenableFuture.
*/
interface Trusted<V> extends ListenableFuture<V> {}
abstract static class TrustedFuture<V> extends AbstractFuture<V> implements Trusted<V> {
@Override
public final V get() throws InterruptedException, ExecutionException {
return super.get();
}
@Override
public final V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return super.get(timeout, unit);
}
@Override
public final boolean isDone() {
return super.isDone();
}
@Override
public final boolean isCancelled() {
return super.isCancelled();
}
@Override
public final void addListener(Runnable listener, Executor executor) {
super.addListener(listener, executor);
}
@Override
public final boolean cancel(boolean mayInterruptIfRunning) {
return super.cancel(mayInterruptIfRunning);
}
}
private static final Logger log = Logger.getLogger(AbstractFuture.class.getName());
private State state;
private V value;
private Future<? extends V> delegate;
private Throwable throwable;
private boolean mayInterruptIfRunning;
private List<Listener> listeners;
protected AbstractFuture() {
state = State.PENDING;
listeners = new ArrayList<Listener>();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (!state.permitsPublicUserToTransitionTo(State.CANCELLED)) {
return false;
}
this.mayInterruptIfRunning = mayInterruptIfRunning;
state = State.CANCELLED;
notifyAndClearListeners();
if (delegate != null) {
// TODO(lukes): consider adding the StackOverflowError protection from the server version
delegate.cancel(mayInterruptIfRunning);
}
return true;
}
protected void interruptTask() {}
@Override
public boolean isCancelled() {
return state.isCancelled();
}
@Override
public boolean isDone() {
return state.isDone();
}
/*
* ForwardingFluentFuture needs to override those methods, so they are not final.
*/
@Override
public V get() throws InterruptedException, ExecutionException {
state.maybeThrowOnGet(throwable);
return value;
}
@Override
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
checkNotNull(unit);
return get();
}
@Override
public void addListener(Runnable runnable, Executor executor) {
Listener listener = new Listener(runnable, executor);
if (isDone()) {
listener.execute();
} else {
listeners.add(listener);
}
}
protected boolean setException(Throwable throwable) {
checkNotNull(throwable);
if (!state.permitsPublicUserToTransitionTo(State.FAILURE)) {
return false;
}
forceSetException(throwable);
return true;
}
private void forceSetException(Throwable throwable) {
this.throwable = throwable;
this.state = State.FAILURE;
notifyAndClearListeners();
}
protected boolean set(V value) {
if (!state.permitsPublicUserToTransitionTo(State.VALUE)) {
return false;
}
forceSet(value);
return true;
}
private void forceSet(V value) {
this.value = value;
this.state = State.VALUE;
notifyAndClearListeners();
}
protected boolean setFuture(ListenableFuture<? extends V> future) {
checkNotNull(future);
// If this future is already cancelled, cancel the delegate.
// TODO(cpovirk): Should we do this at the end of the method, as in the server version?
// TODO(cpovirk): Use maybePropagateCancellationTo?
if (isCancelled()) {
future.cancel(mayInterruptIfRunning);
}
if (!state.permitsPublicUserToTransitionTo(State.DELEGATED)) {
return false;
}
state = State.DELEGATED;
this.delegate = future;
future.addListener(new SetFuture(future), directExecutor());
return true;
}
protected final boolean wasInterrupted() {
return mayInterruptIfRunning;
}
private void notifyAndClearListeners() {
afterDone();
// TODO(lukes): consider adding the StackOverflowError protection from the server version
// TODO(cpovirk): consider clearing this.delegate
for (Listener listener : listeners) {
listener.execute();
}
listeners = null;
}
protected void afterDone() {}
@Override
protected final Throwable tryInternalFastPathGetFailure() {
return state == State.FAILURE ? throwable : null;
}
final Throwable trustedGetException() {
checkState(state == State.FAILURE);
return throwable;
}
final void maybePropagateCancellationTo(@Nullable Future<?> related) {
if (related != null & isCancelled()) {
related.cancel(wasInterrupted());
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder().append(super.toString()).append("[status=");
if (isCancelled()) {
builder.append("CANCELLED");
} else if (isDone()) {
addDoneString(builder);
} else {
String pendingDescription;
try {
pendingDescription = pendingToString();
} catch (RuntimeException e) {
// Don't call getMessage or toString() on the exception, in case the exception thrown by the
// subclass is implemented with bugs similar to the subclass.
pendingDescription = "Exception thrown from implementation: " + e.getClass();
}
// The future may complete during or before the call to getPendingToString, so we use null
// as a signal that we should try checking if the future is done again.
if (!isNullOrEmpty(pendingDescription)) {
builder.append("PENDING, info=[").append(pendingDescription).append("]");
} else if (isDone()) {
addDoneString(builder);
} else {
builder.append("PENDING");
}
}
return builder.append("]").toString();
}
/**
* Provide a human-readable explanation of why this future has not yet completed.
*
* @return null if an explanation cannot be provided because the future is done.
*/
@Nullable
String pendingToString() {
if (state == State.DELEGATED) {
return "setFuture=[" + delegate + "]";
}
return null;
}
private void addDoneString(StringBuilder builder) {
try {
V value = getDone(this);
builder.append("SUCCESS, result=[").append(value).append("]");
} catch (ExecutionException e) {
builder.append("FAILURE, cause=[").append(e.getCause()).append("]");
} catch (CancellationException e) {
builder.append("CANCELLED");
} catch (RuntimeException e) {
builder.append("UNKNOWN, cause=[").append(e.getClass()).append(" thrown from get()]");
}
}
private enum State {
PENDING {
@Override
boolean isDone() {
return false;
}
@Override
void maybeThrowOnGet(Throwable cause) throws ExecutionException {
throw new IllegalStateException("Cannot get() on a pending future.");
}
@Override
boolean permitsPublicUserToTransitionTo(State state) {
return !state.equals(PENDING);
}
},
DELEGATED {
@Override
boolean isDone() {
return false;
}
@Override
void maybeThrowOnGet(Throwable cause) throws ExecutionException {
throw new IllegalStateException("Cannot get() on a pending future.");
}
boolean permitsPublicUserToTransitionTo(State state) {
return state.equals(CANCELLED);
}
},
VALUE,
FAILURE {
@Override
void maybeThrowOnGet(Throwable cause) throws ExecutionException {
throw new ExecutionException(cause);
}
},
CANCELLED {
@Override
boolean isCancelled() {
return true;
}
@Override
void maybeThrowOnGet(Throwable cause) throws ExecutionException {
// TODO(cpovirk): chain in a CancellationException created at the cancel() call?
throw new CancellationException();
}
};
boolean isDone() {
return true;
}
boolean isCancelled() {
return false;
}
void maybeThrowOnGet(Throwable cause) throws ExecutionException {}
boolean permitsPublicUserToTransitionTo(State state) {
return false;
}
}
private static final class Listener {
final Runnable command;
final Executor executor;
Listener(Runnable command, Executor executor) {
this.command = checkNotNull(command);
this.executor = checkNotNull(executor);
}
void execute() {
try {
executor.execute(command);
} catch (RuntimeException e) {
log.log(
Level.SEVERE,
"RuntimeException while executing runnable " + command + " with executor " + executor,
e);
}
}
}
private final class SetFuture implements Runnable {
final ListenableFuture<? extends V> delegate;
SetFuture(ListenableFuture<? extends V> delegate) {
this.delegate = delegate;
}
@Override
public void run() {
if (isCancelled()) {
return;
}
if (delegate instanceof AbstractFuture) {
AbstractFuture<? extends V> other = (AbstractFuture<? extends V>) delegate;
value = other.value;
throwable = other.throwable;
// don't copy the mayInterruptIfRunning bit, for consistency with the server, to ensure that
// interruptTask() is called if and only if the bit is true and because we cannot infer the
// interrupt status from non AbstractFuture futures.
state = other.state;
notifyAndClearListeners();
return;
}
/*
* Almost everything in GWT is an AbstractFuture (which is as good as TrustedFuture under
* GWT). But ImmediateFuture and UncheckedThrowingFuture aren't, so we still need this case.
*/
try {
forceSet(getDone(delegate));
} catch (ExecutionException exception) {
forceSetException(exception.getCause());
} catch (CancellationException cancellation) {
cancel(false);
} catch (Throwable t) {
forceSetException(t);
}
}
}
}