// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.sdk.internal.wip; import org.chromium.sdk.RelayOk; import org.chromium.sdk.SyncCallback; import org.chromium.sdk.internal.wip.protocol.input.WipCommandResponse; import org.chromium.sdk.internal.wip.protocol.output.WipParams; import org.chromium.sdk.internal.wip.protocol.output.WipParamsWithResponse; import org.chromium.sdk.util.GenericCallback; import org.chromium.sdk.util.RelaySyncCallback; /** * A utility class that helps running a chain of asynchronous commands in a safe manner. * 'Safe' here means that the client will get {@link SyncCallback} called in the end * in any scenario. * <p> * The class helps reformat control sequence: instead of being callback-driven, the program * becomes step-driven. Each step defines a request being sent, the way a response is processed * and a link to the next step. * <p> * The class is bound to {@link WipCommandProcessor}. */ public class WipRelayRunner { /** * The main abstraction of {@link WipRelayRunner}. A particular relay consists is a chain * of steps. Each step returns a next step except for the 'final' step * (see {@link WipRelayRunner#createFinalStep(Object)}). * <p>This type is essentially an algebraic data type. * * @param <RES> a type that the entire relay should return */ interface Step<RES> { <R> R accept(Visitor<R, RES> visitor); interface Visitor<R, RES> { R visitFinal(RES finalResult); R visitSend(SendStepSimple<RES> sendStep); <DATA> R visitSend(SendStepWithResponse<DATA, RES> sendStep); } } /** * Creates a final step that simply holds a result. */ public static <RES> Step<RES> createFinalStep(final RES finalResult) { return new Step<RES>() { @Override public <R> R accept(Visitor<R, RES> visitor) { return visitor.visitFinal(finalResult); } }; } /** * A base class for 'send' step that sends a request and processes its response. * @param <RES> a type that the entire relay should return */ public static abstract class SendStepSimple<RES> implements Step<RES> { public abstract WipParams getParams(); /** * Handles normal response and returns a next step (or throws {@link ProcessException}). * The response itself contains no data, so there's no such parameter. */ public abstract Step<RES> processResponse() throws ProcessException; /** * Optionally wraps the cause with a more high-level exception. Must return cause by default. * @return not null */ public abstract Exception processFailure(Exception cause); @Override public final <R> R accept(Visitor<R, RES> visitor) { return visitor.visitSend(this); } } public static abstract class SendStepWithResponse<DATA, RES> implements Step<RES> { public abstract WipParamsWithResponse<DATA> getParams(); /** * Handles normal response and returns a next step (or throws {@link ProcessException}). */ public abstract Step<RES> processResponse(DATA response) throws ProcessException; /** * Optionally wraps the cause with a more high-level exception. Must return cause by default. * @return not null */ public abstract Exception processFailure(Exception cause); @Override public final <R> R accept(Visitor<R, RES> visitor) { return visitor.visitSend(this); } } /** * An exception that step can throw if response cannot be processed normally. It aborts the relay * and gets passed to user callback. */ public static class ProcessException extends Exception { public ProcessException() { } public ProcessException(String message, Throwable cause) { super(message, cause); } public ProcessException(String message) { super(message); } public ProcessException(Throwable cause) { super(cause); } } /** * Runs a relay defined by a chain of steps. * @param <RES> return type of the entire relay * @param firstStep a first step that defines the entire relay * @param callback * @param relaySyncCallback a {@link SyncCallback} wrapped as {@link RelaySyncCallback} */ public static <RES> RelayOk run(final WipCommandProcessor commandProcessor, Step<RES> firstStep, final GenericCallback<RES> callback, final RelaySyncCallback relaySyncCallback) { return firstStep.accept(new Step.Visitor<RelayOk, RES>() { @Override public RelayOk visitFinal(RES finalResult) { if (callback != null) { callback.success(finalResult); } return relaySyncCallback.finish(); } @Override public RelayOk visitSend(final SendStepSimple<RES> sendStep) { final RelaySyncCallback.Guard guard = relaySyncCallback.newGuard(); WipCommandCallback sendCallback = new WipCommandCallback() { @Override public void messageReceived(WipCommandResponse response) { Step<RES> processResult; try { processResult = sendStep.processResponse(); } catch (ProcessException e) { if (callback != null) { callback.failure(e); } // Todo: consider throwing e. return; } RelayOk relayOk = run(commandProcessor, processResult, callback, guard.getRelay()); guard.discharge(relayOk); } @Override public void failure(String message) { if (callback != null) { callback.failure(sendStep.processFailure(new Exception(message))); } } }; return commandProcessor.send(sendStep.getParams(), sendCallback, guard.asSyncCallback()); } @Override public <RESPONSE> RelayOk visitSend(final SendStepWithResponse<RESPONSE, RES> sendStep) { final RelaySyncCallback.Guard guard = relaySyncCallback.newGuard(); GenericCallback<RESPONSE> sendCallback = new GenericCallback<RESPONSE>() { @Override public void success(RESPONSE response) { Step<RES> processResult; try { processResult = sendStep.processResponse(response); } catch (ProcessException e) { if (callback != null) { callback.failure(e); } // Todo: consider throwing e. return; } RelayOk relayOk = run(commandProcessor, processResult, callback, guard.getRelay()); guard.discharge(relayOk); } @Override public void failure(Exception exception) { if (callback != null) { callback.failure(sendStep.processFailure(exception)); } } }; return commandProcessor.send(sendStep.getParams(), sendCallback, guard.asSyncCallback()); } }); } }