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