blob: 372af26d435891de050e0a26a19716e78d9ed01a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.netbeans.modules.javascript.v8debug.breakpoints;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.lib.v8debug.V8Arguments;
import org.netbeans.lib.v8debug.V8Breakpoint;
import org.netbeans.lib.v8debug.V8Command;
import org.netbeans.lib.v8debug.V8Request;
import org.netbeans.lib.v8debug.V8Response;
import org.netbeans.lib.v8debug.commands.ChangeBreakpoint;
import org.netbeans.lib.v8debug.commands.ClearBreakpoint;
import org.netbeans.lib.v8debug.commands.Flags;
import org.netbeans.lib.v8debug.commands.SetBreakpoint;
import org.netbeans.lib.v8debug.events.BreakEventBody;
import org.netbeans.modules.javascript.v8debug.ScriptsHandler;
import org.netbeans.modules.javascript.v8debug.V8Debugger;
import org.netbeans.modules.javascript.v8debug.frames.CallFrame;
import org.netbeans.modules.javascript2.debug.breakpoints.JSBreakpointStatus;
import org.netbeans.modules.javascript2.debug.breakpoints.JSLineBreakpoint;
import org.netbeans.modules.web.common.sourcemap.SourceMapsTranslator;
import org.netbeans.modules.web.common.sourcemap.SourceMapsTranslator.Location;
import org.openide.filesystems.FileObject;
import org.openide.util.NbBundle;
import org.openide.util.Pair;
import org.openide.util.WeakSet;
/**
*
* @author Martin Entlicher
*/
public class BreakpointsHandler implements V8Debugger.Listener {
private static final Logger LOG = Logger.getLogger(BreakpointsHandler.class.getName());
private final V8Debugger dbg;
private final V8Debugger.CommandResponseCallback breakpointsCommandsCallback = new BreakpointsCommandsCallback();
private final Map<V8Arguments, Pair<JSLineBreakpoint, Location>> submittingBreakpoints = new HashMap<>();
private final Map<JSLineBreakpoint, SubmittedBreakpoint> submittedBreakpoints = new HashMap<>();
private final Map<Long, SubmittedBreakpoint> breakpointsById = new HashMap<>();
private final Set<JSLineBreakpoint> removeAfterSubmit = new WeakSet<>();
private final List<ActiveBreakpointListener> abListeners = new CopyOnWriteArrayList<ActiveBreakpointListener>();
private volatile JSLineBreakpoint activeBreakpoint;
private final List<BreakpointsActiveListener> acListeners = new CopyOnWriteArrayList<>();
private volatile boolean areBreakpointsActive = true;
public BreakpointsHandler(V8Debugger dbg) { // TODO pass in initially submitted breakpoints in the debuggee
this.dbg = dbg;
dbg.addListener(this);
}
@NbBundle.Messages({
"MSG_BRKP_Unresolved=Not resolved/inactive at current line."
})
public boolean add(JSLineBreakpoint lb) {
Pair<SetBreakpoint.Arguments, Location> srargsLoc = createSetRequestArguments(lb);
LOG.log(Level.FINE, "Adding {0}, args = {1}", new Object[]{lb, srargsLoc.first()});
if (srargsLoc == null) {
return false;
}
synchronized (submittingBreakpoints) {
submittingBreakpoints.put(srargsLoc.first(), Pair.of(lb, srargsLoc.second()));
}
JSBreakpointStatus.setInvalid(lb, Bundle.MSG_BRKP_Unresolved());
V8Request request = dbg.sendCommandRequest(V8Command.Setbreakpoint, srargsLoc.first(), breakpointsCommandsCallback);
LOG.log(Level.FINE, " request = {0}", request);
if (request == null) {
// Failed
synchronized (submittingBreakpoints) {
submittingBreakpoints.remove(srargsLoc.first());
}
return false;
} else {
return true;
}
}
public boolean remove(JSLineBreakpoint lb) {
SubmittedBreakpoint sb;
synchronized (submittedBreakpoints) {
sb = submittedBreakpoints.get(lb);
if (sb == null) {
removeAfterSubmit.add(lb);
return false;
}
}
sb.notifyDestroyed();
return requestRemove(lb, sb.getId());
}
private boolean requestRemove(JSLineBreakpoint lb, long id) {
ClearBreakpoint.Arguments cbargs = new ClearBreakpoint.Arguments(id);
V8Request request = dbg.sendCommandRequest(V8Command.Clearbreakpoint, cbargs);
LOG.log(Level.FINE, "Removing {0}, request = {1}", new Object[]{lb, request});
return request != null;
}
@CheckForNull
private Pair<SetBreakpoint.Arguments, Location> createSetRequestArguments(JSLineBreakpoint b) {
FileObject fo = b.getFileObject();
ScriptsHandler scriptsHandler = dbg.getScriptsHandler();
String serverPath = null;
long lineNumber = (long) b.getLineNumber() - 1;
Long columnNumber = null;
Location bpLocation = null;
if (fo != null) {
SourceMapsTranslator smt = dbg.getScriptsHandler().getSourceMapsTranslator();
if (smt != null) {
Location loc = new Location(fo, (int) lineNumber, 0);
Location cl = smt.getCompiledLocation(loc);
if (cl != loc) {
fo = cl.getFile();
lineNumber = cl.getLine();
columnNumber = (long) cl.getColumn();
if (lineNumber == 0) {
columnNumber += dbg.getScriptsHandler().getScriptFirstLineColumnShift(fo);
}
bpLocation = cl;
}
}
serverPath = scriptsHandler.getServerPath(fo);
} else { // Future BP
URL url = b.getURL();
if (scriptsHandler.containsRemoteFile(url)) {
serverPath = scriptsHandler.getServerPath(url);
}
}
if (serverPath == null) {
return null;
}
String condition = (b.isConditional()) ? b.getCondition() : null;
Long groupId = null; // TODO ?
SetBreakpoint.Arguments args = new SetBreakpoint.Arguments(
V8Breakpoint.Type.scriptName, serverPath,
lineNumber, columnNumber, b.isEnabled(),
condition, null, groupId);
return Pair.of(args, bpLocation);
}
static ChangeBreakpoint.Arguments createChangeRequestArguments(SubmittedBreakpoint sb) {
JSLineBreakpoint b = sb.getBreakpoint();
String condition = (b.isConditional()) ? b.getCondition() : null;
return new ChangeBreakpoint.Arguments(sb.getId(), b.isEnabled(), condition, null);
}
public void event(BreakEventBody beb) {
long[] ids = beb.getBreakpoints();
if (ids == null) {
return ;
}
for (long id : ids) {
SubmittedBreakpoint sb;
synchronized (submittedBreakpoints) {
sb = breakpointsById.get(id);
}
if (sb == null) {
continue;
}
JSLineBreakpoint b = sb.getBreakpoint();
setActiveBreakpoint(b);
}
}
@Override
public void notifySuspended(boolean suspended) {
if (!suspended) {
setActiveBreakpoint(null);
}
}
@Override
public void notifyCurrentFrame(CallFrame cf) {
}
@Override
public void notifyFinished() {
synchronized (submittingBreakpoints) {
submittingBreakpoints.clear();
}
Collection<SubmittedBreakpoint> sbs;
synchronized (submittedBreakpoints) {
sbs = new ArrayList<>(submittedBreakpoints.values());
submittedBreakpoints.clear();
breakpointsById.clear();
}
for (SubmittedBreakpoint sb : sbs) {
sb.notifyDestroyed();
}
setActiveBreakpoint(null);
}
public JSLineBreakpoint getActiveBreakpoint() {
return activeBreakpoint;
}
private void setActiveBreakpoint(JSLineBreakpoint activeBreakpoint) {
this.activeBreakpoint = activeBreakpoint;
JSBreakpointStatus.setActive(activeBreakpoint);
for (ActiveBreakpointListener abl : abListeners) {
abl.notifyActiveBreakpoint(activeBreakpoint);
}
}
public void addActiveBreakpointListener(ActiveBreakpointListener abl) {
abListeners.add(abl);
}
public void removeActiveBreakpointListener(ActiveBreakpointListener abl) {
abListeners.remove(abl);
}
boolean areBreakpointsActive() {
return areBreakpointsActive;
}
void setBreakpointsActive(final boolean active) {
Flags.Arguments args = new Flags.Arguments(Flags.FLAG_BREAK_POINTS_ACTIVE, active);
V8Request fRequest = dbg.sendCommandRequest(V8Command.Flags, args, new V8Debugger.CommandResponseCallback() {
@Override
public void notifyResponse(V8Request request, V8Response response) {
if (response != null && response.isSuccess()) {
areBreakpointsActive = active;
for (BreakpointsActiveListener acl : acListeners) {
acl.breakpointsActivated(active);
}
}
}
});
}
void addBreakpointsActiveListener(BreakpointsActiveListener abl) {
acListeners.add(abl);
}
void removeBreakpointsActiveListener(BreakpointsActiveListener abl) {
acListeners.remove(abl);
}
public void positionChanged(long bpId, long line, long column) {
SubmittedBreakpoint sb;
synchronized (submittedBreakpoints) {
sb = breakpointsById.get(bpId);
}
if (sb != null) {
sb.updatePosition(line, column);
}
}
/** Fired when an active (hit) breakpoint changes. */
public static interface ActiveBreakpointListener {
void notifyActiveBreakpoint(JSLineBreakpoint activeBreakpoint);
}
/** Fired when breakpoints are activated/deactivated */
static interface BreakpointsActiveListener {
void breakpointsActivated(boolean activated);
}
private final class BreakpointsCommandsCallback implements V8Debugger.CommandResponseCallback {
@NbBundle.Messages({
"MSG_BRKP_Resolved=Successfully resolved at current line."
})
@Override
public void notifyResponse(V8Request request, V8Response response) {
Pair<JSLineBreakpoint, Location> lbLoc;
synchronized (submittingBreakpoints) {
lbLoc = submittingBreakpoints.remove(request.getArguments());
}
if (lbLoc == null) {
LOG.log(Level.INFO, "Did not find a submitting breakpoint for response {0}, request was {1}",
new Object[]{response, request});
return ;
}
JSLineBreakpoint lb = lbLoc.first();
if (response != null) {
SetBreakpoint.ResponseBody sbrb = (SetBreakpoint.ResponseBody) response.getBody();
long id = sbrb.getBreakpoint();
Location bpLoc = lbLoc.second();
SubmittedBreakpoint sb = new SubmittedBreakpoint(lb, id, bpLoc, sbrb.getActualLocations(), dbg);
boolean removed;
synchronized (submittedBreakpoints) {
submittedBreakpoints.put(lb, sb);
breakpointsById.put(id, sb);
removed = removeAfterSubmit.remove(lb);
}
if (removed) {
requestRemove(lb, id);
sb.notifyDestroyed();
} else {
JSBreakpointStatus.setValid(lb, Bundle.MSG_BRKP_Resolved());
}
} else {
JSBreakpointStatus.setInvalid(lb, response.getErrorMessage());
}
}
}
}