/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.debug.core.server;
import com.google.dart.tools.debug.core.DartDebugCorePlugin;
import com.google.dart.tools.debug.core.server.VmListener.PausedReason;
import com.google.dart.tools.debug.core.webkit.JsonUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* A low level interface to the Dart VM debugger protocol.
*/
public class VmConnection {
public static enum BreakOnExceptionsType {
all,
none,
unhandled
}
public static interface BreakpointResolvedCallback {
public void handleResolved(VmBreakpoint bp);
}
static interface Callback {
public void handleResult(JSONObject result) throws JSONException;
}
private static final String EVENT_ISOLATE = "isolate";
private static final String EVENT_PAUSED = "paused";
private static final String EVENT_BREAKPOINTRESOLVED = "breakpointResolved";
private static Charset UTF8 = Charset.forName("UTF-8");
private static ExecutorService threadPool = Executors.newCachedThreadPool();
private List<VmListener> listeners = new ArrayList<VmListener>();
private String host;
private int port;
private Map<Integer, Callback> callbackMap = new HashMap<Integer, Callback>();
private int nextCommandId = 1;
private Socket socket;
private OutputStream out;
private List<VmBreakpoint> breakpoints = Collections.synchronizedList(new ArrayList<VmBreakpoint>());
private Map<String, String> sourceCache = new HashMap<String, String>();
private Map<String, VmLineNumberTable> lineNumberTableCache = new HashMap<String, VmLineNumberTable>();
private Map<String, VmIsolate> isolateMap = new HashMap<String, VmIsolate>();
private VmLocation currentLocation;
private boolean isStepping;
private String stepCommand;
public VmConnection(String host, int port) {
this.host = host;
this.port = port;
}
public void addListener(VmListener listener) {
listeners.add(listener);
}
public void callToString(final VmValue object, final VmCallback<VmValue> callback)
throws IOException {
evaluateObject(object.getIsolate(), object, "toString()", callback);
}
public void close() throws IOException {
if (socket != null) {
socket.close();
socket = null;
}
}
/**
* Connect to the VM debug server.
*
* @throws IOException
*/
public void connect() throws IOException {
socket = new Socket(host, port);
out = socket.getOutputStream();
final InputStream in = socket.getInputStream();
// Start a reader thread.
new Thread(new Runnable() {
@Override
public void run() {
for (VmListener listener : listeners) {
listener.connectionOpened(VmConnection.this);
}
try {
processVmEvents(in);
} catch (EOFException e) {
} catch (SocketException se) {
// ignore java.net.SocketException: Connection reset
final String reset = "Connection reset";
if (!(se.getMessage() != null && se.getMessage().contains(reset))) {
DartDebugCorePlugin.logError(se);
}
} catch (IOException e) {
DartDebugCorePlugin.logError(e);
} finally {
socket = null;
}
for (VmListener listener : listeners) {
listener.connectionClosed(VmConnection.this);
}
handleTerminated();
}
}).start();
}
/**
* Enable stepping for all libraries (except for certain core ones).
*
* @throws IOException
*/
public void enableAllStepping(final VmIsolate isolate) throws IOException {
getLibraries(isolate, new VmCallback<List<VmLibraryRef>>() {
@Override
public void handleResult(VmResult<List<VmLibraryRef>> result) {
if (!result.isError()) {
for (VmLibraryRef ref : result.getResult()) {
if (!ref.isInternal() && !ref.isAsync()) {
try {
setLibraryProperties(isolate, ref.getId(), true);
} catch (IOException e) {
}
}
}
}
}
});
}
public void evaluateLibrary(final VmIsolate isolate, VmLibrary vmLibrary, String expression,
final VmCallback<VmValue> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "evaluateExpr");
request.put(
"params",
new JSONObject().put("libraryId", vmLibrary.getLibraryId()).put("expression", expression));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<VmValue> evalResult = convertEvaluateObjectResult(isolate, result);
callback.handleResult(evalResult);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void evaluateObject(final VmIsolate isolate, VmClass vmClass, String expression,
final VmCallback<VmValue> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "evaluateExpr");
request.put(
"params",
new JSONObject().put("classId", vmClass.getClassId()).put("expression", expression));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<VmValue> evalResult = convertEvaluateObjectResult(isolate, result);
callback.handleResult(evalResult);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void evaluateObject(final VmIsolate isolate, VmValue value, String expression,
final VmCallback<VmValue> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "evaluateExpr");
request.put(
"params",
new JSONObject().put("objectId", value.getObjectId()).put("expression", expression));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<VmValue> evalResult = convertEvaluateObjectResult(isolate, result);
callback.handleResult(evalResult);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void evaluateOnCallFrame(final VmIsolate isolate, VmCallFrame callFrame,
String expression, final VmCallback<VmValue> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "evaluateExpr");
request.put(
"params",
new JSONObject().put("frameId", callFrame.getFrameId()).put("expression", expression));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<VmValue> evalResult = convertEvaluateObjectResult(isolate, result);
callback.handleResult(evalResult);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public VmClass getClassInfoSync(VmIsolate isolate, int classId) {
if (!isolate.hasClassInfo(classId)) {
populateClassInfo(isolate, classId);
}
return isolate.getClassInfo(classId);
}
public VmClass getClassInfoSync(VmObject obj) {
if (obj.getClassId() == -1) {
return null;
}
return getClassInfoSync(obj.getIsolate(), obj.getClassId());
}
public String getClassNameSync(VmObject obj) {
VmClass vmClass = getClassInfoSync(obj);
if (vmClass == null) {
return "";
} else {
VmIsolate isolate = obj.getIsolate();
return isolate.getClassName(obj.getClassId());
}
}
public void getClassProperties(final VmIsolate isolate, final int classId,
final VmCallback<VmClass> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "getClassProperties");
request.put("params", new JSONObject().put("classId", classId));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<VmClass> vmClassResult = convertGetClassPropertiesResult(
isolate,
classId,
result);
callback.handleResult(vmClassResult);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void getGlobalVariables(final VmIsolate isolate, final int libraryId,
final VmCallback<List<VmVariable>> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "getGlobalVariables");
request.put("params", new JSONObject().put("libraryId", libraryId));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<List<VmVariable>> retValue = convertGetGlobalVariablesResult(isolate, result);
callback.handleResult(retValue);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void getIsolateIds(final VmCallback<List<Integer>> callback) throws IOException {
sendSimpleCommand("getIsolateIds", null, new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
callback.handleResult(convertGetIsolateIdsResult(result));
}
});
}
public void getLibraries(VmIsolate isolate, final VmCallback<List<VmLibraryRef>> callback)
throws IOException {
if (isolate.isPaused()) {
sendSimpleCommand("getLibraries", isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
callback.handleResult(convertGetLibrariesResult(result));
}
});
} else {
VmResult<List<VmLibraryRef>> result = new VmResult<List<VmLibraryRef>>();
result.setResult(new ArrayList<VmLibraryRef>());
callback.handleResult(result);
}
}
public void getLibraryProperties(final VmIsolate isolate, final int libraryId,
final VmCallback<VmLibrary> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "getLibraryProperties");
request.put("params", new JSONObject().put("libraryId", libraryId));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<VmLibrary> retValue = convertGetLibraryPropertiesResult(
isolate,
libraryId,
result);
callback.handleResult(retValue);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public VmLibrary getLibraryPropertiesSync(VmIsolate isolate, int libraryId) {
if (!isolate.hasLibraryInfo(libraryId)) {
populateLibraryInfo(isolate, libraryId);
}
return isolate.getLibraryInfo(libraryId);
}
public VmLibrary getLibraryPropertiesSync(VmObject obj) {
VmClass vmClass = getClassInfoSync(obj);
if (vmClass == null) {
return null;
}
return getLibraryPropertiesSync(obj.getIsolate(), vmClass.getLibraryId());
}
public int getLineNumberFromLocation(VmIsolate isolate, VmLocation location) {
String cacheKey = location.getLibraryId() + ":" + location.getUrl();
if (!lineNumberTableCache.containsKey(cacheKey)) {
final CountDownLatch latch = new CountDownLatch(1);
final VmLineNumberTable[] result = new VmLineNumberTable[1];
try {
getLineNumberTable(
isolate,
location.getLibraryId(),
location.getUrl(),
new VmCallback<VmLineNumberTable>() {
@Override
public void handleResult(VmResult<VmLineNumberTable> r) {
result[0] = r.getResult();
latch.countDown();
}
});
} catch (IOException ex) {
latch.countDown();
}
try {
latch.await();
} catch (InterruptedException e) {
}
lineNumberTableCache.put(cacheKey, result[0]);
}
VmLineNumberTable lineNumberTable = lineNumberTableCache.get(cacheKey);
if (lineNumberTable == null) {
return 0;
} else {
return lineNumberTable.getLineForLocation(location);
}
}
public void getLineNumberTable(final VmIsolate isolate, final int libraryId,
final String eclipseUrl, final VmCallback<VmLineNumberTable> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
final String vmUrl = VmUtils.eclipseUrlToVm(eclipseUrl);
try {
JSONObject request = new JSONObject();
request.put("command", "getLineNumberTable");
request.put("params", new JSONObject().put("libraryId", libraryId).put("url", vmUrl));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<VmLineNumberTable> vmObjectResult = convertGetLineNumberTableResult(
isolate,
libraryId,
eclipseUrl,
result);
callback.handleResult(vmObjectResult);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void getListElements(final VmIsolate isolate, int listObjectId, int index,
final VmCallback<VmValue> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "getListElements");
request.put("params", new JSONObject().put("objectId", listObjectId).put("index", index));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<VmValue> vmObjectResult = convertGetListElementsResult(isolate, result);
callback.handleResult(vmObjectResult);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void getObjectProperties(final VmIsolate isolate, final int objectId,
final VmCallback<VmObject> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "getObjectProperties");
request.put("params", new JSONObject().put("objectId", objectId));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<VmObject> vmObjectResult = convertGetObjectPropertiesResult(
isolate,
objectId,
result);
callback.handleResult(vmObjectResult);
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
/**
* This synchronous, potentially long-running call returns the cached source for the given
* libraryId and source url.
*
* @param isolate
* @param libraryId
* @param url
* @return
*/
public String getScriptSource(VmIsolate isolate, final int libraryId, String url) {
final String cacheKey = libraryId + ":" + url;
if (!sourceCache.containsKey(cacheKey)) {
final CountDownLatch latch = new CountDownLatch(1);
try {
getScriptSourceAsync(isolate, libraryId, url, new VmCallback<String>() {
@Override
public void handleResult(VmResult<String> result) {
if (result.isError()) {
sourceCache.put(cacheKey, null);
} else {
sourceCache.put(cacheKey, result.getResult());
}
latch.countDown();
}
});
} catch (IOException e) {
sourceCache.put(cacheKey, null);
latch.countDown();
}
try {
latch.await();
} catch (InterruptedException e) {
}
}
return sourceCache.get(cacheKey);
}
public void getScriptSourceAsync(VmIsolate isolate, int libraryId, String url,
final VmCallback<String> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "getScriptSource");
request.put("params", new JSONObject().put("libraryId", libraryId).put("url", url));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
callback.handleResult(convertGetScriptSourceResult(result));
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void getScriptURLs(VmIsolate isolate, int libraryId,
final VmCallback<List<String>> callback) throws IOException {
if (callback == null) {
throw new IllegalArgumentException("a callback is required");
}
try {
JSONObject request = new JSONObject();
request.put("command", "getScriptURLs");
request.put("params", new JSONObject().put("libraryId", libraryId));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
callback.handleResult(convertGetScriptURLsResult(result));
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void getStackTrace(final VmIsolate isolate, final VmCallback<List<VmCallFrame>> callback)
throws IOException {
sendSimpleCommand("getStackTrace", isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
callback.handleResult(convertGetStackTraceResult(isolate, result));
}
});
}
/**
* Send an interrupt command to the given isolate.
*
* @param isolate
* @throws IOException
*/
public void interrupt(VmIsolate isolate) throws IOException {
sendSimpleCommand("interrupt", isolate.getId());
}
/**
* If the given isolate is running, send it a pause ('interrupt') command. Return an object that
* can be used to undo the operation. If the interrupt was already paused, no interrupt command
* will be sent and the returned VmInterruptResult object will represent a no-op.
* <p>
* Ex:
*
* <pre>
* VmInterruptResult interruptResult = connection.interruptConditionally(isolate);
* ...do work to the now paused isolate...
* interruptResult.resume();
* </pre>
*
* @param isolate
* @return
* @throws IOException
*/
public VmInterruptResult interruptConditionally(VmIsolate isolate) throws IOException {
if (!isolate.isPaused()) {
interrupt(isolate);
isolate.setPaused(true);
return VmInterruptResult.createResumeResult(this, isolate);
} else {
return VmInterruptResult.createNoopResult(this);
}
}
/**
* @return whether the connection is still open
*/
public boolean isConnected() {
return socket != null;
}
public void removeBreakpoint(VmIsolate isolate, final VmBreakpoint breakpoint) throws IOException {
try {
JSONObject request = new JSONObject();
request.put("command", "removeBreakpoint");
request.put("params", new JSONObject().put("breakpointId", breakpoint.getBreakpointId()));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject object) throws JSONException {
// Update the list of breakpoints based on the result code.
VmResult<?> result = VmResult.createFrom(object);
if (!result.isError()) {
breakpoints.remove(breakpoint);
}
}
});
breakpoints.remove(breakpoint);
} catch (JSONException exception) {
throw new IOException(exception);
}
}
public void removeListener(VmListener listener) {
listeners.remove(listener);
}
public void resume(VmIsolate isolate) throws IOException {
sendSimpleCommand("resume", isolate.getId(), resumeOnSuccess(isolate));
}
/**
* Set a breakpoint in the given file and line.
*
* @param isolate
* @param url
* @param line
* @param callback
* @throws IOException
*/
public void setBreakpoint(final VmIsolate isolate, final String url, final int line,
final VmCallback<VmBreakpoint> callback) throws IOException {
if (!isolate.isPaused()) {
throw new IOException("attempt to set breakpoint on a running isolate");
}
try {
JSONObject request = new JSONObject();
request.put("command", "setBreakpoint");
request.put(
"params",
new JSONObject().put("url", VmUtils.eclipseUrlToVm(url)).put("line", line));
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject object) throws JSONException {
VmResult<VmBreakpoint> result = new VmResult<VmBreakpoint>();
if (!object.has("error")) {
int breakpointId = JsonUtils.getInt(object.getJSONObject("result"), "breakpointId");
VmBreakpoint breakpoint = new VmBreakpoint(isolate, null, breakpointId);
breakpoints.add(breakpoint);
result.setResult(breakpoint);
} else {
result.setError(object.getString("error"));
}
if (callback != null) {
callback.handleResult(result);
}
}
});
} catch (JSONException exception) {
throw new IOException(exception);
}
try {
// TODO(devoncarew): workaround for bug https://code.google.com/p/dart/issues/detail?id=9705
// We need to give the VM time to process all the events before we start sending more.
// There's some race condition going on in the VM's queue.
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
/**
* Set the given library's properties; currently this enables / disables stepping into the
* library.
*
* @param libraryId
* @param debuggingEnabled
* @throws IOException
*/
public void setLibraryProperties(VmIsolate isolate, int libraryId, boolean debuggingEnabled)
throws IOException {
// TODO(scheglov) commented out on 2014-09-25
// I still can step into SDK libraries without it.
// And with it VM sometimes ignores "resume" command and application just hangs.
try {
JSONObject request = new JSONObject();
request.put("command", "setLibraryProperties");
request.put(
"params",
new JSONObject().put("libraryId", libraryId).put(
"debuggingEnabled",
Boolean.toString(debuggingEnabled)));
sendRequest(request, isolate.getId(), null);
} catch (JSONException exception) {
throw new IOException(exception);
}
}
/**
* Set the VM to pause on exceptions.
*
* @param isolate
* @param kind
* @throws IOException
*/
public void setPauseOnException(VmIsolate isolate, BreakOnExceptionsType kind) throws IOException {
setPauseOnException(isolate, kind, null);
}
/**
* Set the VM to pause on exceptions.
*
* @param isolate
* @param kind
* @param callback
* @throws IOException
*/
public void setPauseOnException(VmIsolate isolate, BreakOnExceptionsType kind,
final VmCallback<Boolean> callback) throws IOException {
try {
JSONObject request = new JSONObject();
request.put("command", "setPauseOnException");
request.put("params", new JSONObject().put("exceptions", kind.toString()));
if (callback == null) {
sendRequest(request, isolate.getId(), null);
} else {
sendRequest(request, isolate.getId(), new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<Boolean> callbackResult = VmResult.createFrom(result);
callbackResult.setResult(true);
callback.handleResult(callbackResult);
}
});
}
} catch (JSONException exception) {
throw new IOException(exception);
}
}
/**
* Set the VM to pause on exceptions.
*
* @param isolate
* @param kind
* @throws IOException
*/
public void setPauseOnExceptionSync(VmIsolate isolate, BreakOnExceptionsType kind)
throws IOException {
final CountDownLatch latch = new CountDownLatch(1);
try {
setPauseOnException(isolate, kind, new VmCallback<Boolean>() {
@Override
public void handleResult(VmResult<Boolean> result) {
latch.countDown();
}
});
} catch (IOException ioe) {
latch.countDown();
} catch (Throwable t) {
latch.countDown();
throw new RuntimeException(t);
}
try {
latch.await();
} catch (InterruptedException e) {
}
}
public void stepInto(VmIsolate isolate) throws IOException {
isStepping = true;
stepCommand = "stepInto";
sendSimpleCommand(stepCommand, isolate.getId(), resumeOnSuccess(isolate));
}
public void stepOut(VmIsolate isolate) throws IOException {
sendSimpleCommand("stepOut", isolate.getId(), resumeOnSuccess(isolate));
}
public void stepOver(VmIsolate isolate) throws IOException {
isStepping = true;
stepCommand = "stepOver";
sendSimpleCommand(stepCommand, isolate.getId(), resumeOnSuccess(isolate));
}
protected synchronized void handleTerminated() {
// Clean up the callbackMap on termination.
List<Callback> callbacks = new ArrayList<VmConnection.Callback>(callbackMap.values());
for (Callback callback : callbacks) {
try {
callback.handleResult(VmResult.createJsonErrorResult("connection termination"));
} catch (JSONException e) {
}
}
callbackMap.clear();
}
protected void processJson(final JSONObject result) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
if (result.has("id")) {
processResponse(result);
} else {
processNotification(result);
}
} catch (IOException exception) {
} catch (Throwable exception) {
DartDebugCorePlugin.logInfo(exception);
}
}
});
}
protected void sendSimpleCommand(String command, String isolateId) throws IOException {
sendSimpleCommand(command, isolateId, null);
}
protected void sendSimpleCommand(String command, String isolateId, Callback callback)
throws IOException {
try {
JSONObject request = new JSONObject();
request.put("command", command);
sendRequest(request, isolateId, callback);
} catch (JSONException exception) {
throw new IOException(exception);
}
}
void sendRequest(JSONObject request, String isolateId, Callback callback) throws IOException {
int id = 0;
try {
if (!isConnected()) {
if (callback != null) {
callback.handleResult(VmResult.createJsonErrorResult("connection termination"));
}
return;
}
if (!request.has("params")) {
request.put("params", new JSONObject());
}
JSONObject params = request.getJSONObject("params");
if (!params.has("isolateId") && isolateId != null) {
params.put("isolateId", isolateId);
}
} catch (JSONException jse) {
throw new IOException(jse);
}
synchronized (this) {
id = nextCommandId++;
try {
request.put("id", id);
} catch (JSONException ex) {
throw new IOException(ex);
}
if (callback != null) {
callbackMap.put(id, callback);
}
}
try {
send(request.toString());
} catch (IOException ex) {
if (callback != null) {
synchronized (this) {
callbackMap.remove(id);
}
}
throw ex;
}
}
private VmResult<VmValue> convertEvaluateObjectResult(VmIsolate isolate, JSONObject object)
throws JSONException {
VmResult<VmValue> result = VmResult.createFrom(object);
if (object.has("result")) {
result.setResult(VmValue.createFrom(isolate, object.getJSONObject("result")));
}
return result;
}
private VmResult<VmClass> convertGetClassPropertiesResult(VmIsolate isolate, int classId,
JSONObject object) throws JSONException {
VmResult<VmClass> result = VmResult.createFrom(object);
if (object.has("result")) {
result.setResult(VmClass.createFrom(isolate, object.getJSONObject("result")));
result.getResult().setClassId(classId);
}
return result;
}
private VmResult<List<VmVariable>> convertGetGlobalVariablesResult(VmIsolate isolate,
JSONObject object) throws JSONException {
VmResult<List<VmVariable>> result = VmResult.createFrom(object);
if (object.has("result")) {
JSONObject jsonResult = object.getJSONObject("result");
result.setResult(VmVariable.createFrom(isolate, jsonResult.optJSONArray("globals"), false));
}
return result;
}
private VmResult<List<Integer>> convertGetIsolateIdsResult(JSONObject object)
throws JSONException {
VmResult<List<Integer>> result = VmResult.createFrom(object);
if (object.has("result")) {
JSONArray arr = object.getJSONObject("result").optJSONArray("isolateIds");
List<Integer> isolateIds = new ArrayList<Integer>();
for (int i = 0; i < arr.length(); i++) {
isolateIds.add(new Integer(arr.getInt(i)));
}
result.setResult(isolateIds);
}
return result;
}
private VmResult<List<VmLibraryRef>> convertGetLibrariesResult(JSONObject object)
throws JSONException {
VmResult<List<VmLibraryRef>> result = VmResult.createFrom(object);
if (object.has("result")) {
result.setResult(VmLibraryRef.createFrom(object.getJSONObject("result").optJSONArray(
"libraries")));
}
return result;
}
private VmResult<VmLibrary> convertGetLibraryPropertiesResult(VmIsolate isolate, int libraryId,
JSONObject object) throws JSONException {
VmResult<VmLibrary> result = VmResult.createFrom(object);
if (object.has("result")) {
result.setResult(VmLibrary.createFrom(isolate, libraryId, object.getJSONObject("result")));
}
return result;
}
private VmResult<VmLineNumberTable> convertGetLineNumberTableResult(VmIsolate isolate,
int libraryId, String url, JSONObject object) throws JSONException {
VmResult<VmLineNumberTable> result = VmResult.createFrom(object);
if (object.has("result")) {
result.setResult(VmLineNumberTable.createFrom(
isolate,
libraryId,
url,
object.getJSONObject("result")));
}
return result;
}
private VmResult<VmValue> convertGetListElementsResult(VmIsolate isolate, JSONObject object)
throws JSONException {
VmResult<VmValue> result = VmResult.createFrom(object);
if (object.has("result")) {
result.setResult(VmValue.createFrom(isolate, object.getJSONObject("result")));
}
return result;
}
private VmResult<VmObject> convertGetObjectPropertiesResult(VmIsolate isolate, int objectId,
JSONObject object) throws JSONException {
VmResult<VmObject> result = VmResult.createFrom(object);
if (object.has("result")) {
result.setResult(VmObject.createFrom(isolate, object.getJSONObject("result")));
result.getResult().setObjectId(objectId);
}
return result;
}
private VmResult<String> convertGetScriptSourceResult(JSONObject object) throws JSONException {
VmResult<String> result = VmResult.createFrom(object);
if (object.has("result")) {
result.setResult(object.getJSONObject("result").getString("text"));
}
return result;
}
private VmResult<List<String>> convertGetScriptURLsResult(JSONObject object) throws JSONException {
VmResult<List<String>> result = VmResult.createFrom(object);
if (object.has("result")) {
List<String> libUrls = new ArrayList<String>();
JSONArray arr = object.getJSONObject("result").getJSONArray("urls");
for (int i = 0; i < arr.length(); i++) {
libUrls.add(VmUtils.vmUrlToEclipse(arr.getString(i)));
}
result.setResult(libUrls);
}
return result;
}
private VmResult<List<VmCallFrame>> convertGetStackTraceResult(VmIsolate isolate,
JSONObject object) throws JSONException {
VmResult<List<VmCallFrame>> result = VmResult.createFrom(object);
if (object.has("result")) {
List<VmCallFrame> frames = VmCallFrame.createFrom(
isolate,
object.getJSONObject("result").getJSONArray("callFrames"));
result.setResult(frames);
}
return result;
}
private VmIsolate getCreateIsolate(String isolateId) {
return getCreateIsolate(isolateId, false);
}
private VmIsolate getCreateIsolate(String isolateId, boolean paused) {
if (isolateId == null) {
return null;
}
if (isolateMap.get(isolateId) == null) {
isolateMap.put(isolateId, new VmIsolate(isolateId, paused));
}
return isolateMap.get(isolateId);
}
private void handleBreakpointResolved(VmIsolate isolate, int breakpointId, VmLocation location) {
boolean foundBreakpoint = false;
synchronized (breakpoints) {
for (VmBreakpoint breakpoint : breakpoints) {
if (breakpoint.getBreakpointId() == breakpointId) {
foundBreakpoint = true;
breakpoint.updateLocation(location);
notifyBreakpointResolved(isolate, breakpoint);
}
}
}
if (!foundBreakpoint) {
VmBreakpoint breakpoint = new VmBreakpoint(isolate, location, breakpointId);
breakpoints.add(breakpoint);
notifyBreakpointResolved(isolate, breakpoint);
}
}
private void notifyBreakpointResolved(VmIsolate isolate, VmBreakpoint breakpoint) {
for (VmListener listener : listeners) {
listener.breakpointResolved(isolate, breakpoint);
}
}
private void notifyDebuggerResumed(VmIsolate isolate) {
isolate.clearClassInfoMap();
for (VmListener listener : listeners) {
listener.debuggerResumed(isolate);
}
}
private void populateClassInfo(final VmIsolate isolate, final int classId) {
final CountDownLatch latch = new CountDownLatch(1);
try {
getClassProperties(isolate, classId, new VmCallback<VmClass>() {
@Override
public void handleResult(VmResult<VmClass> result) {
if (!result.isError()) {
isolate.setClassInfo(classId, result.getResult());
}
latch.countDown();
}
});
} catch (IOException e1) {
latch.countDown();
}
try {
latch.await();
} catch (InterruptedException e) {
}
}
private void populateLibraryInfo(final VmIsolate isolate, final int libraryId) {
final CountDownLatch latch = new CountDownLatch(1);
try {
getLibraryProperties(isolate, libraryId, new VmCallback<VmLibrary>() {
@Override
public void handleResult(VmResult<VmLibrary> result) {
if (!result.isError()) {
isolate.setLibraryInfo(libraryId, result.getResult());
}
latch.countDown();
}
});
} catch (IOException e1) {
latch.countDown();
}
try {
latch.await();
} catch (InterruptedException e) {
}
}
private void processNotification(JSONObject result) throws JSONException, IOException {
if (result.has("event")) {
String eventName = result.getString("event");
JSONObject params = result.optJSONObject("params");
if (eventName.equals(EVENT_PAUSED)) {
String isolateId = params.optString("isolateId");
String reason = params.optString("reason", null);
VmIsolate isolate = getCreateIsolate(isolateId);
VmValue exception = VmValue.createFrom(isolate, params.optJSONObject("exception"));
VmLocation location = VmLocation.createFrom(isolate, params.optJSONObject("location"));
isolate.setPaused(true);
sendDelayedDebuggerPaused(PausedReason.parse(reason), isolate, location, exception);
} else if (eventName.equals(EVENT_BREAKPOINTRESOLVED)) {
// { "event": "breakpointResolved", "params": {"breakpointId": 2, "url": "file:///Users/devoncarew/tools/eclipse_37/eclipse/samples/time/time_server.dart", "line": 19 }}
int breakpointId = params.optInt("breakpointId");
String isolateId = params.optString("isolateId");
VmIsolate isolate = getCreateIsolate(isolateId);
VmLocation location = VmLocation.createFrom(isolate, params.getJSONObject("location"));
handleBreakpointResolved(isolate, breakpointId, location);
} else if (eventName.equals(EVENT_ISOLATE)) {
// { "event": "isolate", "params": { "reason": "created", "id": 7114 }}]
// { "event": "isolate", "params": { "reason": "shutdown", "id": 7114 }}]
String reason = params.optString("reason", null);
String isolateId = params.optString("id");
final VmIsolate isolate = getCreateIsolate(isolateId, "created".equals(reason));
if ("created".equals(reason)) {
for (VmListener listener : listeners) {
listener.isolateCreated(isolate);
}
} else if ("shutdown".equals(reason)) {
for (VmListener listener : listeners) {
listener.isolateShutdown(isolate);
}
isolate.setPaused(false);
isolateMap.remove(isolate.getId());
}
} else {
DartDebugCorePlugin.logInfo("no handler for notification: " + eventName);
}
} else {
DartDebugCorePlugin.logInfo("event not understood: " + result);
}
}
private void processResponse(JSONObject result) throws JSONException {
// Process a command response.
int id = result.getInt("id");
Callback callback;
synchronized (this) {
callback = callbackMap.remove(id);
}
if (callback != null) {
callback.handleResult(result);
} else if (result.has("error")) {
// If we get an error back, and nobody was listening for the result, then log it.
VmResult<?> vmResult = VmResult.createFrom(result);
DartDebugCorePlugin.logInfo("Error from command id " + id + ": " + vmResult.getError());
}
}
private void processVmEvents(InputStream in) throws IOException {
Reader reader = new InputStreamReader(in, UTF8);
JSONObject obj = readJson(reader);
while (obj != null) {
processJson(obj);
obj = readJson(reader);
}
}
private JSONObject readJson(Reader in) throws IOException {
final int MAX_PRINT_LENGTH = 2000;
StringBuilder builder = new StringBuilder();
boolean inQuote = false;
boolean ignoreLast = false;
int curlyCount = 0;
int c = in.read();
while (true) {
if (c == -1) {
throw new EOFException();
}
builder.append((char) c);
if (!ignoreLast) {
if (c == '"') {
inQuote = !inQuote;
}
}
if (inQuote && c == '\\') {
ignoreLast = true;
} else {
ignoreLast = false;
}
if (!inQuote) {
if (c == '{') {
curlyCount++;
} else if (c == '}') {
curlyCount--;
if (curlyCount == 0) {
try {
String str = builder.toString();
// TODO(devoncarew): we know this is occurring for exception text.
// Possibly from toString() invocations?
if (str.indexOf('\n') != -1) {
DartDebugCorePlugin.logError("bad json from vm: " + str);
str = str.replace("\n", "\\n");
}
if (str.length() > MAX_PRINT_LENGTH) {
DartDebugCorePlugin.log("<== " + str.substring(0, MAX_PRINT_LENGTH) + "...");
DartDebugCorePlugin.log("<== (long line: " + str.length() + " chars)");
} else {
DartDebugCorePlugin.log("<== " + str);
}
return new JSONObject(str);
} catch (JSONException e) {
throw new IOException(e);
}
}
}
}
c = in.read();
}
}
private Callback resumeOnSuccess(final VmIsolate isolate) {
return new Callback() {
@Override
public void handleResult(JSONObject result) throws JSONException {
VmResult<String> response = VmResult.createFrom(result);
if (!response.isError()) {
isolate.setPaused(false);
notifyDebuggerResumed(isolate);
}
}
};
}
/**
* Return whether the given vm locations represent the same source line.
*/
private boolean sameSourceLine(VmLocation location1, VmLocation location2) {
if (location1 == null || location2 == null) {
return false;
}
int line1 = getLineNumberFromLocation(location1.getIsolate(), location1);
int line2 = getLineNumberFromLocation(location2.getIsolate(), location2);
if (line1 <= 0 || line2 <= 0) {
return false;
}
return line1 == line2;
}
private void send(String str) throws IOException {
DartDebugCorePlugin.log("==> " + str);
byte[] bytes = str.getBytes(UTF8);
out.write(bytes);
out.flush();
}
private void sendDelayedDebuggerPaused(final PausedReason reason, final VmIsolate isolate,
final VmLocation location, final VmValue exception) throws JSONException, IOException {
// Request lines now, while the isolate is current.
// Otherwise VM will never respond to the "getLineNumberTable" request.
getLineNumberFromLocation(location.getIsolate(), location);
// If we're stepping, check here to see if we should continue stepping.
if (sameSourceLine(currentLocation, location) && reason == PausedReason.breakpoint
&& isStepping) {
sendSimpleCommand(stepCommand, isolate.getId());
} else {
try {
getStackTrace(isolate, new VmCallback<List<VmCallFrame>>() {
@Override
public void handleResult(VmResult<List<VmCallFrame>> result) {
currentLocation = location;
isStepping = false;
if (result.isError()) {
DartDebugCorePlugin.logInfo(result.getError());
} else {
List<VmCallFrame> frames = result.getResult();;
for (VmListener listener : listeners) {
listener.debuggerPaused(reason, isolate, frames, exception);
}
}
}
});
} catch (IOException e) {
throw new JSONException(e);
}
}
}
}