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

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