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
16package javassist.util;
17
18import com.sun.jdi.*;
19import com.sun.jdi.connect.*;
20import com.sun.jdi.event.*;
21import com.sun.jdi.request.*;
22import java.io.*;
23import java.util.*;
24
25class 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 */
76public 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