|
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 } |