/*
* Copyright (c) 2012, 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.core.NotYetImplementedException;
import com.google.dart.tools.debug.core.DartDebugCorePlugin;
import com.google.dart.tools.debug.core.DartLaunchConfigWrapper;
import com.google.dart.tools.debug.core.breakpoints.DartBreakpoint;
import com.google.dart.tools.debug.core.source.UriToFileResolver;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.IBreakpointManagerListener;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.debug.core.model.IThread;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* An implementation of IDebugTarget for Dart VM debug connections.
*/
public class ServerDebugTarget extends ServerDebugElement implements IDebugTarget, VmListener,
IBreakpointManagerListener {
private static ServerDebugTarget activeTarget;
public static ServerDebugTarget getActiveTarget() {
return activeTarget;
}
private static void setActiveTarget(ServerDebugTarget target) {
activeTarget = target;
}
private ILaunch launch;
private IProcess process;
private VmConnection connection;
private List<ServerDebugThread> threads = new ArrayList<ServerDebugThread>();
private boolean firstBreak = true;
private ServerBreakpointManager breakpointManager;
private VmIsolate currentIsolate;
@SuppressWarnings("unused")
private IProject currentProject;
private UriToFileResolver uriToFileResolver;
private IEclipsePreferences preferences;
private IPreferenceChangeListener preferenceListener;
public ServerDebugTarget(ILaunch launch, IProcess process, int connectionPort) {
this(launch, process, null, connectionPort);
}
public ServerDebugTarget(ILaunch launch, IProcess process, String connectionHost,
int connectionPort) {
super(null);
setActiveTarget(this);
this.launch = launch;
this.process = process;
DartLaunchConfigWrapper wrapper = new DartLaunchConfigWrapper(launch.getLaunchConfiguration());
currentProject = wrapper.getProject();
uriToFileResolver = new UriToFileResolver(launch);
connection = new VmConnection(connectionHost, connectionPort);
connection.addListener(this);
breakpointManager = new ServerBreakpointManager(this);
// listen for preference changes
preferences = DartDebugCorePlugin.getPlugin().getPrefs();
preferenceListener = new IPreferenceChangeListener() {
@Override
public void preferenceChange(PreferenceChangeEvent event) {
handlePreferenceChange(event);
}
};
preferences.addPreferenceChangeListener(preferenceListener);
}
@Override
public void breakpointAdded(IBreakpoint breakpoint) {
throw new NotYetImplementedException();
}
@Override
public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) {
throw new NotYetImplementedException();
}
@Override
public void breakpointManagerEnablementChanged(boolean enabled) {
setBreakpointsActive(enabled);
}
@Override
public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) {
throw new NotYetImplementedException();
}
@Override
public void breakpointResolved(VmIsolate isolate, VmBreakpoint breakpoint) {
breakpointManager.handleBreakpointResolved(isolate, breakpoint);
}
@Override
public boolean canDisconnect() {
return false;
}
@Override
public boolean canResume() {
return false;
}
@Override
public boolean canSuspend() {
return false;
}
@Override
public boolean canTerminate() {
return process.canTerminate();
}
/**
* Connect to the debugger instance at connectionPort port.
*
* @throws DebugException
*/
public void connect() throws DebugException {
long timeout = 10000;
long startTime = System.currentTimeMillis();
try {
sleep(50);
while (true) {
try {
connection.connect();
break;
} catch (IOException ioe) {
if (process.isTerminated()) {
throw ioe;
}
if (System.currentTimeMillis() > startTime + timeout) {
throw ioe;
} else {
sleep(25);
}
}
}
} catch (IOException ioe) {
throw new DebugException(new Status(
IStatus.ERROR,
DartDebugCorePlugin.PLUGIN_ID,
"Unable to connect debugger to the Dart VM: " + ioe.getMessage()));
}
}
@Override
public void connectionClosed(VmConnection connection) {
fireTerminateEvent();
}
@Override
public void connectionOpened(VmConnection connection) {
IBreakpointManager eclipseBpManager = DebugPlugin.getDefault().getBreakpointManager();
eclipseBpManager.addBreakpointManagerListener(this);
if (!eclipseBpManager.isEnabled()) {
setBreakpointsActive(false);
}
}
@Override
public void debuggerPaused(PausedReason reason, VmIsolate isolate, List<VmCallFrame> frames,
VmValue exception) {
currentIsolate = isolate;
boolean resumed = false;
if (firstBreak) {
firstBreak = false;
// Init everything.
firstIsolateInit(isolate);
}
if (PausedReason.breakpoint == reason) {
//DartBreakpoint breakpoint = getBreakpointFor(frames);
// If this is our first break then resume.
if (isolate.isFirstBreak()) {
isolate.setFirstBreak(false);
breakpointManager.handleIsolateCreated(isolate);
if (!hasBreakpointAtTopFrame(frames)) {
resumed = true;
try {
getVmConnection().resume(isolate);
} catch (IOException ioe) {
DartDebugCorePlugin.logError(ioe);
}
}
}
}
if (!resumed) {
ServerDebugThread thread = findThread(isolate);
if (thread != null) {
if (exception != null) {
printExceptionToStdout(exception);
}
thread.handleDebuggerPaused(reason, frames, exception);
}
}
}
@Override
public void debuggerResumed(VmIsolate isolate) {
ServerDebugThread thread = findThread(isolate);
if (thread != null) {
thread.handleDebuggerResumed();
}
}
@Override
public void disconnect() throws DebugException {
throw new UnsupportedOperationException("disconnect is not supported");
}
@Override
public void fireTerminateEvent() {
setActiveTarget(null);
dispose();
threads.clear();
// Check for null on system shutdown.
if (DebugPlugin.getDefault() != null) {
super.fireTerminateEvent();
}
}
@Override
public IDebugTarget getDebugTarget() {
return this;
}
@Override
public ILaunch getLaunch() {
return launch;
}
@Override
public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException {
return null;
}
@Override
public String getName() throws DebugException {
return "dart";
}
@Override
public IProcess getProcess() {
return process;
}
@Override
public IThread[] getThreads() throws DebugException {
return threads.toArray(new IThread[threads.size()]);
// if (debugThread == null) {
// return new IThread[0];
// } else {
// return new IThread[] {debugThread};
// }
}
public UriToFileResolver getUriToFileResolver() {
return uriToFileResolver;
}
public VmConnection getVmConnection() {
return connection;
}
@Override
public boolean hasThreads() throws DebugException {
return !isTerminated();
}
@Override
public boolean isDisconnected() {
return process.isTerminated();
}
@Override
public void isolateCreated(VmIsolate isolate) {
addThread(new ServerDebugThread(this, isolate));
try {
connection.enableAllStepping(isolate);
} catch (IOException e) {
DartDebugCorePlugin.logError(e);
}
breakpointManager.setPauseOnExceptionSync(isolate);
}
@Override
public void isolateShutdown(VmIsolate isolate) {
removeThread(findThread(isolate));
breakpointManager.handleIsolateShutdown(isolate);
}
@Override
public boolean isSuspended() {
// Create a copy of the threads before iterating.
for (IThread thread : new ArrayList<IThread>(threads)) {
if (thread.isSuspended()) {
return true;
}
}
return false;
}
@Override
public boolean isTerminated() {
return process.isTerminated();
}
@Override
public void resume() throws DebugException {
}
@Override
public boolean supportsBreakpoint(IBreakpoint breakpoint) {
// Any breakpoint, we don't know in general case if some project is used or not.
// E.g. when package root is used, we might end up referencing a project,
// not an external resource.
return true;
// if (!(breakpoint instanceof DartBreakpoint)) {
// return false;
// }
// DartBreakpoint dartBreakpoint = (DartBreakpoint) breakpoint;
// IFile file = dartBreakpoint.getFile();
// // external file?
// if (currentProject == null || file == null) {
// return true;
// }
// // check the project
// IProject project = file.getProject();
// // file from the same project, or a Pub package project
// if (currentProject.equals(project)) {
// return true;
// }
// // OK, a Pub package project
// if (PubCacheManager_NEW.isPubCacheProject(project)) {
// return true;
// }
// // a breakpoint from another project
// return false;
}
@Override
public boolean supportsStorageRetrieval() {
return false;
}
@Override
public void suspend() throws DebugException {
}
@Override
public void terminate() throws DebugException {
process.terminate();
uriToFileResolver.dispose();
}
public void writeToStdout(String message) {
fireStreamAppended(message + "\n");
}
protected ServerDebugThread findThread(VmIsolate isolate) {
for (ServerDebugThread thread : threads) {
if (thread.getIsolate().getId() == isolate.getId()) {
return thread;
}
}
return null;
}
protected DartBreakpoint getBreakpointFor(List<VmCallFrame> frames) {
if (frames.size() == 0) {
return null;
}
return getBreakpointFor(frames.get(0));
}
protected DartBreakpoint getBreakpointFor(VmCallFrame frame) {
return getBreakpointFor(frame.getLocation());
}
protected VmIsolate getCurrentIsolate() {
return currentIsolate;
}
@Override
protected ServerDebugTarget getTarget() {
return this;
}
private void addThread(ServerDebugThread thread) {
threads.add(thread);
thread.fireCreationEvent();
}
private void dispose() {
preferences.removePreferenceChangeListener(preferenceListener);
if (connection != null) {
connection.handleTerminated();
}
breakpointManager.dispose();
}
private void fireStreamAppended(String message) {
IStreamMonitor monitor = getProcess().getStreamsProxy().getOutputStreamMonitor();
try {
// monitor.fireStreamAppended(message);
Method method = getMethod(monitor, "fireStreamAppended");
if (method != null) {
method.invoke(monitor, message);
}
} catch (Throwable t) {
DartDebugCorePlugin.logInfo(message);
}
}
private void firstIsolateInit(VmIsolate isolate) {
breakpointManager.connect(isolate);
}
private DartBreakpoint getBreakpointFor(VmLocation location) {
if (location == null) {
return null;
}
IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(
DartDebugCorePlugin.DEBUG_MODEL_ID);
String url = location.getUrl();
int line = location.getLineNumber(getConnection());
for (IBreakpoint bp : breakpoints) {
if (getTarget().supportsBreakpoint(bp)) {
DartBreakpoint breakpoint = (DartBreakpoint) bp;
if (breakpoint.getLine() == line) {
IFile file = breakpoint.getFile();
if (file != null) {
String bpUrl = file.getLocationURI().toString();
if (bpUrl.equals(url)) {
return breakpoint;
}
}
}
}
}
return null;
}
private Method getMethod(Object obj, String methodName) {
for (Method method : obj.getClass().getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
method.setAccessible(true);
return method;
}
}
return null;
}
private void handlePreferenceChange(PreferenceChangeEvent event) {
String key = event.getKey();
if (DartDebugCorePlugin.PREFS_BREAK_ON_EXCEPTIONS.equals(key)) {
breakpointManager.setPauseOnExceptionForLiveIsolates();
}
}
private boolean hasBreakpointAtTopFrame(List<VmCallFrame> frames) {
VmCallFrame frame = frames.get(0);
VmLocation location = frame.getLocation();
return breakpointManager.hasBreakpointAtLine(location);
}
private void printExceptionToStdout(VmValue exception) {
String text = exception.getText();
int index = text.indexOf('\n');
if (index != -1) {
text = text.substring(0, index).trim();
}
fireStreamAppended("Breaking on exception: " + text);
try {
VmIsolate isolate = exception.getIsolate();
connection.evaluateObject(isolate, exception, "toString()", new VmCallback<VmValue>() {
@Override
public void handleResult(VmResult<VmValue> result) {
if (result.isError()) {
fireStreamAppended("\n");
} else {
String desc = result.getResult().getText();
desc = StringUtils.removeStart(desc, "\"");
desc = StringUtils.removeEnd(desc, "\"");
fireStreamAppended(": " + desc + "\n");
}
}
});
} catch (IOException e) {
}
}
private void removeThread(ServerDebugThread thread) {
if (thread != null) {
threads.remove(thread);
thread.fireTerminateEvent();
}
}
private void setBreakpointsActive(boolean enabled) {
// TODO(devoncarew): we need for the command-line debugger to support enabling / disabling all
// breakpoints.
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (Exception exception) {
}
}
}