// 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 java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.chromium.sdk.DebugEventListener.VmStatusListener;
import org.chromium.sdk.RelayOk;
import org.chromium.sdk.SyncCallback;
import org.chromium.sdk.TabDebugEventListener;
import org.chromium.sdk.internal.BaseCommandProcessor;
import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
import org.chromium.sdk.internal.websocket.WsConnection;
import org.chromium.sdk.internal.wip.protocol.BasicConstants;
import org.chromium.sdk.internal.wip.protocol.WipParserAccess;
import org.chromium.sdk.internal.wip.protocol.input.WipCommandResponse;
import org.chromium.sdk.internal.wip.protocol.input.WipCommandResponse.Success;
import org.chromium.sdk.internal.wip.protocol.input.WipEvent;
import org.chromium.sdk.internal.wip.protocol.input.WipEventType;
import org.chromium.sdk.internal.wip.protocol.input.debugger.BreakpointResolvedEventData;
import org.chromium.sdk.internal.wip.protocol.input.debugger.PausedEventData;
import org.chromium.sdk.internal.wip.protocol.input.debugger.ResumedEventData;
import org.chromium.sdk.internal.wip.protocol.input.debugger.ScriptParsedEventData;
import org.chromium.sdk.internal.wip.protocol.input.page.FrameDetachedEventData;
import org.chromium.sdk.internal.wip.protocol.input.page.FrameNavigatedEventData;
import org.chromium.sdk.internal.wip.protocol.output.WipParams;
import org.chromium.sdk.internal.wip.protocol.output.WipParamsWithResponse;
import org.chromium.sdk.internal.wip.protocol.output.WipRequest;
import org.chromium.sdk.util.GenericCallback;
import org.json.simple.JSONObject;
/**
* Responsible for the basic processing and dispatching all incoming and outgoing messages.
*/
class WipCommandProcessor {
private static final Logger LOGGER = Logger.getLogger(WipCommandProcessor.class.getName());
private final WipTabImpl tabImpl;
private final BaseCommandProcessor<Integer, JSONObject, JSONObject, WipCommandResponse>
baseProcessor;
private final AtomicInteger currentSeq = new AtomicInteger(0);
WipCommandProcessor(WipTabImpl tabImpl, WsConnection wsSocket) {
this.tabImpl = tabImpl;
WipMessageTypeHandler handler = new WipMessageTypeHandler();
baseProcessor =
new BaseCommandProcessor<Integer, JSONObject, JSONObject, WipCommandResponse>(handler);
}
RelayOk sendRaw(JSONObject message, WipCommandCallback callback, SyncCallback syncCallback) {
return baseProcessor.send(message, false, callback, syncCallback);
}
RelayOk send(WipParams params, WipCommandCallback callback, SyncCallback syncCallback) {
WipRequest request = new WipRequest(params);
return sendRaw(request, callback, syncCallback);
}
/**
* @param <RESPONSE> type of response expected that is determined by params
* @param params request parameters that also holds a method name
* @param callback a callback that accepts method-specific response or null
* @param syncCallback may be null
*/
<RESPONSE> RelayOk send(final WipParamsWithResponse<RESPONSE> params,
final GenericCallback<RESPONSE> callback, SyncCallback syncCallback) {
WipRequest request = new WipRequest(params);
WipCommandCallback commandCallback;
if (callback == null) {
commandCallback = null;
} else {
commandCallback = new WipCommandCallback.Default() {
@Override
protected void onSuccess(Success success) {
RESPONSE response;
try {
response = params.parseResponse(success.data(), WipParserAccess.get());
} catch (JsonProtocolParseException e) {
throw new RuntimeException(e);
}
callback.success(response);
}
@Override
protected void onError(String message) {
callback.failure(new Exception(message));
}
};
}
return sendRaw(request, commandCallback, syncCallback);
}
void acceptResponse(JSONObject message) {
baseProcessor.processIncoming(message);
}
void processEos() {
baseProcessor.processEos();
}
private void processEvent(JSONObject jsonObject) {
WipEvent event;
try {
event = WipParserAccess.get().parseWipEvent(jsonObject);
} catch (JsonProtocolParseException e) {
LOGGER.log(Level.SEVERE, "Failed to parse event", e);
return;
}
EVENT_MAP.handleEvent(event, this);
}
/**
* Handles all operations specific to Wip messages.
*/
private class WipMessageTypeHandler implements
BaseCommandProcessor.Handler<Integer, JSONObject, JSONObject, WipCommandResponse> {
@Override
public Integer getUpdatedSeq(JSONObject message) {
Integer seq = currentSeq.addAndGet(1);
message.put(BasicConstants.Property.ID, seq);
return seq;
}
@Override
public String getCommandName(JSONObject message) {
return (String) message.get(BasicConstants.Property.METHOD);
}
@Override
public void send(JSONObject message, boolean isImmediate) {
try {
WipCommandProcessor.this.tabImpl.getWsSocket().sendTextualMessage(
message.toJSONString());
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to send", e);
}
}
@Override
public WipCommandResponse parseWithSeq(JSONObject incoming) {
if (!incoming.containsKey(BasicConstants.Property.ID)) {
return null;
}
try {
return WipParserAccess.get().parseWipCommandResponse(incoming);
} catch (JsonProtocolParseException e) {
throw new RuntimeException("Failed to parse response", e);
}
}
@Override
public Integer getSeq(WipCommandResponse incomingWithSeq) {
Object seqObject = incomingWithSeq.id();
if (seqObject == null) {
return null;
}
Number number = (Number) seqObject;
return number.intValue();
}
@Override
public void acceptNonSeq(JSONObject incoming) {
processEvent(incoming);
}
@Override
public void reportVmStatus(String currentRequest, int numberOfEnqueued) {
TabDebugEventListener tabEventListener = tabImpl.getDebugListener();
VmStatusListener vmStatusListener =
tabEventListener.getDebugEventListener().getVmStatusListener();
if (vmStatusListener != null) {
vmStatusListener.busyStatusChanged(currentRequest, numberOfEnqueued);
}
}
}
private abstract static class EventHandler<T> {
abstract void accept(T eventData, WipCommandProcessor commandProcessor);
}
private static final EventMap EVENT_MAP;
static {
EVENT_MAP = new EventMap();
EVENT_MAP.add(PausedEventData.TYPE, new EventHandler<PausedEventData>() {
@Override
void accept(PausedEventData data, WipCommandProcessor commandProcessor) {
commandProcessor.tabImpl.getContextBuilder().createContext(data);
}
});
EVENT_MAP.add(ResumedEventData.TYPE, new EventHandler<ResumedEventData>() {
@Override
void accept(ResumedEventData event, WipCommandProcessor commandProcessor) {
commandProcessor.tabImpl.getContextBuilder().onResumeReportedFromRemote(event);
}
});
EVENT_MAP.add(ScriptParsedEventData.TYPE, new EventHandler<ScriptParsedEventData> () {
@Override
void accept(ScriptParsedEventData eventData,
WipCommandProcessor commandProcessor) {
commandProcessor.tabImpl.getScriptManager().scriptIsReportedParsed(eventData);
}
});
EVENT_MAP.add(BreakpointResolvedEventData.TYPE,
new EventHandler<BreakpointResolvedEventData> () {
@Override
void accept(BreakpointResolvedEventData eventData,
WipCommandProcessor commandProcessor) {
commandProcessor.tabImpl.getBreakpointManager().breakpointReportedResolved(eventData);
}
});
EVENT_MAP.add(FrameNavigatedEventData.TYPE, new EventHandler<FrameNavigatedEventData> () {
@Override
void accept(FrameNavigatedEventData eventData,
WipCommandProcessor commandProcessor) {
commandProcessor.tabImpl.getFrameManager().frameNavigated(eventData);
}
});
EVENT_MAP.add(FrameDetachedEventData.TYPE, null);
}
public RelayOk runInDispatchThread(Runnable runnable, SyncCallback syncCallback) {
return this.tabImpl.getWsSocket().runInDispatchThread(runnable, syncCallback);
}
private static class EventMap {
private final Map<String, InternalHandler<?>> map = new HashMap<String, InternalHandler<?>>();
public <T> void add(WipEventType<T> type, EventHandler<T> eventHandler) {
InternalHandler<T> internalHandler = new InternalHandler<T>(eventHandler, type);
map.put(type.getMethodName(), internalHandler);
}
public void handleEvent(WipEvent event, WipCommandProcessor commandProcessor) {
String method = event.method();
InternalHandler<?> parser = map.get(method);
if (parser == null) {
LOGGER.log(Level.INFO, "Unsupported event: " + method);
return;
}
parser.handle(event, commandProcessor);
}
private static class InternalHandler<T> {
private final EventHandler<T> handler;
private final WipEventType<T> type;
InternalHandler(EventHandler<T> handler, WipEventType<T> type) {
this.handler = handler;
this.type = type;
}
public void handle(WipEvent event, WipCommandProcessor commandProcessor) {
if (handler == null) {
return;
}
WipEvent.Data genericData = event.data();
T data;
if (genericData == null) {
data = null;
} else {
try {
data = type.parse(WipParserAccess.get(), genericData.getUnderlyingObject());
} catch (JsonProtocolParseException e) {
throw new RuntimeException(e);
}
}
handler.accept(data, commandProcessor);
}
}
}
}