You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

HotSwapper.java 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /*
  2. * Javassist, a Java-bytecode translator toolkit.
  3. * Copyright (C) 1999- 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. * or the Apache License Version 2.0.
  10. *
  11. * Software distributed under the License is distributed on an "AS IS" basis,
  12. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13. * for the specific language governing rights and limitations under the
  14. * License.
  15. */
  16. package javassist.util;
  17. import com.sun.jdi.*;
  18. import com.sun.jdi.connect.*;
  19. import com.sun.jdi.event.*;
  20. import com.sun.jdi.request.*;
  21. import java.io.*;
  22. import java.util.*;
  23. class Trigger {
  24. void doSwap() {}
  25. }
  26. /**
  27. * A utility class for dynamically reloading a class by
  28. * the Java Platform Debugger Architecture (JPDA), or <i>HotSwap</i>.
  29. * It works only with JDK 1.4 and later.
  30. *
  31. * <p><b>Note:</b> The new definition of the reloaded class must declare
  32. * the same set of methods and fields as the original definition. The
  33. * schema change between the original and new definitions is not allowed
  34. * by the JPDA.
  35. *
  36. * <p>To use this class, the JVM must be launched with the following
  37. * command line options:
  38. *
  39. * <p>For Java 1.4,<br>
  40. * <pre>java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000</pre>
  41. * <p>For Java 5,<br>
  42. * <pre>java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000</pre>
  43. *
  44. * <p>Note that 8000 is the port number used by <code>HotSwapper</code>.
  45. * Any port number can be specified. Since <code>HotSwapper</code> does not
  46. * launch another JVM for running a target application, this port number
  47. * is used only for inter-thread communication.
  48. *
  49. * <p>Furthermore, <code>JAVA_HOME/lib/tools.jar</code> must be included
  50. * in the class path.
  51. *
  52. * <p>Using <code>HotSwapper</code> is easy. See the following example:
  53. *
  54. * <pre>
  55. * CtClass clazz = ...
  56. * byte[] classFile = clazz.toBytecode();
  57. * HotSwapper hs = new HostSwapper(8000); // 8000 is a port number.
  58. * hs.reload("Test", classFile);
  59. * </pre>
  60. *
  61. * <p><code>reload()</code>
  62. * first unload the <code>Test</code> class and load a new version of
  63. * the <code>Test</code> class.
  64. * <code>classFile</code> is a byte array containing the new contents of
  65. * the class file for the <code>Test</code> class. The developers can
  66. * repatedly call <code>reload()</code> on the same <code>HotSwapper</code>
  67. * object so that they can reload a number of classes.
  68. *
  69. * @since 3.1
  70. */
  71. public class HotSwapper {
  72. private VirtualMachine jvm;
  73. private MethodEntryRequest request;
  74. private Map newClassFiles;
  75. private Trigger trigger;
  76. private static final String HOST_NAME = "localhost";
  77. private static final String TRIGGER_NAME = Trigger.class.getName();
  78. /**
  79. * Connects to the JVM.
  80. *
  81. * @param port the port number used for the connection to the JVM.
  82. */
  83. public HotSwapper(int port)
  84. throws IOException, IllegalConnectorArgumentsException
  85. {
  86. this(Integer.toString(port));
  87. }
  88. /**
  89. * Connects to the JVM.
  90. *
  91. * @param port the port number used for the connection to the JVM.
  92. */
  93. public HotSwapper(String port)
  94. throws IOException, IllegalConnectorArgumentsException
  95. {
  96. jvm = null;
  97. request = null;
  98. newClassFiles = null;
  99. trigger = new Trigger();
  100. AttachingConnector connector
  101. = (AttachingConnector)findConnector("com.sun.jdi.SocketAttach");
  102. Map arguments = connector.defaultArguments();
  103. ((Connector.Argument)arguments.get("hostname")).setValue(HOST_NAME);
  104. ((Connector.Argument)arguments.get("port")).setValue(port);
  105. jvm = connector.attach(arguments);
  106. EventRequestManager manager = jvm.eventRequestManager();
  107. request = methodEntryRequests(manager, TRIGGER_NAME);
  108. }
  109. private Connector findConnector(String connector) throws IOException {
  110. List connectors = Bootstrap.virtualMachineManager().allConnectors();
  111. Iterator iter = connectors.iterator();
  112. while (iter.hasNext()) {
  113. Connector con = (Connector)iter.next();
  114. if (con.name().equals(connector)) {
  115. return con;
  116. }
  117. }
  118. throw new IOException("Not found: " + connector);
  119. }
  120. private static MethodEntryRequest methodEntryRequests(
  121. EventRequestManager manager,
  122. String classpattern) {
  123. MethodEntryRequest mereq = manager.createMethodEntryRequest();
  124. mereq.addClassFilter(classpattern);
  125. mereq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
  126. return mereq;
  127. }
  128. /* Stops triggering a hotswapper when reload() is called.
  129. */
  130. private void deleteEventRequest(EventRequestManager manager,
  131. MethodEntryRequest request) {
  132. manager.deleteEventRequest(request);
  133. }
  134. /**
  135. * Reloads a class.
  136. *
  137. * @param className the fully-qualified class name.
  138. * @param classFile the contents of the class file.
  139. */
  140. public void reload(String className, byte[] classFile) {
  141. ReferenceType classtype = toRefType(className);
  142. Map map = new HashMap();
  143. map.put(classtype, classFile);
  144. reload2(map, className);
  145. }
  146. /**
  147. * Reloads a class.
  148. *
  149. * @param classFiles a map between fully-qualified class names
  150. * and class files. The type of the class names
  151. * is <code>String</code> and the type of the
  152. * class files is <code>byte[]</code>.
  153. */
  154. public void reload(Map classFiles) {
  155. Set set = classFiles.entrySet();
  156. Iterator it = set.iterator();
  157. Map map = new HashMap();
  158. String className = null;
  159. while (it.hasNext()) {
  160. Map.Entry e = (Map.Entry)it.next();
  161. className = (String)e.getKey();
  162. map.put(toRefType(className), e.getValue());
  163. }
  164. if (className != null)
  165. reload2(map, className + " etc.");
  166. }
  167. private ReferenceType toRefType(String className) {
  168. List list = jvm.classesByName(className);
  169. if (list == null || list.isEmpty())
  170. throw new RuntimeException("no such class: " + className);
  171. else
  172. return (ReferenceType)list.get(0);
  173. }
  174. private void reload2(Map map, String msg) {
  175. synchronized (trigger) {
  176. startDaemon();
  177. newClassFiles = map;
  178. request.enable();
  179. trigger.doSwap();
  180. request.disable();
  181. Map ncf = newClassFiles;
  182. if (ncf != null) {
  183. newClassFiles = null;
  184. throw new RuntimeException("failed to reload: " + msg);
  185. }
  186. }
  187. }
  188. private void startDaemon() {
  189. new Thread() {
  190. private void errorMsg(Throwable e) {
  191. System.err.print("Exception in thread \"HotSwap\" ");
  192. e.printStackTrace(System.err);
  193. }
  194. public void run() {
  195. EventSet events = null;
  196. try {
  197. events = waitEvent();
  198. EventIterator iter = events.eventIterator();
  199. while (iter.hasNext()) {
  200. Event event = iter.nextEvent();
  201. if (event instanceof MethodEntryEvent) {
  202. hotswap();
  203. break;
  204. }
  205. }
  206. }
  207. catch (Throwable e) {
  208. errorMsg(e);
  209. }
  210. try {
  211. if (events != null)
  212. events.resume();
  213. }
  214. catch (Throwable e) {
  215. errorMsg(e);
  216. }
  217. }
  218. }.start();
  219. }
  220. EventSet waitEvent() throws InterruptedException {
  221. EventQueue queue = jvm.eventQueue();
  222. return queue.remove();
  223. }
  224. void hotswap() {
  225. Map map = newClassFiles;
  226. jvm.redefineClasses(map);
  227. newClassFiles = null;
  228. }
  229. }