alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 *
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.api.debugger.jpda;

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;

/**
 * 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 DebuggerManager dm;
    private Process     process;
    private ProcessIO   pio;
    private JPDADebugger debugger;

    private Object [] debuggerStartLock = new Object[1];
    private Object [] stepLock = new Object[1];

    private JPDASupport() {
        dm = DebuggerManager.getDebuggerManager();
    }

    public ActionsManager getActionsManager() {
        return DebuggerManager.getDebuggerManager().getCurrentEngine().getActionsManager();
    }

    public static Process launchVM(String mainClass, String connectorAddress, boolean vmListens) throws IOException {

        URLClassLoader ucl = (URLClassLoader) JPDASupport.class.getClassLoader();
        URL [] urls = ucl.getURLs();

        StringBuffer cp = new StringBuffer(200);
        for (int i = 0; i < urls.length; i++) {
            URL url = urls[i];
            cp.append(url.getPath());
            cp.append(File.pathSeparatorChar);
        }

        String [] cmdArray = new String [] {
            System.getProperty("java.home") + File.separatorChar + "bin" + File.separatorChar + "java",
            "-Xdebug",
            "-Xnoagent",
            "-Xrunjdwp:transport=" + "dt_socket" + ",address=" + connectorAddress + ",suspend=y,server=" + (vmListens ? "y" : "n"),
            "-Djava.compiler=NONE",
            "-classpath",
            cp.substring(0, cp.length() -1),
            mainClass
        };

        return Runtime.getRuntime().exec(cmdArray);
    }

    private void connect(String mainClass, boolean stopInMain) 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;
        }

        if (stopInMain) addMainMethodBreakpoint(mainClass);

        debuggerStartLock = new Object[1];

        JPDADebugger.startListening(connector, args, new Object[] { });

        DebuggerEngine engine = dm.getCurrentEngine();
        debugger = (JPDADebugger) engine.lookupFirst(null, JPDADebugger.class);
        if (debugger == null) throw new DebuggerStartException("JPDA debugger was not started");
        debugger.addPropertyChangeListener(this);

        waitDebuggerListens();
        process = launchVM(mainClass, localhostAddres, false);
        pio = new ProcessIO(process);
        pio.go();

        if (stopInMain) {
            waitDebuggerStarted();
            if (verbose) System.out.println("JPDA SUPPORT STOPPED IN " + debugger.getCurrentCallStackFrame().getMethodName() + " LINE: " + debugger.getCurrentCallStackFrame().getLineNumber(null));
        }
    }

    private void waitDebuggerListens() {
        try { Thread.sleep(300); } catch (InterruptedException e) {}
    }

    private void addMainMethodBreakpoint(String mainClass) {
        final MethodBreakpoint mb = MethodBreakpoint.create(mainClass, "main");
        mb.setBreakpointType(MethodBreakpoint.TYPE_METHOD_ENTRY);
        mb.addJPDABreakpointListener(new JPDABreakpointListener() {
            public void breakpointReached(JPDABreakpointEvent event) {
                dm.removeBreakpoint(mb);
            }
        });
        dm.addBreakpoint(mb);
    }

    private void attachImpl(String mainClass) throws IOException, DebuggerStartException {

        Process process = launchVM(mainClass, "", true);
        String line = readLine(process.getInputStream());
        int port = Integer.parseInt(line.substring(line.lastIndexOf(':') + 1).trim());
        pio = new ProcessIO(process);
        pio.go();

        addMainMethodBreakpoint(mainClass);

        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");

        Object [] services = new Object [] { };
        debugger = JPDADebugger.attach("localhost", port, services);
        debugger.addPropertyChangeListener(this);

        waitDebuggerStarted();
    }

    private 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 void waitDebuggerStarted() throws DebuggerStartException {
        synchronized(debuggerStartLock) {
            try {
                if ("started".equals(debuggerStartLock[0])) return;
                debuggerStartLock.wait(10000);
                if (debuggerStartLock[0] == null) throw new DebuggerStartException("Debugger did not start");
            } catch (InterruptedException e) {
                throw new DebuggerStartException(e);
            }
        }
    }

    public static JPDASupport listen(String mainClass) throws IOException, IllegalConnectorArgumentsException,
            DebuggerStartException {
        return listen(mainClass, true);
    }

    public static JPDASupport listen(String mainClass, boolean stopInMain) throws IOException, IllegalConnectorArgumentsException, DebuggerStartException {

        JPDASupport jpda = new JPDASupport();
        jpda.connect(mainClass, stopInMain);
        return jpda;
    }

    public static JPDASupport attach(String mainClass) throws IOException, DebuggerStartException {

        JPDASupport jpda = new JPDASupport();
        jpda.attachImpl(mainClass);
        return jpda;
    }

    public void doContinue() {
        if (debugger.getState() != DebuggerConstants.STATE_STOPPED) throw new IllegalStateException();
        waitEnabled(ActionsManager.ACTION_CONTINUE);
        getActionsManager().doAction(ActionsManager.ACTION_CONTINUE);
        if (verbose) System.err.println(df.format(new Date()) + " Successfully invoked 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 (debugger.getState() != DebuggerConstants.STATE_STOPPED) {
            throw new IllegalStateException();
        }
        waitEnabled(action);
        synchronized (stepLock) {
            stepLock[0] = "stepStart";
            getActionsManager().doAction(action);
            if (verbose) System.err.println(df.format(new Date()) + " Successfully invoked step: " + action);
            try {
                stepLock.wait(10000);
                if (stepLock[0] != null) throw new TimeoutException("Step action timed out");
            } catch (InterruptedException e) {
                 throw new IllegalStateException();
            }
        }
    }

    private void waitEnabled(Object action) {
        for (int i = 0; i < 50; i++) {
            if (getActionsManager().isEnabled(action)) return;
            sleep(100);
        }
        throw new IllegalStateException("Action " + action + " is not enabled");
    }

    public void waitDisconnected(int timeoutMillis) {
        long t0 = System.currentTimeMillis();
        while (debugger.getState() != DebuggerConstants.STATE_DISCONNECTED) {
            sleep(100);
            if (System.currentTimeMillis() - t0 > timeoutMillis) throw new TimeoutException();
/*
            if (debugger.getState() == JPDADebugger.STATE_STOPPED) {
                System.out.println("Stopped at " + debugger.getCurrentCallStackFrame().getClassName() + ":" +
                                   debugger.getCurrentCallStackFrame().getLineNumber(null));
            }
*/
        }
    }

    public static class TimeoutException extends RuntimeException {
        TimeoutException() {
        }

        TimeoutException(String message) {
            super(message);
        }
    }

    public void waitState(int state, int timeoutMillis) {
        long t0 = System.currentTimeMillis();
        while (debugger.getState() != state) {
            sleep(200);
            if (System.currentTimeMillis() - t0 > timeoutMillis) {
/*
                VirtualMachine vm = ((JPDADebuggerImpl) debugger).getVirtualMachine();
                List threads = vm.allThreads();
                for (Iterator i = threads.iterator(); i.hasNext();) {
                    ThreadReference threadReference = (ThreadReference) i.next();
                    System.err.println("Thread " + threadReference.name() + " suspended? " + threadReference.isSuspended());
                }
*/
                throw new TimeoutException("Waitstate timeout: " + debugger.getState());
            }
        }
    }

    public void waitStates(int state1, int state2, int timeoutMillis) {
        long t0 = System.currentTimeMillis();
        while (debugger.getState() != state1 && debugger.getState() != state2) {
            if (System.currentTimeMillis() - t0 > timeoutMillis)
                throw new TimeoutException("Waitstate timeout: " + debugger.getState());
            sleep(200);
        }
    }

    private void sleep(int time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            throw new DisconnectedException(e);
        }
    }

    public void doFinish() {
        if (debugger == null) return;
        debugger.removePropertyChangeListener(this);
        if (DebuggerManager.getDebuggerManager().getCurrentEngine() != null) {
            try {
                ActionsManager am = getActionsManager();
                am.doAction(ActionsManager.ACTION_KILL);
                waitDisconnected(10000);
            } catch (Throwable e) {
                e.printStackTrace(System.out);
            }
        }
        debugger = null;
        if (verbose) System.err.println(df.format(new Date()) + " DEBUGGER FINISHED OK");
    }

    private class DisconnectedException extends RuntimeException {
        public DisconnectedException(Throwable cause) {
            super(cause);
        }

        public DisconnectedException() {
            super();
        }
    }

    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) {
//        System.out.println(evt.getSource() + " : " + evt.getPropertyName() + " : " + evt.getOldValue() + " : " + evt.getNewValue());
        if (evt.getSource() instanceof JPDADebugger) {
            JPDADebugger dbg = (JPDADebugger) evt.getSource();

            if (JPDADebugger.PROP_STATE.equals(evt.getPropertyName())) {
                int os = ((Integer) evt.getOldValue()).intValue();
                int ns = ((Integer) evt.getNewValue()).intValue();
                if (verbose) System.err.println(df.format(new Date()) + " STATE: " + os + " => " + ns);
                synchronized(debuggerStartLock) {
                    if (debuggerStartLock != null && debuggerStartLock[0] == null) {
                        if (DebuggerConstants.STATE_RUNNING == os && DebuggerConstants.STATE_STOPPED == ns) {
                            debuggerStartLock[0] = "started";
                            debuggerStartLock.notifyAll();
                        }
                    }
                }
                synchronized(stepLock) {
                    if (stepLock[0] != null) {
                        if (stepLock[0].equals("stepStart")) {
                            if (os == DebuggerConstants.STATE_STOPPED && ns == DebuggerConstants.STATE_RUNNING) {
                                stepLock[0] = "stepInProgress";
                            }
                        } else if (stepLock[0].equals("stepInProgress")) {
                            if (os == DebuggerConstants.STATE_RUNNING && ns == DebuggerConstants.STATE_STOPPED) {
                                stepLock.notifyAll();
                                stepLock[0] = null;
                            }
                        }
                    }
                }
            }
        }
    }

    public JPDADebugger getDebugger() {
        return debugger;
    }

    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");
        }
    }

    // TODO: Include check of these call in the test suite
    public void engineAdded(DebuggerEngine engine) {
    }

    // TODO: Include check of these call in the test suite
    public void engineRemoved(DebuggerEngine engine) {
    }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.