Home | History | Annotate | Download | only in util
      1 /*
      2  * Javassist, a Java-bytecode translator toolkit.
      3  * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
      4  *
      5  * The contents of this file are subject to the Mozilla Public License Version
      6  * 1.1 (the "License"); you may not use this file except in compliance with
      7  * the License.  Alternatively, the contents of this file may be used under
      8  * the terms of the GNU Lesser General Public License Version 2.1 or later.
      9  *
     10  * Software distributed under the License is distributed on an "AS IS" basis,
     11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
     12  * for the specific language governing rights and limitations under the
     13  * License.
     14  */
     15 
     16 package javassist.util;
     17 
     18 import com.sun.jdi.*;
     19 import com.sun.jdi.connect.*;
     20 import com.sun.jdi.event.*;
     21 import com.sun.jdi.request.*;
     22 import java.io.*;
     23 import java.util.*;
     24 
     25 class Trigger {
     26     void doSwap() {}
     27 }
     28 
     29 /**
     30  * A utility class for dynamically reloading a class by
     31  * the Java Platform Debugger Architecture (JPDA), or <it>HotSwap</code>.
     32  * It works only with JDK 1.4 and later.
     33  *
     34  * <p><b>Note:</b> The new definition of the reloaded class must declare
     35  * the same set of methods and fields as the original definition.  The
     36  * schema change between the original and new definitions is not allowed
     37  * by the JPDA.
     38  *
     39  * <p>To use this class, the JVM must be launched with the following
     40  * command line options:
     41  *
     42  * <ul>
     43  * <p>For Java 1.4,<br>
     44  * <pre>java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000</pre>
     45  * <p>For Java 5,<br>
     46  * <pre>java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000</pre>
     47  * </ul>
     48  *
     49  * <p>Note that 8000 is the port number used by <code>HotSwapper</code>.
     50  * Any port number can be specified.  Since <code>HotSwapper</code> does not
     51  * launch another JVM for running a target application, this port number
     52  * is used only for inter-thread communication.
     53  *
     54  * <p>Furthermore, <code>JAVA_HOME/lib/tools.jar</code> must be included
     55  * in the class path.
     56  *
     57  * <p>Using <code>HotSwapper</code> is easy.  See the following example:
     58  *
     59  * <ul><pre>
     60  * CtClass clazz = ...
     61  * byte[] classFile = clazz.toBytecode();
     62  * HotSwapper hs = new HostSwapper(8000);  // 8000 is a port number.
     63  * hs.reload("Test", classFile);
     64  * </pre></ul>
     65  *
     66  * <p><code>reload()</code>
     67  * first unload the <code>Test</code> class and load a new version of
     68  * the <code>Test</code> class.
     69  * <code>classFile</code> is a byte array containing the new contents of
     70  * the class file for the <code>Test</code> class.  The developers can
     71  * repatedly call <code>reload()</code> on the same <code>HotSwapper</code>
     72  * object so that they can reload a number of classes.
     73  *
     74  * @since 3.1
     75  */
     76 public class HotSwapper {
     77     private VirtualMachine jvm;
     78     private MethodEntryRequest request;
     79     private Map newClassFiles;
     80 
     81     private Trigger trigger;
     82 
     83     private static final String HOST_NAME = "localhost";
     84     private static final String TRIGGER_NAME = Trigger.class.getName();
     85 
     86     /**
     87      * Connects to the JVM.
     88      *
     89      * @param port	the port number used for the connection to the JVM.
     90      */
     91     public HotSwapper(int port)
     92         throws IOException, IllegalConnectorArgumentsException
     93     {
     94         this(Integer.toString(port));
     95     }
     96 
     97     /**
     98      * Connects to the JVM.
     99      *
    100      * @param port	the port number used for the connection to the JVM.
    101      */
    102     public HotSwapper(String port)
    103         throws IOException, IllegalConnectorArgumentsException
    104     {
    105         jvm = null;
    106         request = null;
    107         newClassFiles = null;
    108         trigger = new Trigger();
    109         AttachingConnector connector
    110             = (AttachingConnector)findConnector("com.sun.jdi.SocketAttach");
    111 
    112         Map arguments = connector.defaultArguments();
    113         ((Connector.Argument)arguments.get("hostname")).setValue(HOST_NAME);
    114         ((Connector.Argument)arguments.get("port")).setValue(port);
    115         jvm = connector.attach(arguments);
    116         EventRequestManager manager = jvm.eventRequestManager();
    117         request = methodEntryRequests(manager, TRIGGER_NAME);
    118     }
    119 
    120     private Connector findConnector(String connector) throws IOException {
    121         List connectors = Bootstrap.virtualMachineManager().allConnectors();
    122         Iterator iter = connectors.iterator();
    123         while (iter.hasNext()) {
    124             Connector con = (Connector)iter.next();
    125             if (con.name().equals(connector)) {
    126                 return con;
    127             }
    128         }
    129 
    130         throw new IOException("Not found: " + connector);
    131     }
    132 
    133     private static MethodEntryRequest methodEntryRequests(
    134                                 EventRequestManager manager,
    135                                 String classpattern) {
    136         MethodEntryRequest mereq = manager.createMethodEntryRequest();
    137         mereq.addClassFilter(classpattern);
    138         mereq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
    139         return mereq;
    140     }
    141 
    142     /* Stops triggering a hotswapper when reload() is called.
    143      */
    144     private void deleteEventRequest(EventRequestManager manager,
    145                                     MethodEntryRequest request) {
    146         manager.deleteEventRequest(request);
    147     }
    148 
    149     /**
    150      * Reloads a class.
    151      *
    152      * @param className		the fully-qualified class name.
    153      * @param classFile		the contents of the class file.
    154      */
    155     public void reload(String className, byte[] classFile) {
    156         ReferenceType classtype = toRefType(className);
    157         Map map = new HashMap();
    158         map.put(classtype, classFile);
    159         reload2(map, className);
    160     }
    161 
    162     /**
    163      * Reloads a class.
    164      *
    165      * @param classFiles	a map between fully-qualified class names
    166      *				and class files.  The type of the class names
    167      *				is <code>String</code> and the type of the
    168      *				class files is <code>byte[]</code>.
    169      */
    170     public void reload(Map classFiles) {
    171         Set set = classFiles.entrySet();
    172         Iterator it = set.iterator();
    173         Map map = new HashMap();
    174         String className = null;
    175         while (it.hasNext()) {
    176             Map.Entry e = (Map.Entry)it.next();
    177             className = (String)e.getKey();
    178             map.put(toRefType(className), e.getValue());
    179         }
    180 
    181         if (className != null)
    182             reload2(map, className + " etc.");
    183     }
    184 
    185     private ReferenceType toRefType(String className) {
    186         List list = jvm.classesByName(className);
    187         if (list == null || list.isEmpty())
    188             throw new RuntimeException("no such class: " + className);
    189         else
    190             return (ReferenceType)list.get(0);
    191     }
    192 
    193     private void reload2(Map map, String msg) {
    194         synchronized (trigger) {
    195             startDaemon();
    196             newClassFiles = map;
    197             request.enable();
    198             trigger.doSwap();
    199             request.disable();
    200             Map ncf = newClassFiles;
    201             if (ncf != null) {
    202                 newClassFiles = null;
    203                 throw new RuntimeException("failed to reload: " + msg);
    204             }
    205         }
    206     }
    207 
    208     private void startDaemon() {
    209         new Thread() {
    210             private void errorMsg(Throwable e) {
    211                 System.err.print("Exception in thread \"HotSwap\" ");
    212                 e.printStackTrace(System.err);
    213             }
    214 
    215             public void run() {
    216                 EventSet events = null;
    217                 try {
    218                     events = waitEvent();
    219                     EventIterator iter = events.eventIterator();
    220                     while (iter.hasNext()) {
    221                         Event event = iter.nextEvent();
    222                         if (event instanceof MethodEntryEvent) {
    223                             hotswap();
    224                             break;
    225                         }
    226                     }
    227                 }
    228                 catch (Throwable e) {
    229                     errorMsg(e);
    230                 }
    231                 try {
    232                     if (events != null)
    233                         events.resume();
    234                 }
    235                 catch (Throwable e) {
    236                     errorMsg(e);
    237                 }
    238             }
    239         }.start();
    240     }
    241 
    242     EventSet waitEvent() throws InterruptedException {
    243         EventQueue queue = jvm.eventQueue();
    244         return queue.remove();
    245     }
    246 
    247     void hotswap() {
    248         Map map = newClassFiles;
    249         jvm.redefineClasses(map);
    250         newClassFiles = null;
    251     }
    252 }
    253