| // Copyright (C) 2013 The Android Open Source Project |
| // |
| // 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.gerrit.client.rpc; |
| |
| import com.google.gwt.user.client.rpc.AsyncCallback; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Class for grouping together callbacks and calling them in order. |
| * <p> |
| * Callbacks are added to the group with {@link #add(AsyncCallback)}, which |
| * returns a wrapped callback suitable for passing to an asynchronous RPC call. |
| * The last callback must be added using {@link #addFinal(AsyncCallback)} or |
| * {@link #done()} must be invoked. |
| * |
| * The enclosing group buffers returned results and ensures that |
| * {@code onSuccess} is called exactly once for each callback in the group, in |
| * the same order that callbacks were added. This allows callers to, for |
| * example, use a {@link ScreenLoadCallback} as the last callback in the list |
| * and only display the screen once all callbacks have succeeded. |
| * <p> |
| * In the event of a failure, the <em>first</em> caught exception is sent to |
| * <em>all</em> callbacks' {@code onFailure} methods, in order; subsequent |
| * successes or failures are all ignored. Note that this means |
| * {@code onFailure} may be called with an exception unrelated to the callback |
| * processing it. |
| */ |
| public class CallbackGroup { |
| private final List<CallbackImpl<?>> callbacks; |
| private final Set<CallbackImpl<?>> remaining; |
| private boolean finalAdded; |
| |
| private boolean failed; |
| private Throwable failedThrowable; |
| |
| public static <T> Callback<T> emptyCallback() { |
| return new Callback<T>() { |
| @Override |
| public void onSuccess(T result) { |
| } |
| |
| @Override |
| public void onFailure(Throwable err) { |
| } |
| }; |
| } |
| |
| public CallbackGroup() { |
| callbacks = new ArrayList<>(); |
| remaining = new HashSet<>(); |
| } |
| |
| public <T> Callback<T> add(final AsyncCallback<T> cb) { |
| checkFinalAdded(); |
| return handleAdd(cb); |
| } |
| |
| public <T> Callback<T> addFinal(final AsyncCallback<T> cb) { |
| checkFinalAdded(); |
| finalAdded = true; |
| return handleAdd(cb); |
| } |
| |
| public void done() { |
| finalAdded = true; |
| applyAllSuccess(); |
| } |
| |
| public void addListener(AsyncCallback<Void> cb) { |
| if (!failed && finalAdded && remaining.isEmpty()) { |
| cb.onSuccess(null); |
| } else { |
| handleAdd(cb).onSuccess(null); |
| } |
| } |
| |
| public void addListener(CallbackGroup group) { |
| addListener(group.add(CallbackGroup.<Void> emptyCallback())); |
| } |
| |
| private void applyAllSuccess() { |
| if (!failed && finalAdded && remaining.isEmpty()) { |
| for (CallbackImpl<?> cb : callbacks) { |
| cb.applySuccess(); |
| } |
| callbacks.clear(); |
| } |
| } |
| |
| private <T> Callback<T> handleAdd(AsyncCallback<T> cb) { |
| if (failed) { |
| cb.onFailure(failedThrowable); |
| return emptyCallback(); |
| } |
| |
| CallbackImpl<T> wrapper = new CallbackImpl<>(cb); |
| callbacks.add(wrapper); |
| remaining.add(wrapper); |
| return wrapper; |
| } |
| |
| private void checkFinalAdded() { |
| if (finalAdded) { |
| throw new IllegalStateException("final callback already added"); |
| } |
| } |
| |
| public interface Callback<T> |
| extends AsyncCallback<T>, com.google.gwtjsonrpc.common.AsyncCallback<T> { |
| } |
| |
| private class CallbackImpl<T> implements Callback<T> { |
| AsyncCallback<T> delegate; |
| T result; |
| |
| CallbackImpl(AsyncCallback<T> delegate) { |
| this.delegate = delegate; |
| } |
| |
| @Override |
| public void onSuccess(T value) { |
| if (failed) { |
| return; |
| } |
| |
| this.result = value; |
| remaining.remove(this); |
| CallbackGroup.this.applyAllSuccess(); |
| } |
| |
| @Override |
| public void onFailure(Throwable caught) { |
| if (failed) { |
| return; |
| } |
| |
| failed = true; |
| failedThrowable = caught; |
| for (CallbackImpl<?> cb : callbacks) { |
| cb.delegate.onFailure(failedThrowable); |
| cb.delegate = null; |
| cb.result = null; |
| } |
| callbacks.clear(); |
| remaining.clear(); |
| } |
| |
| void applySuccess() { |
| AsyncCallback<T> cb = delegate; |
| if (cb != null) { |
| delegate = null; |
| cb.onSuccess(result); |
| result = null; |
| } |
| } |
| } |
| } |