001: /*
002: * Javassist, a Java-bytecode translator toolkit.
003: * Copyright (C) 1999-2006 Shigeru Chiba. All Rights Reserved.
004: *
005: * The contents of this file are subject to the Mozilla Public License Version
006: * 1.1 (the "License"); you may not use this file except in compliance with
007: * the License. Alternatively, the contents of this file may be used under
008: * the terms of the GNU Lesser General Public License Version 2.1 or later.
009: *
010: * Software distributed under the License is distributed on an "AS IS" basis,
011: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
012: * for the specific language governing rights and limitations under the
013: * License.
014: */
015:
016: package javassist.util;
017:
018: import com.sun.jdi.*;
019: import com.sun.jdi.connect.*;
020: import com.sun.jdi.event.*;
021: import com.sun.jdi.request.*;
022: import java.io.*;
023: import java.util.*;
024:
025: class Trigger {
026: void doSwap() {
027: }
028: }
029:
030: /**
031: * A utility class for dynamically reloading a class by
032: * the Java Platform Debugger Architecture (JPDA), or <it>HotSwap</code>.
033: * It works only with JDK 1.4 and later.
034: *
035: * <p><b>Note:</b> The new definition of the reloaded class must declare
036: * the same set of methods and fields as the original definition. The
037: * schema change between the original and new definitions is not allowed
038: * by the JPDA.
039: *
040: * <p>To use this class, the JVM must be launched with the following
041: * command line options:
042: *
043: * <ul>
044: * <p>For Java 1.4,<br>
045: * <pre>java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000</pre>
046: * <p>For Java 5,<br>
047: * <pre>java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000</pre>
048: * </ul>
049: *
050: * <p>Note that 8000 is the port number used by <code>HotSwapper</code>.
051: * Any port number can be specified. Since <code>HotSwapper</code> does not
052: * launch another JVM for running a target application, this port number
053: * is used only for inter-thread communication.
054: *
055: * <p>Furthermore, <code>JAVA_HOME/lib/tools.jar</code> must be included
056: * in the class path.
057: *
058: * <p>Using <code>HotSwapper</code> is easy. See the following example:
059: *
060: * <ul><pre>
061: * CtClass clazz = ...
062: * byte[] classFile = clazz.toBytecode();
063: * HotSwapper hs = new HostSwapper(8000); // 8000 is a port number.
064: * hs.reload("Test", classFile);
065: * </pre></ul>
066: *
067: * <p><code>reload()</code>
068: * first unload the <code>Test</code> class and load a new version of
069: * the <code>Test</code> class.
070: * <code>classFile</code> is a byte array containing the new contents of
071: * the class file for the <code>Test</code> class. The developers can
072: * repatedly call <code>reload()</code> on the same <code>HotSwapper</code>
073: * object so that they can reload a number of classes.
074: *
075: * @since 3.1
076: */
077: public class HotSwapper {
078: private VirtualMachine jvm;
079: private MethodEntryRequest request;
080: private Map newClassFiles;
081:
082: private Trigger trigger;
083:
084: private static final String HOST_NAME = "localhost";
085: private static final String TRIGGER_NAME = Trigger.class.getName();
086:
087: /**
088: * Connects to the JVM.
089: *
090: * @param port the port number used for the connection to the JVM.
091: */
092: public HotSwapper(int port) throws IOException,
093: IllegalConnectorArgumentsException {
094: this (Integer.toString(port));
095: }
096:
097: /**
098: * Connects to the JVM.
099: *
100: * @param port the port number used for the connection to the JVM.
101: */
102: public HotSwapper(String port) throws IOException,
103: IllegalConnectorArgumentsException {
104: jvm = null;
105: request = null;
106: newClassFiles = null;
107: trigger = new Trigger();
108: AttachingConnector connector = (AttachingConnector) findConnector("com.sun.jdi.SocketAttach");
109:
110: Map arguments = connector.defaultArguments();
111: ((Connector.Argument) arguments.get("hostname"))
112: .setValue(HOST_NAME);
113: ((Connector.Argument) arguments.get("port")).setValue(port);
114: jvm = connector.attach(arguments);
115: EventRequestManager manager = jvm.eventRequestManager();
116: request = methodEntryRequests(manager, TRIGGER_NAME);
117: }
118:
119: private Connector findConnector(String connector)
120: throws IOException {
121: List connectors = Bootstrap.virtualMachineManager()
122: .allConnectors();
123: Iterator iter = connectors.iterator();
124: while (iter.hasNext()) {
125: Connector con = (Connector) iter.next();
126: if (con.name().equals(connector)) {
127: return con;
128: }
129: }
130:
131: throw new IOException("Not found: " + connector);
132: }
133:
134: private static MethodEntryRequest methodEntryRequests(
135: EventRequestManager manager, 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 a 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: } catch (Throwable e) {
228: errorMsg(e);
229: }
230: try {
231: if (events != null)
232: events.resume();
233: } catch (Throwable e) {
234: errorMsg(e);
235: }
236: }
237: }.start();
238: }
239:
240: EventSet waitEvent() throws InterruptedException {
241: EventQueue queue = jvm.eventQueue();
242: return queue.remove();
243: }
244:
245: void hotswap() {
246: Map map = newClassFiles;
247: jvm.redefineClasses(map);
248: newClassFiles = null;
249: }
250: }
|