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