Package org.hotswap.agent.plugin.hotswapper

Source Code of org.hotswap.agent.plugin.hotswapper.HotSwapperJpda

/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License.  Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/

package org.hotswap.agent.plugin.hotswapper;

import com.sun.jdi.Bootstrap;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.*;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;
import org.hotswap.agent.javassist.ClassPool;
import org.hotswap.agent.javassist.CtClass;
import org.hotswap.agent.javassist.LoaderClassPath;

import java.io.IOException;
import java.util.*;

class Trigger {
    void doSwap() {
    }
}

/**
* Example HotSwapperJpda class from javaassist is copied to the plugin, becuse it needs to reside
* in the application classloader to avoid NoClassDefFound error on tools.jar classes. Otherwise
* it is the same code as in javassist.
* <p/>
* A utility class for dynamically reloading a class by
* the Java Platform Debugger Architecture (JPDA), or <it>HotSwap</code>.
* It works only with JDK 1.4 and later.
* <p/>
* <p><b>Note:</b> The new definition of the reloaded class must declare
* the same set of methods and fields as the original definition.  The
* schema change between the original and new definitions is not allowed
* by the JPDA.
* <p/>
* <p>To use this class, the JVM must be launched with the following
* command line options:
* <p/>
* <ul>
* <p>For Java 1.4,<br>
* <pre>java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000</pre>
* <p>For Java 5,<br>
* <pre>java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000</pre>
* </ul>
* <p/>
* <p>Note that 8000 is the port number used by <code>HotSwapperJpda</code>.
* Any port number can be specified.  Since <code>HotSwapperJpda</code> does not
* launch another JVM for running a target application, this port number
* is used only for inter-thread communication.
* <p/>
* <p>Furthermore, <code>JAVA_HOME/lib/tools.jar</code> must be included
* in the class path.
* <p/>
* <p>Using <code>HotSwapperJpda</code> is easy.  See the following example:
* <p/>
* <ul><pre>
* CtClass clazz = ...
* byte[] classFile = clazz.toBytecode();
* HotSwapperJpda hs = new HotSwapperJpda(8000);  // 8000 is a port number.
* hs.reload("Test", classFile);
* </pre></ul>
* <p/>
* <p><code>reload()</code>
* first unload the <code>Test</code> class and load a new version of
* the <code>Test</code> class.
* <code>classFile</code> is a byte array containing the new contents of
* the class file for the <code>Test</code> class.  The developers can
* repatedly call <code>reload()</code> on the same <code>HotSwapperJpda</code>
* object so that they can reload a number of classes.
*
* @since 3.1
*/
public class HotSwapperJpda {
    private VirtualMachine jvm;
    private MethodEntryRequest request;
    private Map newClassFiles;

    private Trigger trigger;

    private static final String HOST_NAME = "localhost";
    private static final String TRIGGER_NAME = Trigger.class.getName();

    /**
     * Connects to the JVM.
     *
     * @param port the port number used for the connection to the JVM.
     */
    public HotSwapperJpda(int port)
            throws IOException, IllegalConnectorArgumentsException {
        this(Integer.toString(port));
    }

    /**
     * Connects to the JVM.
     *
     * @param port the port number used for the connection to the JVM.
     */
    public HotSwapperJpda(String port)
            throws IOException, IllegalConnectorArgumentsException {
        jvm = null;
        request = null;
        newClassFiles = null;
        trigger = new Trigger();
        AttachingConnector connector
                = (AttachingConnector) findConnector("com.sun.jdi.SocketAttach");

        Map arguments = connector.defaultArguments();
        ((Connector.Argument) arguments.get("hostname")).setValue(HOST_NAME);
        ((Connector.Argument) arguments.get("port")).setValue(port);
        jvm = connector.attach(arguments);
        EventRequestManager manager = jvm.eventRequestManager();
        request = methodEntryRequests(manager, TRIGGER_NAME);
    }

    private Connector findConnector(String connector) throws IOException {
        List connectors = Bootstrap.virtualMachineManager().allConnectors();
        Iterator iter = connectors.iterator();
        while (iter.hasNext()) {
            Connector con = (Connector) iter.next();
            if (con.name().equals(connector)) {
                return con;
            }
        }

        throw new IOException("Not found: " + connector);
    }

    private static MethodEntryRequest methodEntryRequests(
            EventRequestManager manager,
            String classpattern) {
        MethodEntryRequest mereq = manager.createMethodEntryRequest();
        mereq.addClassFilter(classpattern);
        mereq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
        return mereq;
    }

    /* Stops triggering a hotswapper when reload() is called.
     */
    private void deleteEventRequest(EventRequestManager manager,
                                    MethodEntryRequest request) {
        manager.deleteEventRequest(request);
    }

    /**
     * Reloads a class.
     *
     * @param className the fully-qualified class name.
     * @param classFile the contents of the class file.
     */
    public void reload(String className, byte[] classFile) {
        ReferenceType classtype = toRefType(className);
        Map map = new HashMap();
        map.put(classtype, classFile);
        reload2(map, className);
    }

    /**
     * Reloads a class.
     *
     * @param classFiles a map between fully-qualified class names
     *                   and class files.  The type of the class names
     *                   is <code>String</code> and the type of the
     *                   class files is <code>byte[]</code>.
     */
    public void reload(Map classFiles) {
        Set set = classFiles.entrySet();
        Iterator it = set.iterator();
        Map map = new HashMap();
        String className = null;
        while (it.hasNext()) {
            Map.Entry e = (Map.Entry) it.next();
            className = (String) e.getKey();
            map.put(toRefType(className), e.getValue());
        }

        if (className != null)
            reload2(map, className + " etc.");
    }

    private ReferenceType toRefType(String className) {
        List list = jvm.classesByName(className);
        if (list == null || list.isEmpty())
            throw new RuntimeException("no such class: " + className);
        else
            return (ReferenceType) list.get(0);
    }

    private void reload2(Map map, String msg) {
        synchronized (trigger) {
            startDaemon();
            newClassFiles = map;
            request.enable();
            trigger.doSwap();
            request.disable();
            Map ncf = newClassFiles;
            if (ncf != null) {
                newClassFiles = null;
                throw new RuntimeException("failed to reload: " + msg);
            }
        }
    }

    private void startDaemon() {
        new Thread() {
            private void errorMsg(Throwable e) {
                System.err.print("Exception in thread \"HotSwap\" ");
                e.printStackTrace(System.err);
            }

            public void run() {
                EventSet events = null;
                try {
                    events = waitEvent();
                    EventIterator iter = events.eventIterator();
                    while (iter.hasNext()) {
                        Event event = iter.nextEvent();
                        if (event instanceof MethodEntryEvent) {
                            hotswap();
                            break;
                        }
                    }
                } catch (Throwable e) {
                    errorMsg(e);
                }
                try {
                    if (events != null)
                        events.resume();
                } catch (Throwable e) {
                    errorMsg(e);
                }
            }
        }.start();
    }

    EventSet waitEvent() throws InterruptedException {
        EventQueue queue = jvm.eventQueue();
        return queue.remove();
    }

    void hotswap() {
        Map map = newClassFiles;
        jvm.redefineClasses(map);
        newClassFiles = null;
    }

    /**
     * Swap class definition from another class file.
     * <p/>
     * This is mainly useful for unit testing - declare multiple version of a class and then
     * hotswap definition and do the tests.
     *
     * @param original original class currently in use
     * @param swap     fully qualified class name of class to swap
     * @throws Exception swap exception
     */
    public void swapClasses(Class original, String swap) throws Exception {
        // need to recreate classpool on each swap to avoid stale class definition
        ClassPool classPool = new ClassPool();
        classPool.appendClassPath(new LoaderClassPath(original.getClassLoader()));

        CtClass ctClass = classPool.getAndRename(swap, original.getName());

        reload(original.getName(), ctClass.toBytecode());
    }
}
TOP

Related Classes of org.hotswap.agent.plugin.hotswapper.HotSwapperJpda

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.