2
|
1 |
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
|
2 |
// Use of this source code is governed by a BSD-style license that can be
|
|
3 |
// found in the LICENSE file.
|
|
4 |
|
|
5 |
package org.chromium.sdk.internal.tools.v8;
|
|
6 |
|
|
7 |
import java.util.Collection;
|
|
8 |
import java.util.logging.Level;
|
|
9 |
import java.util.logging.Logger;
|
|
10 |
|
|
11 |
import org.chromium.sdk.SyncCallback;
|
|
12 |
import org.chromium.sdk.internal.CloseableMap;
|
|
13 |
import org.chromium.sdk.internal.protocol.CommandResponse;
|
|
14 |
import org.chromium.sdk.internal.protocol.IncomingMessage;
|
|
15 |
import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
|
|
16 |
import org.chromium.sdk.internal.tools.v8.request.DebuggerMessage;
|
|
17 |
import org.json.simple.JSONObject;
|
|
18 |
|
|
19 |
/**
|
|
20 |
* Sends JSON commands to V8 VM and handles responses. Command is sent
|
|
21 |
* via {@code V8CommandOutput}. Response is passed back to callback if it was provided.
|
|
22 |
* Also all responses and events are dispatched to group of dedicated processors.
|
|
23 |
*/
|
|
24 |
public class V8CommandProcessor implements V8CommandSender<DebuggerMessage, RuntimeException> {
|
|
25 |
|
|
26 |
/**
|
|
27 |
* A callback to handle V8 debugger responses.
|
|
28 |
*/
|
|
29 |
public interface V8HandlerCallback {
|
|
30 |
/**
|
|
31 |
* This method is invoked when a debugger command result has become
|
|
32 |
* available.
|
|
33 |
*
|
|
34 |
* @param response from the V8 debugger
|
|
35 |
*/
|
|
36 |
void messageReceived(CommandResponse response);
|
|
37 |
|
|
38 |
/**
|
|
39 |
* This method is invoked when a debugger command has failed.
|
|
40 |
*
|
|
41 |
* @param message containing the failure reason
|
|
42 |
*/
|
|
43 |
void failure(String message);
|
|
44 |
|
|
45 |
/** A no-op callback implementation. */
|
|
46 |
V8HandlerCallback NULL_CALLBACK = new V8HandlerCallback() {
|
|
47 |
public void failure(String message) {
|
|
48 |
}
|
|
49 |
|
|
50 |
public void messageReceived(CommandResponse response) {
|
|
51 |
}
|
|
52 |
};
|
|
53 |
}
|
|
54 |
|
|
55 |
/** The class logger. */
|
|
56 |
static final Logger LOGGER = Logger.getLogger(V8CommandProcessor.class.getName());
|
|
57 |
|
|
58 |
private final CloseableMap<Integer, CallbackEntry> callbackMap = CloseableMap.newLinkedMap();
|
|
59 |
|
|
60 |
private final V8CommandOutput messageOutput;
|
|
61 |
|
|
62 |
private final DefaultResponseHandler defaultResponseHandler;
|
|
63 |
|
|
64 |
|
|
65 |
public V8CommandProcessor(V8CommandOutput messageOutput,
|
|
66 |
DefaultResponseHandler defaultResponseHandler) {
|
|
67 |
this.messageOutput = messageOutput;
|
|
68 |
this.defaultResponseHandler = defaultResponseHandler;
|
|
69 |
}
|
|
70 |
|
|
71 |
public void sendV8CommandAsync(DebuggerMessage message, boolean isImmediate,
|
|
72 |
V8HandlerCallback v8HandlerCallback, SyncCallback syncCallback) {
|
|
73 |
|
|
74 |
if (v8HandlerCallback != null) {
|
|
75 |
// TODO(peter.rybin): should we handle IllegalStateException better than rethrowing it?
|
|
76 |
try {
|
|
77 |
callbackMap.put(message.getSeq(), new CallbackEntry(v8HandlerCallback,
|
|
78 |
syncCallback));
|
|
79 |
} catch (IllegalStateException e) {
|
|
80 |
throw new IllegalStateException("Connection is closed", e);
|
|
81 |
}
|
|
82 |
}
|
|
83 |
try {
|
|
84 |
messageOutput.send(message, isImmediate);
|
|
85 |
} catch (RuntimeException e) {
|
|
86 |
if (v8HandlerCallback != null) {
|
|
87 |
callbackMap.remove(message.getSeq());
|
|
88 |
}
|
|
89 |
throw e;
|
|
90 |
}
|
|
91 |
}
|
|
92 |
|
|
93 |
public void processIncomingJson(final JSONObject v8Json) {
|
|
94 |
IncomingMessage response;
|
|
95 |
try {
|
|
96 |
response = V8ProtocolUtil.getV8Parser().parse(v8Json, IncomingMessage.class);
|
|
97 |
} catch (JsonProtocolParseException e) {
|
|
98 |
LOGGER.log(Level.SEVERE, "JSON message does not conform to the protocol", e);
|
|
99 |
return;
|
|
100 |
}
|
|
101 |
|
|
102 |
final CommandResponse commandResponse = response.asCommandResponse();
|
|
103 |
|
|
104 |
if (commandResponse != null) {
|
|
105 |
int requestSeqInt = (int) commandResponse.getRequestSeq();
|
|
106 |
CallbackEntry callbackEntry = callbackMap.removeIfContains(requestSeqInt);
|
|
107 |
if (callbackEntry != null) {
|
|
108 |
LOGGER.log(
|
|
109 |
Level.INFO,
|
|
110 |
"Request-response roundtrip: {0}ms",
|
|
111 |
getCurrentMillis() - callbackEntry.commitMillis);
|
|
112 |
|
|
113 |
CallbackCaller caller = new CallbackCaller() {
|
|
114 |
@Override
|
|
115 |
void call(V8HandlerCallback handlerCallback) {
|
|
116 |
handlerCallback.messageReceived(commandResponse);
|
|
117 |
}
|
|
118 |
};
|
|
119 |
try {
|
|
120 |
callThemBack(callbackEntry, caller, requestSeqInt);
|
|
121 |
} catch (RuntimeException e) {
|
|
122 |
LOGGER.log(Level.SEVERE, "Failed to dispatch response to callback", e);
|
|
123 |
}
|
|
124 |
}
|
|
125 |
}
|
|
126 |
|
|
127 |
defaultResponseHandler.handleResponseWithHandler(response);
|
|
128 |
}
|
|
129 |
|
|
130 |
public void processEos() {
|
|
131 |
// We should call them in the order they have been submitted.
|
|
132 |
Collection<CallbackEntry> entries = callbackMap.close().values();
|
|
133 |
for (CallbackEntry entry : entries) {
|
|
134 |
callThemBack(entry, failureCaller, -1);
|
|
135 |
}
|
|
136 |
}
|
|
137 |
|
|
138 |
private static abstract class CallbackCaller {
|
|
139 |
abstract void call(V8HandlerCallback handlerCallback);
|
|
140 |
}
|
|
141 |
|
|
142 |
private final static CallbackCaller failureCaller = new CallbackCaller() {
|
|
143 |
@Override
|
|
144 |
void call(V8HandlerCallback handlerCallback) {
|
|
145 |
handlerCallback.failure("Detach");
|
|
146 |
}
|
|
147 |
};
|
|
148 |
|
|
149 |
|
|
150 |
private void callThemBack(CallbackEntry callbackEntry, CallbackCaller callbackCaller,
|
|
151 |
int requestSeq) {
|
|
152 |
RuntimeException callbackException = null;
|
|
153 |
try {
|
|
154 |
if (callbackEntry.v8HandlerCallback != null) {
|
|
155 |
LOGGER.log(
|
|
156 |
Level.INFO, "Notified debugger command callback, request_seq={0}", requestSeq);
|
|
157 |
callbackCaller.call(callbackEntry.v8HandlerCallback);
|
|
158 |
}
|
|
159 |
} catch (RuntimeException e) {
|
|
160 |
callbackException = e;
|
|
161 |
throw e;
|
|
162 |
} finally {
|
|
163 |
if (callbackEntry.syncCallback != null) {
|
|
164 |
callbackEntry.syncCallback.callbackDone(callbackException);
|
|
165 |
}
|
|
166 |
}
|
|
167 |
}
|
|
168 |
|
|
169 |
/**
|
|
170 |
* @return milliseconds since the epoch
|
|
171 |
*/
|
|
172 |
private static long getCurrentMillis() {
|
|
173 |
return System.currentTimeMillis();
|
|
174 |
}
|
|
175 |
|
|
176 |
static void checkNull(Object object, String message) {
|
|
177 |
if (object == null) {
|
|
178 |
throw new IllegalArgumentException(message);
|
|
179 |
}
|
|
180 |
}
|
|
181 |
|
|
182 |
|
|
183 |
private static class CallbackEntry {
|
|
184 |
final V8HandlerCallback v8HandlerCallback;
|
|
185 |
|
|
186 |
final SyncCallback syncCallback;
|
|
187 |
|
|
188 |
final long commitMillis;
|
|
189 |
|
|
190 |
CallbackEntry(V8HandlerCallback v8HandlerCallback, SyncCallback syncCallback) {
|
|
191 |
this.v8HandlerCallback = v8HandlerCallback;
|
|
192 |
this.commitMillis = getCurrentMillis();
|
|
193 |
this.syncCallback = syncCallback;
|
|
194 |
}
|
|
195 |
}
|
|
196 |
|
|
197 |
public void removeAllCallbacks() {
|
|
198 |
// TODO(peter.rybin): get rid of this method
|
|
199 |
}
|
|
200 |
}
|