/* * Javassist, a Java-bytecode translator toolkit. * Copyright (C) 1999-2007 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. * * 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 javassist.util; import com.sun.jdi.*; import com.sun.jdi.connect.*; import com.sun.jdi.event.*; import com.sun.jdi.request.*; import java.io.*; import java.util.*; class Trigger { void doSwap() {} } /** * A utility class for dynamically reloading a class by * the Java Platform Debugger Architecture (JPDA), or HotSwap. * It works only with JDK 1.4 and later. * *

Note: 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. * *

To use this class, the JVM must be launched with the following * command line options: * *

* *

Note that 8000 is the port number used by HotSwapper. * Any port number can be specified. Since HotSwapper does not * launch another JVM for running a target application, this port number * is used only for inter-thread communication. * *

Furthermore, JAVA_HOME/lib/tools.jar must be included * in the class path. * *

Using HotSwapper is easy. See the following example: * *

* *

reload() * first unload the Test class and load a new version of * the Test class. * classFile is a byte array containing the new contents of * the class file for the Test class. The developers can * repatedly call reload() on the same HotSwapper * object so that they can reload a number of classes. * * @since 3.1 */ public class HotSwapper { 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 HotSwapper(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 HotSwapper(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 String and the type of the * class files is byte[]. */ 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; } }