| /* |
| * 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()); |
| } |
| } |
| |
| } |
| |
| } |