package com.oracle.truffle.tools.chromeinspector;
import java.io.PrintWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.debug.DebugException;
import com.oracle.truffle.api.debug.DebugValue;
import com.oracle.truffle.api.debug.DebuggerSession;
import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.SourceFilter;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.tools.chromeinspector.server.CommandProcessException;
import com.oracle.truffle.tools.chromeinspector.types.CallArgument;
import com.oracle.truffle.tools.chromeinspector.types.RemoteObject;
public final class InspectorExecutionContext {
public static final String VALUE_NOT_READABLE = "<not readable>";
private static final AtomicLong LAST_ID = new AtomicLong(0);
private final String name;
private final TruffleInstrument.Env env;
private final PrintWriter err;
private final List<Listener> listeners = Collections.synchronizedList(new ArrayList<>(3));
private final long id = LAST_ID.incrementAndGet();
private final boolean[] runPermission = new boolean[]{false};
private final boolean inspectInternal;
private final boolean inspectInitialization;
private final List<URI> sourceRoots;
private final TruffleLogger log;
private final boolean allowToStringSideEffects = true;
private volatile DebuggerSuspendedInfo suspendedInfo;
private volatile SuspendedThreadExecutor suspendThreadExecutor;
private RemoteObjectsHandler roh;
private volatile ScriptsHandler scriptsHandler;
private volatile EventBinding<ScriptsHandler> schBinding;
private int schCounter;
private volatile String lastMimeType = "text/javascript";
private volatile String lastLanguage = "js";
private boolean synchronous = false;
private boolean customObjectFormatterEnabled = false;
public InspectorExecutionContext(String name, boolean inspectInternal, boolean inspectInitialization, TruffleInstrument.Env env, List<URI> sourceRoots, PrintWriter err) {
this.name = name;
this.inspectInternal = inspectInternal;
this.inspectInitialization = inspectInitialization;
this.env = env;
this.sourceRoots = sourceRoots;
this.err = err;
this.log = env.getLogger("");
}
public boolean isInspectInternal() {
return inspectInternal;
}
public boolean isInspectInitialization() {
return inspectInitialization;
}
public boolean areToStringSideEffectsAllowed() {
return allowToStringSideEffects;
}
public TruffleInstrument.Env getEnv() {
return env;
}
public long getId() {
return id;
}
public PrintWriter getErr() {
return err;
}
public void logMessage(String prefix, Object message) {
if (log.isLoggable(Level.FINE)) {
log.fine("CONTEXT " + id + " " + prefix + message);
}
}
public void logException(Throwable ex) {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "CONTEXT " + id, ex);
}
}
public void logException(String prefix, Throwable ex) {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "CONTEXT " + id + " " + prefix, ex);
}
}
Iterable<URI> getSourcePath() {
return sourceRoots;
}
public void doRunIfWaitingForDebugger() {
fireContextCreated();
synchronized (runPermission) {
runPermission[0] = true;
runPermission.notifyAll();
}
}
public boolean canRun() {
synchronized (runPermission) {
return runPermission[0];
}
}
public ScriptsHandler acquireScriptsHandler() {
ScriptsHandler sh;
boolean attachListener = false;
synchronized (this) {
sh = scriptsHandler;
if (sh == null) {
scriptsHandler = sh = new ScriptsHandler(inspectInternal);
attachListener = true;
schCounter = 0;
}
schCounter++;
}
if (attachListener) {
schBinding = env.getInstrumenter().attachLoadSourceListener(SourceFilter.ANY, sh, true);
}
return sh;
}
public synchronized void releaseScriptsHandler() {
if (--schCounter == 0) {
schBinding.dispose();
schBinding = null;
scriptsHandler = null;
}
}
public void addListener(Listener l) {
listeners.add(l);
}
public void removeListener(Listener l) {
listeners.remove(l);
}
private void fireContextCreated() {
for (Listener l : listeners) {
l.contextCreated(id, name);
}
}
public void waitForRunPermission() throws InterruptedException {
if (synchronous) {
return;
}
synchronized (runPermission) {
while (!runPermission[0]) {
runPermission.wait();
}
}
}
public synchronized RemoteObjectsHandler getRemoteObjectsHandler() {
if (roh == null) {
roh = new RemoteObjectsHandler(this);
}
return roh;
}
public RemoteObject createAndRegister(DebugValue value, boolean generatePreview) {
RemoteObject ro = new RemoteObject(value, generatePreview, this);
if (ro.getId() != null) {
getRemoteObjectsHandler().register(ro);
}
return ro;
}
void setValue(DebugValue debugValue, CallArgument newValue) {
String objectId = newValue.getObjectId();
if (objectId != null) {
RemoteObject obj = getRemoteObjectsHandler().getRemote(objectId);
debugValue.set(obj.getDebugValue());
} else {
debugValue.set(newValue.getPrimitiveValue());
}
}
void setSuspendThreadExecutor(SuspendedThreadExecutor suspendThreadExecutor) {
this.suspendThreadExecutor = suspendThreadExecutor;
}
<T> T executeInSuspendThread(SuspendThreadExecutable<T> executable) throws NoSuspendedThreadException, CommandProcessException {
if (synchronous) {
try {
return executable.executeCommand();
} catch (ThreadDeath td) {
throw td;
} catch (DebugException dex) {
return executable.processException(dex);
}
}
CompletableFuture<T> cf = new CompletableFuture<>();
suspendThreadExecutor.execute(new CancellableRunnable() {
@Override
public void run() {
T params = null;
try {
params = executable.executeCommand();
cf.complete(params);
} catch (ThreadDeath td) {
cf.completeExceptionally(td);
throw td;
} catch (DebugException dex) {
cf.complete(executable.processException(dex));
} catch (Throwable t) {
cf.completeExceptionally(t);
}
}
@Override
public void cancel() {
cf.completeExceptionally(new NoSuspendedThreadException("Resuming..."));
}
});
T params;
try {
params = cf.get();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
if (cause instanceof CommandProcessException) {
throw (CommandProcessException) cause;
}
if (cause instanceof NoSuspendedThreadException) {
throw (NoSuspendedThreadException) cause;
}
if (err != null) {
cause.printStackTrace(err);
}
throw new CommandProcessException(ex.getLocalizedMessage());
} catch (InterruptedException ex) {
throw new CommandProcessException(ex.getLocalizedMessage());
}
return params;
}
void setLastLanguage(String language, String mimeType) {
this.lastLanguage = language;
this.lastMimeType = mimeType;
}
String getLastLanguage() {
return lastLanguage;
}
String getLastMimeType() {
return lastMimeType;
}
void setSuspendedInfo(DebuggerSuspendedInfo suspendedInfo) {
this.suspendedInfo = suspendedInfo;
if (suspendedInfo == null) {
synchronized (this) {
if (roh != null) {
roh.reset();
}
}
}
}
DebuggerSuspendedInfo getSuspendedInfo() {
return suspendedInfo;
}
public DebuggerSession getDebuggerSession() {
ScriptsHandler handler = this.scriptsHandler;
return (handler != null) ? handler.getDebuggerSession() : null;
}
public static void resetIDs() {
LAST_ID.set(0);
}
public void reset() {
this.suspendedInfo = null;
this.suspendThreadExecutor = null;
this.roh = null;
assert scriptsHandler == null;
synchronized (runPermission) {
runPermission[0] = true;
runPermission.notifyAll();
}
}
public void setSynchronous(boolean synchronousExecution) {
this.synchronous = synchronousExecution;
}
public boolean isSynchronous() {
return synchronous;
}
void setCustomObjectFormatterEnabled(boolean enabled) {
this.customObjectFormatterEnabled = enabled;
}
public boolean isCustomObjectFormatterEnabled() {
return this.customObjectFormatterEnabled;
}
public interface Listener {
void contextCreated(long id, String name);
void contextDestroyed(long id, String name);
}
interface SuspendedThreadExecutor {
void execute(CancellableRunnable run) throws NoSuspendedThreadException;
}
interface CancellableRunnable extends Runnable {
void cancel();
}
static final class NoSuspendedThreadException extends Exception {
private static final long serialVersionUID = 2834058024185219386L;
private NoSuspendedThreadException(String message) {
super(message);
}
static void raise() throws NoSuspendedThreadException {
throw new NoSuspendedThreadException("<Not suspended>");
}
static void raiseResuming() throws NoSuspendedThreadException {
throw new NoSuspendedThreadException("<Resuming...>");
}
@SuppressWarnings("sync-override")
@Override
public Throwable fillInStackTrace() {
return this;
}
}
}