// 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;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.chromium.sdk.DebugEventListener;
import org.chromium.sdk.StandaloneVm;
import org.chromium.sdk.UnsupportedVersionException;
import org.chromium.sdk.internal.protocol.data.ContextHandle;
import org.chromium.sdk.internal.tools.v8.V8CommandOutput;
import org.chromium.sdk.internal.tools.v8.request.DebuggerMessage;
import org.chromium.sdk.internal.transport.Connection;
import org.chromium.sdk.internal.transport.Handshaker;
import org.chromium.sdk.internal.transport.Message;
import org.chromium.sdk.internal.transport.SocketConnection;
import org.chromium.sdk.internal.transport.Connection.NetListener;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
/**
* Implementation of {@code StandaloneVm}. Currently knows nothing about
* contexts, so all existing V8 contexts are presented mixed together.
*/
class StandaloneVmImpl extends JavascriptVmImpl implements StandaloneVm {
/** The class logger. */
private static final Logger LOGGER =
Logger.getLogger(StandaloneVmImpl.class.getName());
private static final int WAIT_FOR_HANDSHAKE_TIMEOUT_MS = 3000;
private static final V8ContextFilter CONTEXT_FILTER = new V8ContextFilter() {
public boolean isContextOurs(ContextHandle contextHandle) {
// We do not check context in standalone V8 mode.
return true;
}
};
private final SocketConnection connection;
private final Handshaker.StandaloneV8 handshaker;
private final DebugSession debugSession;
private DebugEventListener debugEventListener = null;
private volatile ConnectionState connectionState = ConnectionState.INIT;
private volatile Exception disconnectReason = null;
private volatile Handshaker.StandaloneV8.RemoteInfo savedRemoteInfo = NULL_REMOTE_INFO;
private final Object disconnectMonitor = new Object();
StandaloneVmImpl(SocketConnection connection, Handshaker.StandaloneV8 handshaker) {
this.connection = connection;
this.handshaker = handshaker;
V8CommandOutputImpl v8CommandOutput = new V8CommandOutputImpl(connection);
this.debugSession = new DebugSession(sessionManager, CONTEXT_FILTER, v8CommandOutput);
}
public void attach(DebugEventListener listener) throws IOException, UnsupportedVersionException {
Exception errorCause = null;
try {
attachImpl(listener);
} catch (IOException e) {
errorCause = e;
throw e;
} catch (UnsupportedVersionException e) {
errorCause = e;
throw e;
} finally {
if (errorCause != null) {
disconnectReason = errorCause;
connectionState = ConnectionState.DETACHED;
connection.close();
}
}
}
private void attachImpl(DebugEventListener listener) throws IOException,
UnsupportedVersionException {
connectionState = ConnectionState.CONNECTING;
NetListener netListener = new NetListener() {
public void connectionClosed() {
}
public void eosReceived() {
debugSession.getV8CommandProcessor().processEos();
onDebuggerDetachedImpl(null);
}
public void messageReceived(Message message) {
JSONObject json;
try {
json = JsonUtil.jsonObjectFromJson(message.getContent());
} catch (ParseException e) {
LOGGER.log(Level.SEVERE, "Invalid JSON received: {0}", message.getContent());
return;
}
debugSession.getV8CommandProcessor().processIncomingJson(json);
}
};
connection.setNetListener(netListener);
connection.start();
connectionState = ConnectionState.EXPECTING_HANDSHAKE;
Handshaker.StandaloneV8.RemoteInfo remoteInfo;
try {
remoteInfo = handshaker.getRemoteInfo().get(WAIT_FOR_HANDSHAKE_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw newIOException("Failed to get version", e);
} catch (TimeoutException e) {
throw newIOException("Timed out waiting for version", e);
}
String versionString = remoteInfo.getProtocolVersion();
// TODO(peter.rybin): check version here
if (versionString == null) {
throw new UnsupportedVersionException(null, null);
}
StandaloneVmImpl.this.savedRemoteInfo = remoteInfo;
StandaloneVmImpl.this.debugEventListener = listener;
debugSession.startCommunication();
connectionState = ConnectionState.CONNECTED;
}
public boolean detach() {
boolean res = onDebuggerDetachedImpl(null);
if (!res) {
return false;
}
connection.close();
return true;
}
public boolean isAttached() {
return connectionState == ConnectionState.CONNECTED;
}
private boolean onDebuggerDetachedImpl(Exception cause) {
synchronized (disconnectMonitor) {
if (!isAttached()) {
// We've already been notified.
return false;
}
connectionState = ConnectionState.DETACHED;
disconnectReason = cause;
}
if (debugEventListener != null) {
debugEventListener.disconnected();
}
return true;
}
@Override
protected DebugSession getDebugSession() {
return debugSession;
}
/**
* @return name of embedding application as it wished to name itself; might be null
*/
public String getEmbedderName() {
return savedRemoteInfo.getEmbeddingHostName();
}
/**
* @return version of V8 implementation, format is unspecified; not null
*/
public String getVmVersion() {
return savedRemoteInfo.getV8VmVersion();
}
public String getDisconnectReason() {
// Save volatile field in local variable.
Exception cause = disconnectReason;
if (cause == null) {
return null;
}
return cause.getMessage();
}
private final DebugSessionManager sessionManager = new DebugSessionManager() {
public DebugEventListener getDebugEventListener() {
return debugEventListener;
}
public void onDebuggerDetached() {
// Never called for standalone.
}
};
private final static Handshaker.StandaloneV8.RemoteInfo NULL_REMOTE_INFO =
new Handshaker.StandaloneV8.RemoteInfo() {
public String getEmbeddingHostName() {
return null;
}
public String getProtocolVersion() {
return null;
}
public String getV8VmVersion() {
return null;
}
};
private enum ConnectionState {
INIT,
CONNECTING,
EXPECTING_HANDSHAKE,
CONNECTED,
DETACHED
}
private static class V8CommandOutputImpl implements V8CommandOutput {
private final Connection outputConnection;
V8CommandOutputImpl(Connection outputConnection) {
this.outputConnection = outputConnection;
}
public void send(DebuggerMessage debuggerMessage, boolean immediate) {
String jsonString = JsonUtil.streamAwareToJson(debuggerMessage);
Message message = new Message(Collections.<String, String>emptyMap(), jsonString);
outputConnection.send(message);
// TODO(peter.rybin): support {@code immediate} in protocol
}
}
}