--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8CommandProcessor.java Wed Dec 23 17:13:18 2009 -0800
@@ -0,0 +1,200 @@
+// Copyright (c) 2009 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.tools.v8;
+
+import java.util.Collection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.chromium.sdk.SyncCallback;
+import org.chromium.sdk.internal.CloseableMap;
+import org.chromium.sdk.internal.protocol.CommandResponse;
+import org.chromium.sdk.internal.protocol.IncomingMessage;
+import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
+import org.chromium.sdk.internal.tools.v8.request.DebuggerMessage;
+import org.json.simple.JSONObject;
+
+/**
+ * Sends JSON commands to V8 VM and handles responses. Command is sent
+ * via {@code V8CommandOutput}. Response is passed back to callback if it was provided.
+ * Also all responses and events are dispatched to group of dedicated processors.
+ */
+public class V8CommandProcessor implements V8CommandSender<DebuggerMessage, RuntimeException> {
+
+ /**
+ * A callback to handle V8 debugger responses.
+ */
+ public interface V8HandlerCallback {
+ /**
+ * This method is invoked when a debugger command result has become
+ * available.
+ *
+ * @param response from the V8 debugger
+ */
+ void messageReceived(CommandResponse response);
+
+ /**
+ * This method is invoked when a debugger command has failed.
+ *
+ * @param message containing the failure reason
+ */
+ void failure(String message);
+
+ /** A no-op callback implementation. */
+ V8HandlerCallback NULL_CALLBACK = new V8HandlerCallback() {
+ public void failure(String message) {
+ }
+
+ public void messageReceived(CommandResponse response) {
+ }
+ };
+ }
+
+ /** The class logger. */
+ static final Logger LOGGER = Logger.getLogger(V8CommandProcessor.class.getName());
+
+ private final CloseableMap<Integer, CallbackEntry> callbackMap = CloseableMap.newLinkedMap();
+
+ private final V8CommandOutput messageOutput;
+
+ private final DefaultResponseHandler defaultResponseHandler;
+
+
+ public V8CommandProcessor(V8CommandOutput messageOutput,
+ DefaultResponseHandler defaultResponseHandler) {
+ this.messageOutput = messageOutput;
+ this.defaultResponseHandler = defaultResponseHandler;
+ }
+
+ public void sendV8CommandAsync(DebuggerMessage message, boolean isImmediate,
+ V8HandlerCallback v8HandlerCallback, SyncCallback syncCallback) {
+
+ if (v8HandlerCallback != null) {
+ // TODO(peter.rybin): should we handle IllegalStateException better than rethrowing it?
+ try {
+ callbackMap.put(message.getSeq(), new CallbackEntry(v8HandlerCallback,
+ syncCallback));
+ } catch (IllegalStateException e) {
+ throw new IllegalStateException("Connection is closed", e);
+ }
+ }
+ try {
+ messageOutput.send(message, isImmediate);
+ } catch (RuntimeException e) {
+ if (v8HandlerCallback != null) {
+ callbackMap.remove(message.getSeq());
+ }
+ throw e;
+ }
+ }
+
+ public void processIncomingJson(final JSONObject v8Json) {
+ IncomingMessage response;
+ try {
+ response = V8ProtocolUtil.getV8Parser().parse(v8Json, IncomingMessage.class);
+ } catch (JsonProtocolParseException e) {
+ LOGGER.log(Level.SEVERE, "JSON message does not conform to the protocol", e);
+ return;
+ }
+
+ final CommandResponse commandResponse = response.asCommandResponse();
+
+ if (commandResponse != null) {
+ int requestSeqInt = (int) commandResponse.getRequestSeq();
+ CallbackEntry callbackEntry = callbackMap.removeIfContains(requestSeqInt);
+ if (callbackEntry != null) {
+ LOGGER.log(
+ Level.INFO,
+ "Request-response roundtrip: {0}ms",
+ getCurrentMillis() - callbackEntry.commitMillis);
+
+ CallbackCaller caller = new CallbackCaller() {
+ @Override
+ void call(V8HandlerCallback handlerCallback) {
+ handlerCallback.messageReceived(commandResponse);
+ }
+ };
+ try {
+ callThemBack(callbackEntry, caller, requestSeqInt);
+ } catch (RuntimeException e) {
+ LOGGER.log(Level.SEVERE, "Failed to dispatch response to callback", e);
+ }
+ }
+ }
+
+ defaultResponseHandler.handleResponseWithHandler(response);
+ }
+
+ public void processEos() {
+ // We should call them in the order they have been submitted.
+ Collection<CallbackEntry> entries = callbackMap.close().values();
+ for (CallbackEntry entry : entries) {
+ callThemBack(entry, failureCaller, -1);
+ }
+ }
+
+ private static abstract class CallbackCaller {
+ abstract void call(V8HandlerCallback handlerCallback);
+ }
+
+ private final static CallbackCaller failureCaller = new CallbackCaller() {
+ @Override
+ void call(V8HandlerCallback handlerCallback) {
+ handlerCallback.failure("Detach");
+ }
+ };
+
+
+ private void callThemBack(CallbackEntry callbackEntry, CallbackCaller callbackCaller,
+ int requestSeq) {
+ RuntimeException callbackException = null;
+ try {
+ if (callbackEntry.v8HandlerCallback != null) {
+ LOGGER.log(
+ Level.INFO, "Notified debugger command callback, request_seq={0}", requestSeq);
+ callbackCaller.call(callbackEntry.v8HandlerCallback);
+ }
+ } catch (RuntimeException e) {
+ callbackException = e;
+ throw e;
+ } finally {
+ if (callbackEntry.syncCallback != null) {
+ callbackEntry.syncCallback.callbackDone(callbackException);
+ }
+ }
+ }
+
+ /**
+ * @return milliseconds since the epoch
+ */
+ private static long getCurrentMillis() {
+ return System.currentTimeMillis();
+ }
+
+ static void checkNull(Object object, String message) {
+ if (object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+
+ private static class CallbackEntry {
+ final V8HandlerCallback v8HandlerCallback;
+
+ final SyncCallback syncCallback;
+
+ final long commitMillis;
+
+ CallbackEntry(V8HandlerCallback v8HandlerCallback, SyncCallback syncCallback) {
+ this.v8HandlerCallback = v8HandlerCallback;
+ this.commitMillis = getCurrentMillis();
+ this.syncCallback = syncCallback;
+ }
+ }
+
+ public void removeAllCallbacks() {
+ // TODO(peter.rybin): get rid of this method
+ }
+}