package org.tessell.dispatch.client; import java.util.ArrayList; import java.util.List; import org.tessell.dispatch.shared.Action; import org.tessell.dispatch.shared.Result; import com.google.gwt.user.client.rpc.AsyncCallback; /** A stub implementation of {@link DispatchAsync}. */ public class StubDispatchAsync implements DispatchAsync { private final List<StubAsyncCallback<?, ?>> calls = new ArrayList<StubAsyncCallback<?, ?>>(); @Override public <A extends Action<R>, R extends Result> void execute(final A action, final AsyncCallback<R> callback) { calls.add(new StubAsyncCallback<A, R>(action, callback)); } /** @return all calls for assertions */ public List<StubAsyncCallback<?, ?>> getCalls() { return calls; } /** @return all actions for assertions */ public List<Action<?>> getActions() { final List<Action<?>> actions = new ArrayList<Action<?>>(); for (final StubAsyncCallback<?, ?> call : calls) { actions.add(call.action); } return actions; } /** @return all calls for {@link actionType} for assertions */ @SuppressWarnings("unchecked") public <A extends Action<R>, R extends Result> List<StubAsyncCallback<A, R>> getCalls(final Class<A> actionType) { final List<StubAsyncCallback<A, R>> matches = new ArrayList<StubAsyncCallback<A, R>>(); for (final StubAsyncCallback<?, ?> call : calls) { if (actionType.equals(call.action.getClass())) { matches.add((StubAsyncCallback<A, R>) call); } } return matches; } /** @return the {@link AsyncCallback} for {@code actionType} {@code index} */ public <A extends Action<R>, R extends Result> StubAsyncCallback<A, R> getCallback(final Class<A> actionType, final int index) { return getCalls(actionType).get(index); } /** @return the last {@link AsyncCallback} for {@code actionType} */ public <A extends Action<R>, R extends Result> StubAsyncCallback<A, R> getCallback(final Class<A> actionType) { final List<StubAsyncCallback<A, R>> calls = getCalls(actionType); if (calls.size() == 0) { throw new IllegalStateException("No calls for " + actionType); } return calls.get(calls.size() - 1); } /** @return the {@link Action} for {@code actionType} {@code index} */ public <A extends Action<R>, R extends Result> A getAction(final Class<A> actionType, final int index) { return getCalls(actionType).get(index).action; } /** @return the list {@link Action} for {@code actionType} */ public <A extends Action<R>, R extends Result> A getAction(final Class<A> actionType) { final List<StubAsyncCallback<A, R>> calls = getCalls(actionType); if (calls.size() == 0) { throw new IllegalStateException("No calls have been made for " + actionType); } return calls.get(calls.size() - 1).action; } /** @return all {@link Action}s for {@code actionType} */ public <A extends Action<R>, R extends Result> List<A> getActions(final Class<A> actionType) { final List<A> actions = new ArrayList<A>(); for (final StubAsyncCallback<A, R> call : getCalls(actionType)) { actions.add(call.action); } return actions; } /** A DTO to track the calls the stub has been asked to make. */ public class StubAsyncCallback<A extends Action<R>, R extends Result> implements AsyncCallback<R> { public boolean outstanding = true; public final A action; private final AsyncCallback<R> callback; private StubAsyncCallback(A action, AsyncCallback<R> callback) { this.callback = callback; this.action = action; } @Override public void onSuccess(final R result) { ensureOutstanding(); callback.onSuccess(result); } @Override public void onFailure(final Throwable caught) { ensureOutstanding(); callback.onFailure(caught); } /** For simulating out-of-order results. */ public void onSuccessOutOfOrder(final R result) { outstanding = false; callback.onSuccess(result); } /** For simulating out-of-order results. */ public void onFailureOutOfOrder(final Throwable caught) { outstanding = false; callback.onFailure(caught); } private void ensureOutstanding() { if (!outstanding) { throw new IllegalStateException(action + " has already been called back"); } for (final StubAsyncCallback<?, ?> call : calls) { if (call == this) { break; } if (call.outstanding) { throw new IllegalStateException("Call for " + action + " cannot return before call for " + call.action); } } outstanding = false; } } }