/* * Copyright 2000-2015 JetBrains s.r.o. * * 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 org.jetbrains.concurrency; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Getter; import com.intellij.util.Consumer; import com.intellij.util.Function; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; public class AsyncPromise<T> extends Promise<T> implements Getter<T> { private static final Logger LOG = Logger.getInstance(AsyncPromise.class); public static final RuntimeException OBSOLETE_ERROR = Promise.createError("Obsolete"); private volatile Consumer<T> done; private volatile Consumer<Throwable> rejected; protected volatile State state = State.PENDING; // result object or error message private volatile Object result; @NotNull @Override public State getState() { return state; } @NotNull @Override public Promise<T> done(@NotNull Consumer<T> done) { if (isObsolete(done)) { return this; } switch (state) { case PENDING: break; case FULFILLED: //noinspection unchecked done.consume((T)result); return this; case REJECTED: return this; } this.done = setHandler(this.done, done); return this; } @NotNull @Override public Promise<T> rejected(@NotNull Consumer<Throwable> rejected) { if (isObsolete(rejected)) { return this; } switch (state) { case PENDING: break; case FULFILLED: return this; case REJECTED: rejected.consume((Throwable)result); return this; } this.rejected = setHandler(this.rejected, rejected); return this; } @Override public T get() { //noinspection unchecked return state == State.FULFILLED ? (T)result : null; } @SuppressWarnings("SynchronizeOnThis") private static final class CompoundConsumer<T> implements Consumer<T> { private List<Consumer<T>> consumers = new ArrayList<Consumer<T>>(); public CompoundConsumer(@NotNull Consumer<T> c1, @NotNull Consumer<T> c2) { synchronized (this) { consumers.add(c1); consumers.add(c2); } } @Override public void consume(T t) { List<Consumer<T>> list; synchronized (this) { list = consumers; consumers = null; } if (list != null) { for (Consumer<T> consumer : list) { if (!isObsolete(consumer)) { consumer.consume(t); } } } } public void add(@NotNull Consumer<T> consumer) { synchronized (this) { if (consumers != null) { consumers.add(consumer); } } } } @Override @NotNull public <SUB_RESULT> Promise<SUB_RESULT> then(@NotNull final Function<T, SUB_RESULT> fulfilled) { switch (state) { case PENDING: break; case FULFILLED: //noinspection unchecked return new DonePromise<SUB_RESULT>(fulfilled.fun((T)result)); case REJECTED: return new RejectedPromise<SUB_RESULT>((Throwable)result); } final AsyncPromise<SUB_RESULT> promise = new AsyncPromise<SUB_RESULT>(); addHandlers(new Consumer<T>() { @Override public void consume(T result) { try { if (fulfilled instanceof Obsolescent && ((Obsolescent)fulfilled).isObsolete()) { promise.setError(OBSOLETE_ERROR); } else { promise.setResult(fulfilled.fun(result)); } } catch (Throwable e) { promise.setError(e); } } }, new Consumer<Throwable>() { @Override public void consume(Throwable error) { promise.setError(error); } }); return promise; } @Override public void notify(@NotNull final AsyncPromise<T> child) { LOG.assertTrue(child != this); switch (state) { case PENDING: break; case FULFILLED: //noinspection unchecked child.setResult((T)result); return; case REJECTED: child.setError((Throwable)result); return; } addHandlers(new Consumer<T>() { @Override public void consume(T result) { try { child.setResult(result); } catch (Throwable e) { child.setError(e); } } }, new Consumer<Throwable>() { @Override public void consume(Throwable error) { child.setError(error); } }); } @Override @NotNull public Promise<T> processed(@NotNull final AsyncPromise<T> fulfilled) { switch (state) { case PENDING: break; case FULFILLED: //noinspection unchecked fulfilled.setResult((T)result); return this; case REJECTED: fulfilled.setError((Throwable)result); return this; } addHandlers(new Consumer<T>() { @Override public void consume(T result) { try { fulfilled.setResult(result); } catch (Throwable e) { fulfilled.setError(e); } } }, new Consumer<Throwable>() { @Override public void consume(Throwable error) { fulfilled.setError(error); } }); return this; } private void addHandlers(@NotNull Consumer<T> done, @NotNull Consumer<Throwable> rejected) { this.done = setHandler(this.done, done); this.rejected = setHandler(this.rejected, rejected); } @NotNull private static <T> Consumer<T> setHandler(@Nullable Consumer<T> oldConsumer, @NotNull Consumer<T> newConsumer) { if (oldConsumer == null) { return newConsumer; } else if (oldConsumer instanceof CompoundConsumer) { ((CompoundConsumer<T>)oldConsumer).add(newConsumer); return oldConsumer; } else { return new CompoundConsumer<T>(oldConsumer, newConsumer); } } public void setResult(T result) { if (state != State.PENDING) { return; } this.result = result; state = State.FULFILLED; Consumer<T> done = this.done; clearHandlers(); if (done != null && !isObsolete(done)) { done.consume(result); } } static boolean isObsolete(@Nullable Consumer<?> consumer) { return consumer instanceof Obsolescent && ((Obsolescent)consumer).isObsolete(); } public boolean setError(@NotNull String error) { return setError(Promise.createError(error)); } public boolean setError(@NotNull Throwable error) { if (state != State.PENDING) { return false; } result = error; state = State.REJECTED; Consumer<Throwable> rejected = this.rejected; clearHandlers(); if (rejected != null) { if (!isObsolete(rejected)) { rejected.consume(error); } } else { Promise.logError(LOG, error); } return true; } private void clearHandlers() { done = null; rejected = null; } @Override public Promise<T> processed(@NotNull final Consumer<T> processed) { done(processed); rejected(new Consumer<Throwable>() { @Override public void consume(Throwable error) { processed.consume(null); } }); return this; } }