/*
* BreakpointManager.java
*
* Copyright (C) 2009-12 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.studio.client.common.debugging;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
import org.rstudio.core.client.command.CommandBinder;
import org.rstudio.core.client.command.Handler;
import org.rstudio.core.client.js.JsObject;
import org.rstudio.core.client.widget.MessageDialog;
import org.rstudio.core.client.widget.Operation;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.application.events.RestartStatusEvent;
import org.rstudio.studio.client.common.FilePathUtils;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.debugging.events.BreakpointsSavedEvent;
import org.rstudio.studio.client.common.debugging.events.PackageLoadedEvent;
import org.rstudio.studio.client.common.debugging.events.PackageUnloadedEvent;
import org.rstudio.studio.client.common.debugging.model.Breakpoint;
import org.rstudio.studio.client.common.debugging.model.BreakpointState;
import org.rstudio.studio.client.common.debugging.model.FunctionState;
import org.rstudio.studio.client.common.debugging.model.FunctionSteps;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.server.Void;
import org.rstudio.studio.client.server.VoidServerRequestCallback;
import org.rstudio.studio.client.workbench.WorkbenchContext;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.events.SessionInitEvent;
import org.rstudio.studio.client.workbench.events.SessionInitHandler;
import org.rstudio.studio.client.workbench.model.ClientState;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
import org.rstudio.studio.client.workbench.views.console.events.ConsoleWriteInputEvent;
import org.rstudio.studio.client.workbench.views.console.events.ConsoleWriteInputHandler;
import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
import org.rstudio.studio.client.workbench.views.environment.events.ContextDepthChangedEvent;
import org.rstudio.studio.client.workbench.views.environment.events.DebugSourceCompletedEvent;
import org.rstudio.studio.client.workbench.views.environment.model.CallFrame;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import com.google.inject.Inject;
import com.google.inject.Singleton;
// Provides management for breakpoints.
//
// The typical workflow for interactively adding a new breakpoint is as follows:
// 1) The user clicks on the gutter of the editor, which generates an editor
// event (BreakpointSetEvent)
// 2) The editing target (which maintains a reference to the breakpoint manager)
// asks the manager to create a breakpoint, and passes the new breakpoint
// back to the editing surface (addOrUpdateBreakpoint)
// 3) The breakpoint manager checks to see whether the source code for the
// function on disk is identical to the source code for the function as
// it exists in the R session (get_function_sync_state). If it isn't,
// it defers setting the breakpoint.
// 4) The breakpoint manager fetches the steps and substeps of the function in
// which the breakpoint occurs from the server, and updates the breakpoint
// with this information (get_function_steps)
// 5) The breakpoint manager combines the breakpoint with all of the other
// breakpoints for the function, and makes a single call to the server to
// update the function's breakpoints (set_function_breakpoints)
// 6) If successful, the breakpoint manager emits a BreakpointsSavedEvent, which
// is picked up by the editing target, which updates the display to show that
// the breakpoint is now enabled.
@Singleton
public class BreakpointManager
implements SessionInitHandler,
ContextDepthChangedEvent.Handler,
PackageLoadedEvent.Handler,
PackageUnloadedEvent.Handler,
ConsoleWriteInputHandler,
RestartStatusEvent.Handler,
DebugSourceCompletedEvent.Handler
{
public interface Binder
extends CommandBinder<Commands, BreakpointManager> {}
@Inject
public BreakpointManager(
DebuggingServerOperations server,
EventBus events,
Session session,
WorkbenchContext workbench,
Binder binder,
Commands commands,
GlobalDisplay globalDisplay)
{
server_ = server;
events_ = events;
session_ = session;
workbench_ = workbench;
globalDisplay_ = globalDisplay;
commands_ = commands;
commands_.debugClearBreakpoints().setEnabled(false);
// this singleton class is constructed before the session is initialized,
// so wait until the session init happens to grab our persisted state
events_.addHandler(SessionInitEvent.TYPE, this);
events_.addHandler(ConsoleWriteInputEvent.TYPE, this);
events_.addHandler(ContextDepthChangedEvent.TYPE, this);
events_.addHandler(PackageLoadedEvent.TYPE, this);
events_.addHandler(PackageUnloadedEvent.TYPE, this);
events_.addHandler(RestartStatusEvent.TYPE, this);
events_.addHandler(DebugSourceCompletedEvent.TYPE, this);
binder.bind(commands, this);
}
// Public methods ---------------------------------------------------------
public Breakpoint setTopLevelBreakpoint(
final String path,
final int lineNumber)
{
final Breakpoint breakpoint = addBreakpoint(Breakpoint.create(
currentBreakpointId_++,
path,
"toplevel",
lineNumber,
path.equals(activeSource_) ?
Breakpoint.STATE_INACTIVE :
Breakpoint.STATE_ACTIVE,
Breakpoint.TYPE_TOPLEVEL));
// If we're actively sourcing this file, we can't set breakpoints in
// it just yet
if (path.equals(activeSource_))
breakpoint.setPendingDebugCompletion(true);
notifyServer(breakpoint, true, true);
ArrayList<Breakpoint> bps = new ArrayList<Breakpoint>();
bps.add(breakpoint);
return breakpoint;
}
public Breakpoint setBreakpoint(
final String path,
final String functionName,
int lineNumber,
final boolean immediately)
{
// create the new breakpoint and arguments for the server call
final Breakpoint breakpoint = addBreakpoint(Breakpoint.create(
currentBreakpointId_++,
path,
functionName,
lineNumber,
immediately ?
Breakpoint.STATE_PROCESSING :
Breakpoint.STATE_INACTIVE,
Breakpoint.TYPE_FUNCTION));
notifyServer(breakpoint, true, false);
// If the breakpoint is in a function that is active on the callstack,
// it's being set on the stored rather than the executing copy. It's
// possible to set it right now, but it will probably violate user
// expectations. Process it when the function is no longer executing.
if (activeFunctions_.contains(
new FileFunction(breakpoint)))
{
breakpoint.setPendingDebugCompletion(true);
markInactiveBreakpoint(breakpoint);
}
else
{
server_.getFunctionState(functionName, path, lineNumber,
new ServerRequestCallback<FunctionState>()
{
@Override
public void onResponseReceived(FunctionState state)
{
if (state.isPackageFunction())
{
breakpoint.markAsPackageBreakpoint(state.getPackageName());
}
// If the breakpoint is not to be set immediately,
// stop processing now
if (!immediately)
return;
// if the function lines up with the version on the server, set
// the breakpoint now
if (state.getSyncState())
{
prepareAndSetFunctionBreakpoints(
new FileFunction(breakpoint));
}
// otherwise, save an inactive breakpoint--we'll revisit the
// marker the next time the file is sourced or the package is
// rebuilt
else
{
markInactiveBreakpoint(breakpoint);
}
}
@Override
public void onError(ServerError error)
{
// if we can't figure out whether the function is in sync,
// leave it inactive for now
markInactiveBreakpoint(breakpoint);
}
});
}
breakpointStateDirty_ = true;
return breakpoint;
}
public void removeBreakpoint(int breakpointId)
{
Breakpoint breakpoint = getBreakpoint(breakpointId);
if (breakpoint != null)
{
breakpoints_.remove(breakpoint);
if (breakpoint.getState() == Breakpoint.STATE_ACTIVE &&
breakpoint.getType() == Breakpoint.TYPE_FUNCTION)
{
setFunctionBreakpoints(new FileFunction(breakpoint));
}
notifyServer(breakpoint, false,
breakpoint.getType() == Breakpoint.TYPE_TOPLEVEL);
}
onBreakpointAddOrRemove();
}
public void moveBreakpoint(int breakpointId)
{
// because of Java(Script)'s reference semantics, the editor's instance
// of the breakpoint object is the same one we have here, so we don't
// need to update the line number--we just need to persist the new state.
breakpointStateDirty_ = true;
// the breakpoint knows its position in the function, which needs to be
// recalculated; do that the next time we set breakpoints on this function
Breakpoint breakpoint = getBreakpoint(breakpointId);
if (breakpoint != null)
{
breakpoint.markStepsNeedUpdate();
notifyServer(breakpoint, true, false);
}
}
public ArrayList<Breakpoint> getBreakpointsInFile(String fileName)
{
ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>();
for (Breakpoint breakpoint: breakpoints_)
{
if (breakpoint.isInFile(fileName))
{
breakpoints.add(breakpoint);
}
}
return breakpoints;
}
// Event handlers ----------------------------------------------------------
@Override
public void onSessionInit(SessionInitEvent sie)
{
// Establish a persistent object for the breakpoints. Note that this
// object is read by the server on init, so the scope/name pair here
// needs to match the pair on the server.
new JSObjectStateValue(
"debug-breakpoints",
"debugBreakpointsState",
ClientState.PROJECT_PERSISTENT,
session_.getSessionInfo().getClientState(),
false)
{
@Override
protected void onInit(JsObject value)
{
if (value != null)
{
BreakpointState state = value.cast();
// restore all of the breakpoints
JsArray<Breakpoint> breakpoints =
state.getPersistedBreakpoints();
for (int idx = 0; idx < breakpoints.length(); idx++)
{
Breakpoint breakpoint = breakpoints.get(idx);
// make sure the next breakpoint we create after a restore
// has a value larger than any existing breakpoint
currentBreakpointId_ = Math.max(
currentBreakpointId_,
breakpoint.getBreakpointId() + 1);
addBreakpoint(breakpoint);
}
// this initialization happens after the source windows are
// up, so fire an event to the editor to show all known
// breakpoints. as new source windows are opened, they will
// call getBreakpointsInFile to populate themselves.
events_.fireEvent(
new BreakpointsSavedEvent(breakpoints_, true));
}
}
@Override
protected JsObject getValue()
{
BreakpointState state =
BreakpointState.create();
for (Breakpoint breakpoint: breakpoints_)
{
state.addPersistedBreakpoint(breakpoint);
}
breakpointStateDirty_ = false;
return state.cast();
}
@Override
protected boolean hasChanged()
{
return breakpointStateDirty_;
}
};
}
@Override
public void onConsoleWriteInput(ConsoleWriteInputEvent event)
{
// when a file is sourced, replay all the breakpoints in the file.
RegExp sourceExp = RegExp.compile("source(.with.encoding)?\\('([^']*)'.*");
MatchResult fileMatch = sourceExp.exec(event.getInput());
if (fileMatch == null || fileMatch.getGroupCount() == 0)
{
return;
}
String path = FilePathUtils.normalizePath(
fileMatch.getGroup(2),
workbench_.getCurrentWorkingDir().getPath());
resetBreakpointsInPath(path, true);
}
@Override
public void onDebugSourceCompleted(DebugSourceCompletedEvent event)
{
if (event.getSucceeded())
{
resetBreakpointsInPath(
FilePathUtils.normalizePath(
event.getPath(),
workbench_.getCurrentWorkingDir().getPath()),
true);
}
}
@Override
public void onContextDepthChanged(ContextDepthChangedEvent event)
{
// When we move around in debug context and hit a breakpoint, the initial
// evaluation state is a temporary construction that needs to be stepped
// past to begin actually evaluating the function. Step past it
// immediately.
JsArray<CallFrame> frames = event.getCallFrames();
Set<FileFunction> activeFunctions = new TreeSet<FileFunction>();
boolean hasSourceEquiv = false;
for (int idx = 0; idx < frames.length(); idx++)
{
CallFrame frame = frames.get(idx);
String functionName = frame.getFunctionName();
String fileName = frame.getFileName();
if (functionName.equals(".doTrace") &&
event.isServerInitiated())
{
events_.fireEvent(new SendToConsoleEvent(
DebugCommander.NEXT_COMMAND, true));
}
activeFunctions.add(
new FileFunction(functionName, fileName, "", false));
if (frame.isSourceEquiv())
{
activeSource_ = fileName;
hasSourceEquiv = true;
}
}
// For any functions that were previously active in the callstack but
// are no longer active, enable any pending breakpoints for those
// functions.
Set<FileFunction> enableFunctions = new TreeSet<FileFunction>();
for (FileFunction function: activeFunctions_)
{
if (!activeFunctions.contains(function))
{
for (Breakpoint breakpoint: breakpoints_)
{
if (breakpoint.isPendingDebugCompletion() &&
breakpoint.getState() == Breakpoint.STATE_INACTIVE &&
function.containsBreakpoint(breakpoint))
{
enableFunctions.add(function);
}
}
}
}
for (FileFunction function: enableFunctions)
{
prepareAndSetFunctionBreakpoints(function);
}
// Record the new frame list.
activeFunctions_ = activeFunctions;
// When we finish executing a top-level source, activate the top-level
// breakpoints in the file we were sourcing.
if (!hasSourceEquiv && activeSource_ != null)
{
activateTopLevelBreakpoints(activeSource_);
activeSource_ = null;
}
}
@Handler
public void onDebugClearBreakpoints()
{
globalDisplay_.showYesNoMessage(
MessageDialog.QUESTION,
"Clear All Breakpoints",
"Are you sure you want to remove all the breakpoints in this " +
"project?",
new Operation() {
@Override
public void execute()
{
clearAllBreakpoints();
}
},
false);
}
@Override
public void onPackageLoaded(PackageLoadedEvent event)
{
updatePackageBreakpoints(event.getPackageName(), true);
}
@Override
public void onPackageUnloaded(PackageUnloadedEvent event)
{
updatePackageBreakpoints(event.getPackageName(), false);
}
@Override
public void onRestartStatus(RestartStatusEvent event)
{
if (event.getStatus() == RestartStatusEvent.RESTART_INITIATED)
{
// Restarting R unloads all the packages, so mark all active package
// breakpoints as inactive when this happens.
ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>();
for (Breakpoint breakpoint: breakpoints_)
{
if (breakpoint.isPackageBreakpoint())
{
breakpoint.setState(Breakpoint.STATE_INACTIVE);
breakpoints.add(breakpoint);
}
}
notifyBreakpointsSaved(breakpoints, true);
}
}
// Private methods ---------------------------------------------------------
private void setFunctionBreakpoints(FileFunction function)
{
ArrayList<String> steps = new ArrayList<String>();
final ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>();
for (Breakpoint breakpoint: breakpoints_)
{
if (function.containsBreakpoint(breakpoint))
{
steps.add(breakpoint.getFunctionSteps());
breakpoints.add(breakpoint);
}
}
server_.setFunctionBreakpoints(
function.functionName,
function.fileName,
function.packageName,
steps,
new ServerRequestCallback<Void>()
{
@Override
public void onResponseReceived(Void v)
{
for (Breakpoint breakpoint: breakpoints)
{
breakpoint.setState(Breakpoint.STATE_ACTIVE);
}
notifyBreakpointsSaved(breakpoints, true);
}
@Override
public void onError(ServerError error)
{
discardUnsettableBreakpoints(breakpoints);
}
});
}
private void prepareAndSetFunctionBreakpoints(final FileFunction function)
{
// look over the list of breakpoints in this function and see if any are
// marked inactive, or if they need their steps refreshed (necessary
// when a function has had steps added or removed in the editor)
final ArrayList<Breakpoint> inactiveBreakpoints =
new ArrayList<Breakpoint>();
int[] inactiveLines = new int[]{};
int numLines = 0;
for (Breakpoint breakpoint: breakpoints_)
{
if (function.containsBreakpoint(breakpoint) &&
(breakpoint.getState() != Breakpoint.STATE_ACTIVE ||
breakpoint.needsUpdatedSteps()))
{
inactiveBreakpoints.add(breakpoint);
inactiveLines[numLines++] = breakpoint.getLineNumber();
}
}
// if we found breakpoints that aren't yet active, try to get the
// corresponding steps from the function
if (inactiveBreakpoints.size() > 0)
{
server_.getFunctionSteps(
function.functionName,
function.fileName,
function.packageName,
inactiveLines,
new ServerRequestCallback<JsArray<FunctionSteps>> () {
@Override
public void onResponseReceived
(JsArray<FunctionSteps> response)
{
// found the function and the steps in the function; next,
// ask the server to set the breakpoint
if (response.length() > 0)
{
processFunctionSteps(inactiveBreakpoints, response);
setFunctionBreakpoints(function);
}
// no results: discard the breakpoints
else
{
discardUnsettableBreakpoints(inactiveBreakpoints);
}
}
@Override
public void onError(ServerError error)
{
discardUnsettableBreakpoints(inactiveBreakpoints);
}
});
}
else
{
setFunctionBreakpoints(function);
}
}
private void discardUnsettableBreakpoints(ArrayList<Breakpoint> breakpoints)
{
if (breakpoints.size() == 0)
{
return;
}
for (Breakpoint breakpoint: breakpoints)
{
breakpoints_.remove(breakpoint);
}
onBreakpointAddOrRemove();
notifyBreakpointsSaved(breakpoints, false);
}
private void resetBreakpointsInPath(String path, boolean isFile)
{
Set<FileFunction> functionsToBreak = new TreeSet<FileFunction>();
for (Breakpoint breakpoint: breakpoints_)
{
// set this breakpoint if it's a function breakpoint in the file
// (or path) given
boolean processBreakpoint =
(breakpoint.getType() == Breakpoint.TYPE_FUNCTION) &&
(isFile ?
breakpoint.isInFile(path) :
breakpoint.isInPath(path));
if (processBreakpoint)
{
functionsToBreak.add(new FileFunction(breakpoint));
}
}
for (FileFunction function: functionsToBreak)
{
prepareAndSetFunctionBreakpoints(function);
}
}
private void markInactiveBreakpoint(Breakpoint breakpoint)
{
breakpoint.setState(Breakpoint.STATE_INACTIVE);
ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>();
breakpoints.add(breakpoint);
notifyBreakpointsSaved(breakpoints, true);
}
private void processFunctionSteps(
ArrayList<Breakpoint> breakpoints,
JsArray<FunctionSteps> stepList)
{
ArrayList<Breakpoint> unSettableBreakpoints =
new ArrayList<Breakpoint>();
// Walk through the array of breakpoints for which we requested function
// steps and the array of results from the server in lock-step, populating
// each breakpoint with its steps.
for (int i = 0; i < breakpoints.size() &&
i < stepList.length(); i++)
{
FunctionSteps steps = stepList.get(i);
Breakpoint breakpoint = breakpoints.get(i);
if (steps.getSteps().length() > 0)
{
// if the server set this breakpoint on a different line than
// requested, make sure there's not already a breakpoint on that
// line; if there is, discard this one.
if (breakpoint.getLineNumber() != steps.getLineNumber())
{
for (Breakpoint possibleDupe: breakpoints_)
{
if (breakpoint.getPath().equals(
possibleDupe.getPath()) &&
steps.getLineNumber() ==
possibleDupe.getLineNumber() &&
breakpoint.getBreakpointId() !=
possibleDupe.getBreakpointId())
{
breakpoint.setState(Breakpoint.STATE_REMOVING);
unSettableBreakpoints.add(breakpoint);
}
}
}
breakpoint.addFunctionSteps(steps.getName(),
steps.getLineNumber(),
steps.getSteps());
}
else
{
unSettableBreakpoints.add(breakpoint);
}
}
discardUnsettableBreakpoints(unSettableBreakpoints);
}
private void notifyBreakpointsSaved(
ArrayList<Breakpoint> breakpoints,
boolean saved)
{
breakpointStateDirty_ = true;
events_.fireEvent(
new BreakpointsSavedEvent(breakpoints, saved));
}
private Breakpoint getBreakpoint (int breakpointId)
{
for (Breakpoint breakpoint: breakpoints_)
{
if (breakpoint.getBreakpointId() == breakpointId)
{
return breakpoint;
}
}
return null;
}
private Breakpoint addBreakpoint (Breakpoint breakpoint)
{
breakpoints_.add(breakpoint);
onBreakpointAddOrRemove();
return breakpoint;
}
private void updatePackageBreakpoints(String packageName, boolean enable)
{
Set<FileFunction> functionsToBreak = new TreeSet<FileFunction>();
ArrayList<Breakpoint> breakpointsToDisable = new ArrayList<Breakpoint>();
for (Breakpoint breakpoint: breakpoints_)
{
if (breakpoint.isPackageBreakpoint() &&
breakpoint.getPackageName().equals(packageName))
{
if (enable)
{
functionsToBreak.add(new FileFunction(breakpoint));
}
else
{
breakpoint.setState(Breakpoint.STATE_INACTIVE);
breakpointsToDisable.add(breakpoint);
}
}
}
if (enable)
{
for (FileFunction function: functionsToBreak)
{
prepareAndSetFunctionBreakpoints(function);
}
}
else
{
notifyBreakpointsSaved(breakpointsToDisable, true);
}
}
private void clearAllBreakpoints()
{
Set<FileFunction> functions = new TreeSet<FileFunction>();
for (Breakpoint breakpoint: breakpoints_)
{
breakpoint.setState(Breakpoint.STATE_REMOVING);
if (breakpoint.getType () == Breakpoint.TYPE_FUNCTION)
functions.add(new FileFunction(breakpoint));
}
// Remove the breakpoints from each unique function that had breakpoints
// set previously
for (FileFunction function: functions)
{
server_.setFunctionBreakpoints(
function.functionName,
function.fileName,
function.packageName,
new ArrayList<String>(),
new ServerRequestCallback<Void>()
{
@Override
public void onError(ServerError error)
{
// There's a possibility here that the breakpoints were
// not successfully cleared, so we may be in a temporarily
// confusing state, but no error message will be less
// confusing.
}
});
}
server_.removeAllBreakpoints(new VoidServerRequestCallback());
notifyBreakpointsSaved(new ArrayList<Breakpoint>(breakpoints_), false);
breakpoints_.clear();
onBreakpointAddOrRemove();
}
private void onBreakpointAddOrRemove()
{
breakpointStateDirty_ = true;
commands_.debugClearBreakpoints().setEnabled(breakpoints_.size() > 0);
}
private void notifyServer(Breakpoint breakpoint, boolean added, boolean arm)
{
ArrayList<Breakpoint> bps = new ArrayList<Breakpoint>();
bps.add(breakpoint);
server_.updateBreakpoints(bps, added, arm,
new VoidServerRequestCallback());
}
private void activateTopLevelBreakpoints(String path)
{
for (Breakpoint breakpoint: breakpoints_)
{
ArrayList<Breakpoint> activatedBreakpoints =
new ArrayList<Breakpoint>();
if (breakpoint.isPendingDebugCompletion() &&
breakpoint.getState() == Breakpoint.STATE_INACTIVE &&
breakpoint.getType() == Breakpoint.TYPE_TOPLEVEL &&
breakpoint.getPath().equals(path))
{
// If this is a top-level breakpoint in the file that we
// just finished sourcing, activate the breakpoint.
breakpoint.setPendingDebugCompletion(false);
breakpoint.setState(Breakpoint.STATE_ACTIVE);
activatedBreakpoints.add(breakpoint);
}
if (activatedBreakpoints.size() > 0)
notifyBreakpointsSaved(activatedBreakpoints, true);
}
}
// Private classes ---------------------------------------------------------
class FileFunction implements Comparable<FileFunction>
{
public String functionName;
public String fileName;
public String packageName;
boolean fullPath;
public FileFunction (
String fun, String file, String pkg, boolean useFullPath)
{
functionName = fun;
fileName = file.trim();
packageName = pkg;
fullPath = useFullPath;
}
public FileFunction (String fun, String file, String pkg)
{
this(fun, file, pkg, true);
}
public FileFunction (Breakpoint breakpoint)
{
this(breakpoint.getFunctionName(),
breakpoint.getPath(),
breakpoint.getPackageName());
}
public boolean containsBreakpoint(Breakpoint breakpoint)
{
if (!breakpoint.getFunctionName().equals(functionName))
{
return false;
}
if (fullPath)
{
return breakpoint.getPath().equals(fileName);
}
else
{
return FilePathUtils.friendlyFileName(breakpoint.getPath()).equals(
FilePathUtils.friendlyFileName(fileName));
}
}
@Override
public int compareTo(FileFunction other)
{
int fun = functionName.compareTo(other.functionName);
if (fun != 0)
{
return fun;
}
if (!fullPath || !other.fullPath)
{
return FilePathUtils.friendlyFileName(fileName).compareTo(
FilePathUtils.friendlyFileName(other.fileName));
}
return fileName.compareTo(other.fileName);
}
}
private final DebuggingServerOperations server_;
private final EventBus events_;
private final Session session_;
private final WorkbenchContext workbench_;
private final GlobalDisplay globalDisplay_;
private final Commands commands_;
private ArrayList<Breakpoint> breakpoints_ = new ArrayList<Breakpoint>();
private Set<FileFunction> activeFunctions_ = new TreeSet<FileFunction>();
private String activeSource_;
private boolean breakpointStateDirty_ = false;
private int currentBreakpointId_ = 0;
}