blob: 0031f515df77c22faeb17d224bac705960b9df9d [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.maven.debug;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.ListeningConnector;
import com.sun.jdi.connect.Transport;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.logging.Level;
import org.netbeans.api.debugger.Breakpoint;
import org.netbeans.api.debugger.DebuggerEngine;
import org.netbeans.api.debugger.DebuggerManager;
import org.netbeans.api.debugger.DebuggerManagerAdapter;
import org.netbeans.api.debugger.jpda.DebuggerStartException;
import org.netbeans.api.debugger.jpda.JPDADebugger;
import org.netbeans.api.debugger.jpda.MethodBreakpoint;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.api.execute.RunUtils;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.filesystems.FileUtil;
import org.openide.util.RequestProcessor;
import org.openide.windows.InputOutput;
/**
* Start the JPDA debugger
* @author Milos Kleint
*/
public class JPDAStart implements Runnable {
private static final RequestProcessor RP = new RequestProcessor(JPDAStart.class);
/**
* @parameter expression="${jpda.transport}"
*/
private String transport = "dt_socket"; //NOI18N
private String name;
private String stopClassName;
private String stopMethod;
private ClassPath additionalSourcePath;
private final Object[] lock = new Object[2];
private Project project;
private final String actionName;
private final InputOutput io;
JPDAStart(InputOutput inputOutput, String actionName) {
io = inputOutput;
this.actionName = actionName;
}
/**
* returns the port/address that the debugger listens to..
*/
public String execute(Project project) throws Throwable {
this.project = project;
io.getOut().println("JPDA Listening Start..."); //NOI18N
// getLog().debug("Entering synch lock"); //NOI18N
synchronized (lock) {
// getLog().debug("Entered synch lock"); //NOI18N
RP.post(this);
// getLog().debug("Entering wait"); //NOI18N
lock.wait();
// getLog().debug("Wait finished"); //NOI18N
if (lock[1] != null) {
throw ((Throwable) lock[1]); //NOI18N
}
}
return (String)lock[0];
}
@Override
public void run() {
synchronized (lock) {
try {
ListeningConnector lc = null;
Iterator i = Bootstrap.virtualMachineManager().
listeningConnectors().iterator();
for (; i.hasNext();) {
lc = (ListeningConnector) i.next();
Transport t = lc.transport();
if (t != null && t.name().equals(getTransport())) {
break;
}
}
if (lc == null) {
throw new RuntimeException
("No trasports named " + getTransport() + " found!"); //NOI18N
}
// TODO: revisit later when http://developer.java.sun.com/developer/bugParade/bugs/4932074.html gets integrated into JDK
// This code parses the address string "HOST:PORT" to extract PORT and then point debugee to localhost:PORT
// This is NOT a clean solution to the problem but it SHOULD work in 99% cases
final Map args = lc.defaultArguments();
String address = lc.startListening(args);
try {
int port = Integer.parseInt(address.substring(address.indexOf(':') + 1));
// getProject ().setNewProperty (getAddressProperty (), "localhost:" + port);
Connector.IntegerArgument portArg = (Connector.IntegerArgument) args.get("port"); //NOI18N
portArg.setValue(port);
lock[0] = Integer.toString(port);
} catch (NumberFormatException e) {
// this address format is not known, use default
// getProject ().setNewProperty (getAddressProperty (), address);
lock[0] = address;
}
io.getOut().println("JPDA Address: " + address); //NOI18N
io.getOut().println("Port:" + lock[0]); //NOI18N
ClassPath sourcePath = Utils.createSourcePath(project);
if (getAdditionalSourcePath() != null) {
sourcePath = ClassPathSupport.createProxyClassPath(sourcePath, getAdditionalSourcePath());
}
ClassPath jdkSourcePath = Utils.createJDKSourcePath(project);
if (getStopClassName() != null && getStopClassName().length() > 0) {
final MethodBreakpoint b = getStopMethod() != null ? Utils.createBreakpoint(getStopClassName(), getStopMethod()) : Utils.createBreakpoint(getStopClassName());
final Listener list = new Listener(b);
b.addPropertyChangeListener(MethodBreakpoint.PROP_VALIDITY, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pce) {
if (Breakpoint.VALIDITY.INVALID.equals(b.getValidity()) && getStopMethod() != null) {
//when the original method with method is not available (maybe defined in parent class?), replace it with a class breakpoint
DebuggerManager.getDebuggerManager().removeBreakpoint(b);
MethodBreakpoint b2 = Utils.createBreakpoint(getStopClassName());
list.replaceBreakpoint(b2);
}
}
});
DebuggerManager.getDebuggerManager().addDebuggerListener(
DebuggerManager.PROP_DEBUGGER_ENGINES,
list);
}
final Map properties = new HashMap();
properties.put("sourcepath", sourcePath); //NOI18N
properties.put("name", getName()); //NOI18N
properties.put("jdksources", jdkSourcePath); //NOI18N
properties.put("baseDir", FileUtil.toFile(project.getProjectDirectory())); // NOI18N
if (RunUtils.isCompileOnSaveEnabled(project)) {
properties.put ("listeningCP", "sourcepath"); // NOI18N
}
final ListeningConnector flc = lc;
RP.post(new Runnable() {
@Override
public void run() {
try {
JPDADebugger.startListening(flc, args,
new Object[]{properties, project});
}
catch (DebuggerStartException ex) {
io.getErr().println("Debugger Start Error."); //NOI18N
Logger.getLogger(JPDAStart.class.getName()).log(Level.INFO, "Debugger Start Error.", ex);
}
}
});
} catch (java.io.IOException ioex) {
io.getErr().println("IO Error:"); //NOI18N
// org.openide.ErrorManager.getDefault().notify(ioex);
lock[1] = ioex;
} catch (com.sun.jdi.connect.IllegalConnectorArgumentsException icaex) {
io.getErr().println("Illegal Connector"); //NOI18N
lock[1] = icaex;
} finally {
lock.notify();
}
}
}
private static class Listener extends DebuggerManagerAdapter {
private MethodBreakpoint breakpoint;
private final Set debuggers = new HashSet();
Listener(MethodBreakpoint breakpoint) {
this.breakpoint = breakpoint;
}
@Override
public void propertyChange(PropertyChangeEvent e) {
if (JPDADebugger.PROP_STATE.equals(e.getPropertyName())) {
int state = ((Integer) e.getNewValue()).intValue();
if ( (state == JPDADebugger.STATE_DISCONNECTED) ||
(state == JPDADebugger.STATE_STOPPED)
) {
// RequestProcessor.getDefault ().post (new Runnable () {
// public void run () {
if (breakpoint != null) {
DebuggerManager.getDebuggerManager().
removeBreakpoint(breakpoint);
breakpoint = null;
}
// }
// });
dispose();
}
}
}
private void dispose() {
DebuggerManager.getDebuggerManager().removeDebuggerListener(
DebuggerManager.PROP_DEBUGGER_ENGINES,
this
);
Iterator it = debuggers.iterator();
while (it.hasNext()) {
JPDADebugger d = (JPDADebugger) it.next();
d.removePropertyChangeListener(
JPDADebugger.PROP_STATE,
this
);
}
}
@Override
public void engineAdded(DebuggerEngine engine) {
JPDADebugger debugger = engine.lookupFirst(null, JPDADebugger.class);
if (debugger == null) {
return;
}
debugger.addPropertyChangeListener(
JPDADebugger.PROP_STATE,
this
);
debuggers.add(debugger);
}
@Override
public void engineRemoved(DebuggerEngine engine) {
JPDADebugger debugger = engine.lookupFirst
(null, JPDADebugger.class);
if (debugger == null) {
return;
}
debugger.removePropertyChangeListener(
JPDADebugger.PROP_STATE,
this
);
debuggers.remove(debugger);
}
private void replaceBreakpoint(MethodBreakpoint b2) {
breakpoint = b2;
}
}
public String getTransport() {
return transport;
}
public void setTransport(String transport) {
this.transport = transport;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getStopClassName() {
return stopClassName;
}
public void setStopClassName(String stopClassName) {
this.stopClassName = stopClassName;
}
public String getStopMethod() {
return stopMethod;
}
public void setStopMethod(String stopMethod) {
this.stopMethod = stopMethod;
}
public ClassPath getAdditionalSourcePath() {
return additionalSourcePath;
}
public void setAdditionalSourcePath(ClassPath additionalSourcePath) {
this.additionalSourcePath = additionalSourcePath;
}
}