// 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());
}
});
}
}