| /* |
| * 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.api.debugger.jpda; |
| |
| import java.lang.reflect.Method; |
| import java.net.MalformedURLException; |
| import java.util.HashMap; |
| import java.util.Map; |
| import org.netbeans.api.debugger.*; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.net.URLClassLoader; |
| import java.net.URL; |
| import java.beans.PropertyChangeEvent; |
| import java.text.DateFormat; |
| import java.text.SimpleDateFormat; |
| |
| import com.sun.jdi.connect.*; |
| import com.sun.jdi.VirtualMachineManager; |
| import com.sun.jdi.Bootstrap; |
| //import org.netbeans.api.java.classpath.ClassPath; |
| import junit.framework.Test; |
| import junit.framework.TestCase; |
| import org.netbeans.api.java.classpath.ClassPath; |
| //import org.netbeans.spi.java.classpath.support.ClassPathSupport; |
| import org.netbeans.api.java.queries.SourceForBinaryQuery; |
| import org.netbeans.junit.NbModuleSuite; |
| import org.netbeans.junit.NbModuleSuite.Configuration; |
| import org.netbeans.spi.java.classpath.support.ClassPathSupport; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.Exceptions; |
| |
| /** |
| * Contains support functionality for unit tests. |
| * |
| * @author Maros Sandor |
| */ |
| public class JPDASupport implements DebuggerManagerListener { |
| |
| private static final boolean verbose = false; |
| private static final DateFormat df = new SimpleDateFormat("kk:mm:ss.SSS"); |
| private static DebuggerManager dm = DebuggerManager.getDebuggerManager (); |
| |
| private JPDADebugger jpdaDebugger; |
| private DebuggerEngine debuggerEngine; |
| |
| |
| private Object [] debuggerStartLock = new Object[1]; |
| private Object [] stepLock = new Object[1]; |
| |
| private Object STATE_LOCK = new Object (); |
| |
| |
| private JPDASupport (JPDADebugger jpdaDebugger) { |
| this.jpdaDebugger = jpdaDebugger; |
| jpdaDebugger.addPropertyChangeListener (this); |
| DebuggerEngine[] de = dm.getDebuggerEngines (); |
| int i, k = de.length; |
| for (i = 0; i < k; i++) |
| if (de [i].lookupFirst (null, JPDADebugger.class) == jpdaDebugger) { |
| debuggerEngine = de [i]; |
| break; |
| } |
| } |
| |
| public static Test createTestSuite(Class<? extends TestCase> clazz) { |
| Configuration suiteConfiguration = NbModuleSuite.createConfiguration(clazz); |
| suiteConfiguration = suiteConfiguration.clusters(".*").enableModules(".*java.source.*").gui(false); |
| if (!(ClassLoader.getSystemClassLoader() instanceof URLClassLoader)) { |
| //when running on JDK 9+, to make the com.sun.jdi package dependency work, we need to make getPackage("com.sun.jdi") work |
| //for system CL's parent (which otherwise happily loads the VirtualMachineManager class, |
| //but won't return the package from getPackage due to JDK "specialty": |
| suiteConfiguration = suiteConfiguration.parentClassLoader(new ClassLoader(ClassLoader.getSystemClassLoader().getParent()) { |
| @Override |
| protected Package getPackage(String pack) { |
| if ("com.sun.jdi".equals(pack)) { |
| try { |
| return loadClass("com.sun.jdi.VirtualMachineManager").getPackage(); |
| } catch (ClassNotFoundException ex) { |
| throw new IllegalStateException(ex); |
| } |
| } |
| return super.getPackage(pack); |
| } |
| }); |
| } |
| //suiteConfiguration = suiteConfiguration.reuseUserDir(false); |
| return NbModuleSuite.create(suiteConfiguration); |
| } |
| |
| |
| // starting methods ........................................................ |
| |
| // public static JPDASupport listen (String mainClass) |
| // throws IOException, IllegalConnectorArgumentsException, |
| // DebuggerStartException { |
| // VirtualMachineManager vmm = Bootstrap.virtualMachineManager (); |
| // List lconnectors = vmm.listeningConnectors (); |
| // ListeningConnector connector = null; |
| // for (Iterator i = lconnectors.iterator (); i.hasNext ();) { |
| // ListeningConnector lc = (ListeningConnector) i.next (); |
| // Transport t = lc.transport (); |
| // if (t != null && t.name ().equals ("dt_socket")) { |
| // connector = lc; |
| // break; |
| // } |
| // } |
| // if (connector == null) |
| // throw new RuntimeException |
| // ("No listening socket connector available"); |
| // |
| // Map args = connector.defaultArguments (); |
| // String address = connector.startListening (args); |
| // String localhostAddres; |
| // try |
| // { |
| // int port = Integer.parseInt |
| // (address.substring (address.indexOf (':') + 1)); |
| // localhostAddres = "localhost:" + port; |
| // Connector.IntegerArgument portArg = |
| // (Connector.IntegerArgument) args.get("port"); |
| // portArg.setValue(port); |
| // } catch (Exception e) { |
| // // this address format is not known, use default |
| // localhostAddres = address; |
| // } |
| // |
| // JPDADebugger jpdaDebugger = JPDADebugger.listen |
| // (connector, args, createServices ()); |
| // if (jpdaDebugger == null) |
| // throw new DebuggerStartException ("JPDA jpdaDebugger was not started"); |
| // Process process = launchVM (mainClass, localhostAddres, false); |
| // ProcessIO pio = new ProcessIO (process); |
| // pio.go (); |
| // return new JPDASupport (jpdaDebugger); |
| // } |
| |
| public static JPDASupport attach (String mainClass) throws IOException, |
| DebuggerStartException { |
| return attach(mainClass, null); |
| } |
| |
| public static JPDASupport attach (String mainClass, String[] args) throws IOException, |
| DebuggerStartException { |
| return attach(mainClass, args, new File[0]); |
| } |
| public static JPDASupport attach (String mainClass, String[] args, File[] classPath) throws IOException, |
| DebuggerStartException { |
| Process process = launchVM (mainClass, args, classPath, "", true); |
| String line = readLine (process.getInputStream ()); |
| int port = Integer.parseInt (line.substring (line.lastIndexOf (':') + 1).trim ()); |
| ProcessIO pio = new ProcessIO (process); |
| pio.go (); |
| |
| VirtualMachineManager vmm = Bootstrap.virtualMachineManager(); |
| List aconnectors = vmm.attachingConnectors(); |
| AttachingConnector connector = null; |
| for (Iterator i = aconnectors.iterator(); i.hasNext();) { |
| AttachingConnector ac = (AttachingConnector) i.next(); |
| Transport t = ac.transport (); |
| if (t != null && t.name().equals("dt_socket")) { |
| connector = ac; |
| break; |
| } |
| } |
| if (connector == null) |
| throw new RuntimeException |
| ("No attaching socket connector available"); |
| |
| JPDADebugger jpdaDebugger = JPDADebugger.attach ( |
| "localhost", |
| port, |
| createServices () |
| ); |
| return new JPDASupport (jpdaDebugger); |
| } |
| |
| |
| // public interface ........................................................ |
| |
| public void doContinue () { |
| if (jpdaDebugger.getState () != JPDADebugger.STATE_STOPPED) |
| throw new IllegalStateException (); |
| debuggerEngine.getActionsManager ().doAction |
| (ActionsManager.ACTION_CONTINUE); |
| } |
| |
| public void stepOver () { |
| step (ActionsManager.ACTION_STEP_OVER); |
| } |
| |
| public void stepInto () { |
| step (ActionsManager.ACTION_STEP_INTO); |
| } |
| |
| public void stepOut () { |
| step (ActionsManager.ACTION_STEP_OUT); |
| } |
| |
| public void step (Object action) { |
| if (jpdaDebugger.getState () != JPDADebugger.STATE_STOPPED) |
| throw new IllegalStateException (); |
| debuggerEngine.getActionsManager ().doAction (action); |
| waitState (JPDADebugger.STATE_STOPPED); |
| } |
| |
| public void stepAsynch (final Object actionAsynch, final ActionsManagerListener al) { |
| if (jpdaDebugger.getState () != JPDADebugger.STATE_STOPPED) |
| throw new IllegalStateException (); |
| debuggerEngine.getActionsManager().addActionsManagerListener( |
| new ActionsManagerListener() { |
| public void actionPerformed(Object action) { |
| if (action != actionAsynch) return ; |
| al.actionPerformed(action); |
| debuggerEngine.getActionsManager().removeActionsManagerListener(this); |
| } |
| public void actionStateChanged(Object action, boolean enabled) { |
| } |
| } |
| ); |
| debuggerEngine.getActionsManager ().postAction (actionAsynch); |
| } |
| |
| public void doFinish () { |
| if (jpdaDebugger == null) return; |
| debuggerEngine.getActionsManager (). |
| doAction (ActionsManager.ACTION_KILL); |
| waitState (JPDADebugger.STATE_DISCONNECTED); |
| } |
| |
| public void waitState (int state) { |
| synchronized (STATE_LOCK) { |
| while ( jpdaDebugger.getState () != state && |
| jpdaDebugger.getState () != JPDADebugger.STATE_DISCONNECTED |
| ) { |
| try { |
| STATE_LOCK.wait (); |
| } catch (InterruptedException ex) { |
| ex.printStackTrace (); |
| } |
| } |
| } |
| } |
| |
| /*public void waitState (int state) { |
| synchronized (STATE_LOCK) { |
| int ds = jpdaDebugger.getState (); |
| System.err.println("JPDASupport.waitState("+state+"): ds = "+ds+", jpdaDebugger = "+jpdaDebugger); |
| while ( ds != state && |
| ds != JPDADebugger.STATE_DISCONNECTED |
| ) { |
| try { |
| STATE_LOCK.wait (); |
| } catch (InterruptedException ex) { |
| ex.printStackTrace (); |
| } |
| ds = jpdaDebugger.getState (); |
| System.err.println("JPDASupport.waitState("+state+"): new ds = "+ds+", jpdaDebugger = "+jpdaDebugger); |
| } |
| System.err.println("JPDASupport.waitState("+state+"): state reached."); |
| } |
| }*/ |
| |
| public JPDADebugger getDebugger() { |
| return jpdaDebugger; |
| } |
| |
| /** |
| * Remove all non-hidden breakpoints. |
| */ |
| public static void removeAllBreakpoints () { |
| Breakpoint[] bs = DebuggerManager.getDebuggerManager (). |
| getBreakpoints (); |
| int i, k = bs.length; |
| for (i = 0; i < k; i++) { |
| if (!(bs[i] instanceof JPDABreakpoint && ((JPDABreakpoint) bs[i]).isHidden())) { |
| DebuggerManager.getDebuggerManager ().removeBreakpoint (bs [i]); |
| } |
| } |
| } |
| |
| |
| // other methods ........................................................... |
| |
| private static Object[] createServices () { |
| try { |
| Map map = new HashMap (); |
| String sourceRoot = System.getProperty ("test.dir.src"); |
| URL sourceUrl = new File(sourceRoot).toURI().toURL(); |
| String sourceUrlStr = sourceUrl.toString() + "/"; |
| sourceUrl = new URL(sourceUrlStr); |
| ClassPath cp = ClassPathSupport.createClassPath (new URL[] { |
| sourceUrl |
| }); |
| map.put ("sourcepath", cp); |
| map.put ("baseDir", new File(sourceRoot).getParentFile()); |
| return new Object[] { map }; |
| } catch (MalformedURLException ex) { |
| //System.err.println("MalformedURLException: sourceRoot = '"+sourceRoot+"'."); |
| ex.printStackTrace(); |
| return new Object[] {}; |
| } |
| } |
| |
| private static String readLine (InputStream in) throws IOException { |
| StringBuffer sb = new StringBuffer(); |
| for (;;) { |
| int c = in.read(); |
| if (c == -1) throw new EOFException(); |
| if (c == 0x0D) { |
| c = in.read(); |
| if (c != 0x0A) sb.append((char)0x0D); |
| } |
| if (c == 0x0A) return sb.toString(); |
| sb.append((char)c); |
| } |
| } |
| |
| private static Process launchVM ( |
| String mainClass, |
| String[] args, |
| File[] extraCP, |
| String connectorAddress, |
| boolean server |
| ) throws IOException { |
| |
| String cp = getClassPath(extraCP); |
| //System.err.println("CP = "+cp); |
| |
| String [] cmdArray = new String [] { |
| System.getProperty ("java.home") + File.separatorChar + |
| "bin" + File.separatorChar + "java", |
| "-agentlib:jdwp=transport=" + "dt_socket" + ",address=" + |
| connectorAddress + ",suspend=y,server=" + |
| (server ? "y" : "n"), |
| "-classpath", |
| cp.substring(0, cp.length() -1), |
| mainClass |
| }; |
| if (args != null && args.length > 0) { |
| String[] arr = new String[cmdArray.length + args.length]; |
| System.arraycopy(cmdArray, 0, arr, 0, cmdArray.length); |
| System.arraycopy(args, 0, arr, cmdArray.length, args.length); |
| cmdArray = arr; |
| } |
| |
| return Runtime.getRuntime ().exec (cmdArray); |
| } |
| |
| private static String getClassPath(File[] extraCP) { |
| StringBuilder cp = new StringBuilder (200); |
| ClassLoader cl = JPDASupport.class.getClassLoader (); |
| if (cl instanceof URLClassLoader) { |
| URLClassLoader ucl = (URLClassLoader) cl; |
| URL [] urls = ucl.getURLs (); |
| |
| for (int i = 0; i < urls.length; i++) { |
| URL url = urls [i]; |
| cp.append (url.getPath ()); |
| cp.append (File.pathSeparatorChar); |
| } |
| } else if (cl.getClass().getName().indexOf("org.netbeans.ModuleManager$SystemClassLoader") >= 0) { |
| Class jarClassLoaderClass = cl.getClass().getSuperclass(); |
| try { |
| java.lang.reflect.Field sourcesField = jarClassLoaderClass.getDeclaredField("sources"); |
| sourcesField.setAccessible(true); |
| Object[] sources = (Object[]) sourcesField.get(cl); |
| for (int i = 0; i < sources.length; i++) { |
| Method getURL = sources[i].getClass().getMethod("getURL"); |
| getURL.setAccessible(true); |
| URL url = (URL) getURL.invoke(sources[i]); |
| cp.append (url.getPath ()); |
| cp.append (File.pathSeparatorChar); |
| } |
| } catch (Exception ex) { |
| throw new RuntimeException("Problem retrieving class path from class loader: "+cl, ex); |
| } |
| } else { |
| throw new RuntimeException("Unsupported class loader: "+cl); |
| } |
| for (File f : extraCP) { |
| cp.append(f.getPath()); |
| cp.append(File.pathSeparatorChar); |
| } |
| |
| return cp.toString(); |
| } |
| |
| public String toString () { |
| switch (jpdaDebugger.getState ()) { |
| case JPDADebugger.STATE_DISCONNECTED: |
| return "Debugger finished."; |
| case JPDADebugger.STATE_RUNNING: |
| return "Debugger running."; |
| case JPDADebugger.STATE_STARTING: |
| return "Debugger starting."; |
| case JPDADebugger.STATE_STOPPED: |
| CallStackFrame f = jpdaDebugger.getCurrentCallStackFrame (); |
| return "Debugger stopped: " + |
| f.getClassName () + "." + |
| f.getMethodName () + ":" + |
| f.getLineNumber (null); |
| } |
| return super.toString (); |
| } |
| |
| // DebuggerListener ........................................................ |
| |
| public Breakpoint[] initBreakpoints() { |
| return new Breakpoint[0]; |
| } |
| |
| public void breakpointAdded(Breakpoint breakpoint) { |
| } |
| |
| public void breakpointRemoved(Breakpoint breakpoint) { |
| } |
| |
| public void initWatches() { |
| } |
| |
| public void watchAdded(Watch watch) { |
| } |
| |
| public void watchRemoved(Watch watch) { |
| } |
| |
| public void sessionAdded(Session session) { |
| } |
| |
| public void sessionRemoved(Session session) { |
| } |
| |
| public void propertyChange (PropertyChangeEvent evt) { |
| if (evt.getSource() instanceof JPDADebugger) { |
| JPDADebugger dbg = (JPDADebugger) evt.getSource(); |
| |
| if (JPDADebugger.PROP_STATE.equals(evt.getPropertyName())) { |
| synchronized (STATE_LOCK) { |
| STATE_LOCK.notifyAll (); |
| } |
| if (jpdaDebugger.getState () == JPDADebugger.STATE_DISCONNECTED) |
| jpdaDebugger.removePropertyChangeListener (this); |
| } |
| } |
| } |
| |
| // TODO: Include check of these call in the test suite |
| public void engineAdded (DebuggerEngine debuggerEngine) { |
| } |
| |
| // TODO: Include check of these call in the test suite |
| public void engineRemoved (DebuggerEngine debuggerEngine) { |
| } |
| |
| |
| // innerclasses ............................................................ |
| |
| private static class ProcessIO { |
| |
| private Process p; |
| |
| public ProcessIO(Process p) { |
| this.p = p; |
| } |
| |
| public void go() { |
| InputStream out = p.getInputStream(); |
| InputStream err = p.getErrorStream(); |
| |
| new SimplePipe(System.out, out).start(); |
| new SimplePipe(System.out, err).start(); |
| } |
| } |
| |
| private static class SimplePipe extends Thread { |
| private OutputStream out; |
| private InputStream in; |
| |
| public SimplePipe(OutputStream out, InputStream in) { |
| this.out = out; |
| this.in = in; |
| setDaemon(true); |
| } |
| |
| public void run() { |
| byte [] buffer = new byte[1024]; |
| int n; |
| try { |
| while ((n = in.read(buffer)) != -1) { |
| out.write(buffer, 0, n); |
| } |
| } catch (IOException e) { |
| } finally { |
| try { |
| out.close(); |
| in.close(); |
| } catch (IOException e) { |
| } |
| } |
| System.out.println("PIO QUIT"); |
| } |
| } |
| } |