/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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 com.google.dart.tools.debug.core.server;
import com.google.dart.tools.debug.core.DartDebugCorePlugin;
import com.google.dart.tools.debug.core.DartDebugCorePlugin.BreakOnExceptions;
import com.google.dart.tools.debug.core.breakpoints.DartBreakpoint;
import com.google.dart.tools.debug.core.server.VmConnection.BreakOnExceptionsType;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.runtime.IPath;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointListener;
import org.eclipse.debug.core.model.IBreakpoint;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
// TODO: handle removing deleted breakpoints
/**
* This breakpoint manager serves to off-load some of the breakpoint logic from ServerDebugTarget.
*/
class ServerBreakpointManager implements IBreakpointListener {
private class SetBreakpointHelper {
final VmIsolate isolate;
final DartBreakpoint breakpoint;
final int line;
public SetBreakpointHelper(VmIsolate isolate, DartBreakpoint breakpoint, int line) {
this.isolate = isolate;
this.breakpoint = breakpoint;
this.line = line;
}
void setBreakpoint(String url) throws IOException {
// we need some URL
if (url == null) {
return;
}
// try to set
connection.setBreakpoint(isolate, url, line, new VmCallback<VmBreakpoint>() {
@Override
public void handleResult(VmResult<VmBreakpoint> result) {
if (result.isError()) {
// We try to set all breakpoints, so we may set invalid breakpoint.
// So, ignore any errors.
} else {
addCreatedBreakpoint(breakpoint, result.getResult());
}
}
});
}
}
private final ServerDebugTarget target;
private final VmConnection connection;
private VmIsolate mainIsolate;
private List<IBreakpoint> ignoredBreakpoints = new ArrayList<IBreakpoint>();
Map<IBreakpoint, List<VmBreakpoint>> createdBreakpoints = new HashMap<IBreakpoint, List<VmBreakpoint>>();
List<VmIsolate> liveIsolates = new ArrayList<VmIsolate>();
public ServerBreakpointManager(ServerDebugTarget target) {
this.target = target;
this.connection = target.getConnection();
}
@Override
public void breakpointAdded(IBreakpoint breakpoint) {
if (supportsBreakpoint(breakpoint)) {
addBreakpoints(Arrays.asList((DartBreakpoint) breakpoint));
}
}
@Override
public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) {
if (ignoredBreakpoints.contains(breakpoint)) {
ignoredBreakpoints.remove(breakpoint);
return;
}
if (supportsBreakpoint(breakpoint)) {
breakpointRemoved(breakpoint, delta);
breakpointAdded(breakpoint);
}
}
@Override
public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) {
if (supportsBreakpoint(breakpoint)) {
List<VmBreakpoint> breakpoints = createdBreakpoints.remove(breakpoint);
if (breakpoints != null) {
try {
for (VmBreakpoint bp : breakpoints) {
connection.removeBreakpoint(bp.getIsolate(), bp);
}
} catch (IOException exception) {
DartDebugCorePlugin.logError(exception);
}
}
}
}
public void connect(VmIsolate mainIsolate) {
this.mainIsolate = mainIsolate;
liveIsolates.add(mainIsolate);
// Set up the existing breakpoints.
addBreakpoints(mainIsolate, getSupportedBreakpoints(), true);
DebugPlugin.getDefault().getBreakpointManager().addBreakpointListener(this);
}
public void dispose() {
if (DebugPlugin.getDefault() != null) {
DebugPlugin.getDefault().getBreakpointManager().removeBreakpointListener(this);
}
}
public void handleIsolateCreated(VmIsolate isolate) {
if (mainIsolate == null) {
return;
}
if (!liveIsolates.contains(isolate)) {
connectToIsolate(isolate);
liveIsolates.add(isolate);
}
}
public void handleIsolateShutdown(VmIsolate isolate) {
liveIsolates.remove(isolate);
disconnectFromIsolate(isolate);
}
public boolean hasBreakpointAtLine(VmLocation location) {
String locationUrl = location.getUrl();
if (locationUrl == null) {
return false;
}
// prepare line (requires lines table)
int locationLine = location.getLineNumber(connection);
// check every breakpoint's line + file
for (IBreakpoint _bp : createdBreakpoints.keySet()) {
if (_bp instanceof DartBreakpoint) {
DartBreakpoint bp = (DartBreakpoint) _bp;
if (bp.getLine() == locationLine) {
IFile bpFile = bp.getFile();
if (bpFile != null) {
URI bpUri = bpFile.getLocationURI();
String bpUriString = bpUri.toString();
if (bpUri != null && bpUriString.equals(locationUrl)) {
return true;
}
}
}
}
}
return false;
}
public void setPauseOnExceptionForLiveIsolates() {
BreakOnExceptionsType pauseType = getPauseType();
for (VmIsolate isolate : liveIsolates) {
try {
connection.setPauseOnException(isolate, pauseType);
} catch (IOException e) {
DartDebugCorePlugin.logError(e);
}
}
}
public void setPauseOnExceptionSync(VmIsolate isolate) {
BreakOnExceptionsType pauseType = getPauseType();
try {
connection.setPauseOnExceptionSync(isolate, pauseType);
} catch (IOException e) {
DartDebugCorePlugin.logError(e);
}
}
protected void addCreatedBreakpoint(DartBreakpoint breakpoint, VmBreakpoint result) {
List<VmBreakpoint> breakpoints = createdBreakpoints.get(breakpoint);
if (breakpoints == null) {
breakpoints = new ArrayList<VmBreakpoint>();
createdBreakpoints.put(breakpoint, breakpoints);
}
breakpoints.add(result);
}
protected DartBreakpoint getDartBreakpointFor(VmBreakpoint bp) {
for (IBreakpoint dartBreakpoint : createdBreakpoints.keySet()) {
List<VmBreakpoint> bps = createdBreakpoints.get(dartBreakpoint);
if (bps != null && bps.contains(bp)) {
return (DartBreakpoint) dartBreakpoint;
}
}
return null;
}
protected void handleBreakpointResolved(VmIsolate isolate, VmBreakpoint bp) {
// Update the corresponding editor breakpoint if the location has changed.
DartBreakpoint dartBreakpoint = getDartBreakpointFor(bp);
if (dartBreakpoint != null && isolate == mainIsolate) {
int lineNo = bp.getLocation().getLineNumber(connection);
if (lineNo != dartBreakpoint.getLine()) {
ignoredBreakpoints.add(dartBreakpoint);
String message = "[breakpoint in " + dartBreakpoint.getName() + " moved from line "
+ dartBreakpoint.getLine() + " to " + lineNo + "]";
target.writeToStdout(message);
dartBreakpoint.updateLineNumber(lineNo);
}
}
}
List<DartBreakpoint> getSupportedBreakpoints() {
IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(
DartDebugCorePlugin.DEBUG_MODEL_ID);
List<DartBreakpoint> bps = new ArrayList<DartBreakpoint>();
for (IBreakpoint breakpoint : breakpoints) {
if (target.supportsBreakpoint(breakpoint)) {
bps.add((DartBreakpoint) breakpoint);
}
}
return bps;
}
private void addBreakpoints(List<DartBreakpoint> breakpoints) {
for (VmIsolate isolate : liveIsolates) {
addBreakpoints(isolate, breakpoints, true);
}
}
private void addBreakpoints(VmIsolate isolate, final List<DartBreakpoint> breakpoints,
boolean pause) {
boolean enabled = false;
for (DartBreakpoint bp : breakpoints) {
enabled |= bp.isBreakpointEnabled();
}
if (!enabled) {
return;
}
try {
VmInterruptResult interruptResult = pause ? connection.interruptConditionally(isolate)
: VmInterruptResult.createNoopResult(connection);
for (DartBreakpoint breakpoint : breakpoints) {
if (breakpoint.isBreakpointEnabled()) {
int line = breakpoint.getLine();
SetBreakpointHelper helper = new SetBreakpointHelper(isolate, breakpoint, line);
// file path
String filePath = breakpoint.getActualFilePath();
if (filePath == null) {
continue;
}
// try package: URL
{
String url = getPackagesUrlForFilePath(filePath);
helper.setBreakpoint(url);
}
// try file:// URL
{
//String url = getAbsoluteUrlForFilePath(filePath);
IFile file = breakpoint.getFile();
String url = getProjectRelativePath(file);
helper.setBreakpoint(url);
}
}
}
interruptResult.resume();
} catch (IOException exception) {
DartDebugCorePlugin.logError(exception);
}
}
private void connectToIsolate(VmIsolate isolate) {
try {
VmInterruptResult interruptResult = connection.interruptConditionally(isolate);
connection.enableAllStepping(isolate);
// Set all existing breakpoints on the new isolate.
addBreakpoints(isolate, getSupportedBreakpoints(), false);
interruptResult.resume();
} catch (IOException exception) {
DartDebugCorePlugin.logError(exception);
}
}
private void disconnectFromIsolate(VmIsolate isolate) {
for (Entry<IBreakpoint, List<VmBreakpoint>> entry : createdBreakpoints.entrySet()) {
List<VmBreakpoint> breakpoints = entry.getValue();
for (Iterator<VmBreakpoint> bpIter = breakpoints.iterator(); bpIter.hasNext();) {
VmBreakpoint breakpoint = bpIter.next();
if (breakpoint.getIsolate() == isolate) {
bpIter.remove();
}
}
}
}
@SuppressWarnings("unused")
private String getAbsoluteUrlForFilePath(String filePath) {
return new File(filePath).toURI().toString();
}
private String getPackagesUrlForFilePath(String filePath) {
return target.getUriToFileResolver().getUriForPath(filePath);
}
private BreakOnExceptionsType getPauseType() {
BreakOnExceptions boe = DartDebugCorePlugin.getPlugin().getBreakOnExceptions();
BreakOnExceptionsType pauseType = BreakOnExceptionsType.none;
if (boe == BreakOnExceptions.uncaught) {
pauseType = BreakOnExceptionsType.unhandled;
} else if (boe == BreakOnExceptions.all) {
pauseType = BreakOnExceptionsType.all;
}
return pauseType;
}
private String getProjectRelativePath(IFile file) {
if (file != null) {
IPath path = file.getProjectRelativePath();
if (path != null) {
return path.toPortableString();
}
}
return null;
}
private boolean supportsBreakpoint(IBreakpoint breakpoint) {
return target.supportsBreakpoint(breakpoint);
}
}