org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8CommandProcessor.java
changeset 2 e4420d2515f1
--- /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
+  }
+}