package com.oracle.truffle.tools.chromeinspector;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import com.oracle.truffle.tools.utils.json.JSONArray;
import com.oracle.truffle.tools.utils.json.JSONObject;
import com.oracle.truffle.api.debug.Breakpoint;
import com.oracle.truffle.api.debug.DebugValue;
import com.oracle.truffle.api.debug.DebuggerSession;
import com.oracle.truffle.api.debug.SourceElement;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.tools.chromeinspector.ScriptsHandler.LoadScriptListener;
import com.oracle.truffle.tools.chromeinspector.commands.Params;
import com.oracle.truffle.tools.chromeinspector.events.Event;
import com.oracle.truffle.tools.chromeinspector.events.EventHandler;
import com.oracle.truffle.tools.chromeinspector.server.CommandProcessException;
import com.oracle.truffle.tools.chromeinspector.types.Location;
import com.oracle.truffle.tools.chromeinspector.types.Script;
final class BreakpointsHandler {
private long lastID = 0;
private final DebuggerSession ds;
private final ScriptsHandler slh;
private final ResolvedHandler resolvedHandler;
private final Map<Breakpoint, Long> bpIDs = new HashMap<>();
private final Map<Breakpoint, SourceSection> resolvedBreakpoints = new HashMap<>();
private final Map<Long, LoadScriptListener> scriptListeners = new HashMap<>();
private final AtomicReference<Breakpoint> exceptionBreakpoint = new AtomicReference<>();
BreakpointsHandler(DebuggerSession ds, ScriptsHandler slh, Supplier<EventHandler> eventHandler) {
this.ds = ds;
this.slh = slh;
this.resolvedHandler = new ResolvedHandler(eventHandler);
}
String getId(Breakpoint bp) {
synchronized (bpIDs) {
Long id = bpIDs.get(bp);
if (id != null) {
return id.toString();
} else {
return null;
}
}
}
Params createURLBreakpoint(Object url, int line, int column, String condition) {
JSONArray locations = new JSONArray();
long id;
LoadScriptListener scriptListener;
synchronized (bpIDs) {
id = ++lastID;
scriptListener = script -> {
if (url instanceof Pattern ? ((Pattern) url).matcher(script.getUrl()).matches() : ScriptsHandler.compareURLs((String) url, script.getUrl())) {
Breakpoint bp = createBuilder(script.getSource(), line, column).resolveListener(resolvedHandler).build();
if (condition != null && !condition.isEmpty()) {
bp.setCondition(condition);
}
bp = ds.install(bp);
synchronized (bpIDs) {
bpIDs.put(bp, id);
SourceSection section = resolvedBreakpoints.remove(bp);
if (section != null) {
Location resolvedLocation = new Location(script.getId(), section.getStartLine(), section.getStartColumn());
locations.put(resolvedLocation.toJSON());
}
}
}
};
scriptListeners.put(id, scriptListener);
}
slh.addLoadScriptListener(scriptListener);
JSONObject json = new JSONObject();
json.put("breakpointId", Long.toString(id));
json.put("locations", locations);
return new Params(json);
}
Params createBreakpoint(Location location, String condition) throws CommandProcessException {
Script script = slh.getScript(location.getScriptId());
if (script == null) {
throw new CommandProcessException("No script with id '" + location.getScriptId() + "'");
}
Breakpoint bp = createBuilder(script.getSource(), location.getLine(), location.getColumn()).resolveListener(resolvedHandler).build();
if (condition != null && !condition.isEmpty()) {
bp.setCondition(condition);
}
bp = ds.install(bp);
Location resolvedLocation = location;
long id;
synchronized (bpIDs) {
id = ++lastID;
bpIDs.put(bp, id);
SourceSection section = resolvedBreakpoints.remove(bp);
if (section != null) {
resolvedLocation = new Location(location.getScriptId(), section.getStartLine(), section.getStartColumn());
}
}
JSONObject json = new JSONObject();
json.put("breakpointId", Long.toString(id));
json.put("actualLocation", resolvedLocation.toJSON());
return new Params(json);
}
boolean removeBreakpoint(String idStr) {
boolean bpRemoved = false;
try {
long id = Long.parseLong(idStr);
synchronized (bpIDs) {
Iterator<Map.Entry<Breakpoint, Long>> bpEntryIt = bpIDs.entrySet().iterator();
while (bpEntryIt.hasNext()) {
Map.Entry<Breakpoint, Long> bpEntry = bpEntryIt.next();
if (id == bpEntry.getValue().longValue()) {
Breakpoint bp = bpEntry.getKey();
if (bp != null) {
bp.dispose();
}
bpEntryIt.remove();
bpRemoved = true;
}
}
LoadScriptListener scriptListener = scriptListeners.remove(id);
if (scriptListener != null) {
slh.removeLoadScriptListener(scriptListener);
bpRemoved = true;
}
}
} catch (NumberFormatException nfex) {
}
return bpRemoved;
}
void createOneShotBreakpoint(Location location) throws CommandProcessException {
Script script = slh.getScript(location.getScriptId());
if (script == null) {
throw new CommandProcessException("No script with id '" + location.getScriptId() + "'");
}
Breakpoint bp = createBuilder(script.getSource(), location.getLine(), location.getColumn()).oneShot().build();
ds.install(bp);
}
void setExceptionBreakpoint(boolean caught, boolean uncaught) {
Breakpoint newBp = null;
if (caught || uncaught) {
newBp = Breakpoint.newExceptionBuilder(caught, uncaught).build();
ds.install(newBp);
}
Breakpoint oldBp = exceptionBreakpoint.getAndSet(newBp);
if (oldBp != null) {
oldBp.dispose();
}
}
private static Breakpoint.Builder createBuilder(Source source, int line, int column) {
Breakpoint.Builder builder = Breakpoint.newBuilder(source).lineIs(line);
if (column > 0) {
builder.columnIs(column);
}
return builder;
}
Params createFunctionBreakpoint(DebugValue functionValue, String condition) {
SourceSection functionLocation = functionValue.getSourceLocation();
Breakpoint.Builder builder;
if (functionLocation != null) {
builder = Breakpoint.newBuilder(functionLocation);
} else {
builder = Breakpoint.newBuilder((URI) null);
}
builder.rootInstance(functionValue);
builder.sourceElements(SourceElement.ROOT);
Breakpoint bp = builder.build();
if (condition != null && !condition.isEmpty()) {
bp.setCondition(condition);
}
ds.install(bp);
long id;
synchronized (bpIDs) {
id = ++lastID;
bpIDs.put(bp, id);
}
JSONObject json = new JSONObject();
json.put("breakpointId", Long.toString(id));
return new Params(json);
}
void removeFunctionBreakpoint(DebugValue functionValue) {
List<Breakpoint> breakpoints = functionValue.getRootInstanceBreakpoints();
for (Breakpoint breakpoint : breakpoints) {
String id = getId(breakpoint);
if (id != null) {
removeBreakpoint(id);
}
}
}
private final class ResolvedHandler implements Breakpoint.ResolveListener {
private final Supplier<EventHandler> eventHandler;
private ResolvedHandler(Supplier<EventHandler> eventHandler) {
this.eventHandler = eventHandler;
}
@Override
public void breakpointResolved(Breakpoint breakpoint, SourceSection section) {
Long breakpointId;
synchronized (bpIDs) {
breakpointId = bpIDs.get(breakpoint);
if (breakpointId == null) {
resolvedBreakpoints.put(breakpoint, section);
return;
}
}
int scriptId = slh.getScriptId(section.getSource());
Location location = new Location(scriptId, section.getStartLine(), section.getStartColumn());
JSONObject jsonParams = new JSONObject();
jsonParams.put("breakpointId", Long.toString(breakpointId));
jsonParams.put("location", location.toJSON());
Params params = new Params(jsonParams);
Event event = new Event("Debugger.breakpointResolved", params);
eventHandler.get().event(event);
}
}
}