HotSwapper.java revision 69e17611504376e4d4603925f8528dfc890fd2c6
1d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)/*
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Javassist, a Java-bytecode translator toolkit.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
5d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles) * The contents of this file are subject to the Mozilla Public License Version
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 1.1 (the "License"); you may not use this file except in compliance with
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the License.  Alternatively, the contents of this file may be used under
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the terms of the GNU Lesser General Public License Version 2.1 or later.
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Software distributed under the License is distributed on an "AS IS" basis,
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * for the specific language governing rights and limitations under the
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * License.
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)package javassist.util;
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.sun.jdi.*;
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.sun.jdi.connect.*;
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.sun.jdi.event.*;
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import com.sun.jdi.request.*;
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.io.*;
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.*;
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Trigger {
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    void doSwap() {}
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * A utility class for dynamically reloading a class by
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the Java Platform Debugger Architecture (JPDA), or <it>HotSwap</code>.
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * It works only with JDK 1.4 and later.
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p><b>Note:</b> The new definition of the reloaded class must declare
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the same set of methods and fields as the original definition.  The
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * schema change between the original and new definitions is not allowed
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * by the JPDA.
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p>To use this class, the JVM must be launched with the following
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * command line options:
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <ul>
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p>For Java 1.4,<br>
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <pre>java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000</pre>
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p>For Java 5,<br>
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <pre>java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000</pre>
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * </ul>
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p>Note that 8000 is the port number used by <code>HotSwapper</code>.
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Any port number can be specified.  Since <code>HotSwapper</code> does not
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * launch another JVM for running a target application, this port number
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * is used only for inter-thread communication.
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p>Furthermore, <code>JAVA_HOME/lib/tools.jar</code> must be included
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * in the class path.
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p>Using <code>HotSwapper</code> is easy.  See the following example:
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <ul><pre>
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * CtClass clazz = ...
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * byte[] classFile = clazz.toBytecode();
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * HotSwapper hs = new HostSwapper(8000);  // 8000 is a port number.
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * hs.reload("Test", classFile);
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * </pre></ul>
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p><code>reload()</code>
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * first unload the <code>Test</code> class and load a new version of
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the <code>Test</code> class.
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <code>classFile</code> is a byte array containing the new contents of
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the class file for the <code>Test</code> class.  The developers can
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * repatedly call <code>reload()</code> on the same <code>HotSwapper</code>
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * object so that they can reload a number of classes.
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @since 3.1
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)public class HotSwapper {
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private VirtualMachine jvm;
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private MethodEntryRequest request;
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private Map newClassFiles;
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private Trigger trigger;
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final String HOST_NAME = "localhost";
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final String TRIGGER_NAME = Trigger.class.getName();
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Connects to the JVM.
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     *
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @param port	the port number used for the connection to the JVM.
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public HotSwapper(int port)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        throws IOException, IllegalConnectorArgumentsException
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    {
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this(Integer.toString(port));
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Connects to the JVM.
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     *
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @param port	the port number used for the connection to the JVM.
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public HotSwapper(String port)
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        throws IOException, IllegalConnectorArgumentsException
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    {
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        jvm = null;
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        request = null;
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        newClassFiles = null;
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        trigger = new Trigger();
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        AttachingConnector connector
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            = (AttachingConnector)findConnector("com.sun.jdi.SocketAttach");
111d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)
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