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.

HotSwapTool.java 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /*
  2. * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
  3. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4. *
  5. * This code is free software; you can redistribute it and/or modify it
  6. * under the terms of the GNU General Public License version 2 only, as
  7. * published by the Free Software Foundation.
  8. *
  9. * This code is distributed in the hope that it will be useful, but WITHOUT
  10. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  12. * version 2 for more details (a copy is included in the LICENSE file that
  13. * accompanied this code).
  14. *
  15. * You should have received a copy of the GNU General Public License version
  16. * 2 along with this work; if not, write to the Free Software Foundation,
  17. * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18. *
  19. * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20. * or visit www.oracle.com if you need additional information or have any
  21. * questions.
  22. *
  23. */
  24. package com.github.dcevm;
  25. import org.objectweb.asm.ClassReader;
  26. import org.objectweb.asm.ClassWriter;
  27. import org.objectweb.asm.Type;
  28. import java.io.*;
  29. import java.lang.instrument.UnmodifiableClassException;
  30. import java.net.URL;
  31. import java.util.HashMap;
  32. import java.util.Hashtable;
  33. import java.util.Map;
  34. /**
  35. * @author Thomas Wuerthinger
  36. * @author Kerstin Breiteneder
  37. * @author Christoph Wimberger
  38. * @author Ivan Dubrov
  39. */
  40. public class HotSwapTool {
  41. /**
  42. * Prefix for the version number in the class name. The class bytes are modified that this string including
  43. * the following number is removed. This means that e.g. A___2 is treated as A anywhere in the source code. This is introduced
  44. * to make the IDE not complain about multiple defined classes.
  45. */
  46. public static final String IDENTIFIER = "___";
  47. private static final String CLASS_FILE_SUFFIX = ".class";
  48. private static Map<Class<?>, Integer> currentVersion = new Hashtable<Class<?>, Integer>();
  49. private static Redefiner redefiner;
  50. private static int redefinitionCount;
  51. private static long totalTime;
  52. static {
  53. try {
  54. //redefiner = new JDIRedefiner(4000);
  55. redefiner = new InstrumentationRedefiner();
  56. } catch (Exception e) {
  57. throw new IllegalStateException(e);
  58. }
  59. }
  60. /**
  61. * Returns the current version of the inner classes of a specified outer class.
  62. *
  63. * @param baseClass the outer class whose version is queried
  64. * @return the version of the inner classes of the specified outer class
  65. */
  66. public static int getCurrentVersion(Class<?> baseClass) {
  67. if (!currentVersion.containsKey(baseClass)) {
  68. currentVersion.put(baseClass, 0);
  69. }
  70. return currentVersion.get(baseClass);
  71. }
  72. /**
  73. * Performs an explicit shutdown and disconnects from the VM.
  74. */
  75. public static void shutdown() throws IOException {
  76. redefiner.close();
  77. redefiner = null;
  78. }
  79. private static Map<Class<?>, byte[]> buildRedefinitionMap(Map<String, File> classes) throws IOException, ClassNotFoundException {
  80. // Collect rename rules
  81. // Also, makes sure all classes are loaded in the VM, before they are redefined
  82. final Map<String, String> typeMappings = new HashMap<String, String>();
  83. for (String name : classes.keySet()) {
  84. Class<?> clazz = Class.forName(name); // FIXME: classloader?
  85. ClassRedefinitionPolicy policy = clazz.getAnnotation(ClassRedefinitionPolicy.class);
  86. Class<?> replacement = (policy != null && policy.alias() != ClassRedefinitionPolicy.NoClass.class) ?
  87. policy.alias() : clazz;
  88. typeMappings.put(Type.getInternalName(clazz), stripVersion(Type.getInternalName(replacement)));
  89. }
  90. Map<Class<?>, byte[]> classesMap = new HashMap<Class<?>, byte[]>();
  91. for (File file : classes.values()) {
  92. loadAdaptedClass(file, typeMappings, classesMap);
  93. }
  94. return classesMap;
  95. }
  96. private static void loadAdaptedClass(File file, Map<String, String> typeMappnigs, Map<Class<?>, byte[]> result) throws IOException, ClassNotFoundException {
  97. ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
  98. TestClassAdapter adapter = new TestClassAdapter(writer, typeMappnigs);
  99. InputStream in = new FileInputStream(file);
  100. try {
  101. new ClassReader(in).accept(adapter, ClassReader.EXPAND_FRAMES);
  102. } finally {
  103. try {
  104. in.close();
  105. } catch (IOException e) {
  106. // Ignore.
  107. }
  108. }
  109. byte[] bytes = writer.toByteArray();
  110. String className = adapter.getClassName().replace('/', '.');
  111. result.put(Class.forName(className), bytes); // FIXME: ClassLoader...
  112. }
  113. /**
  114. * Redefines all inner classes of a outer class to a specified version. Inner classes who do not have a particular
  115. * representation for a version remain unchanged.
  116. *
  117. * @param outerClass the outer class whose inner classes should be redefined
  118. * @param versionNumber the target version number
  119. */
  120. public static void toVersion(Class<?> outerClass, int versionNumber) {
  121. assert versionNumber >= 0;
  122. if (versionNumber == getCurrentVersion(outerClass)) {
  123. // Nothing to do!
  124. return;
  125. }
  126. Map<String, File> files = findClassesWithVersion(outerClass, versionNumber);
  127. try {
  128. Map<Class<?>, byte[]> map = buildRedefinitionMap(files);
  129. long startTime = System.currentTimeMillis();
  130. redefiner.redefineClasses(map);
  131. long curTime = System.currentTimeMillis() - startTime;
  132. totalTime += curTime;
  133. redefinitionCount++;
  134. } catch (UnmodifiableClassException e) {
  135. throw new UnsupportedOperationException(e);
  136. } catch (ClassNotFoundException e) {
  137. throw new RuntimeException("Cannot redefine classes", e);
  138. } catch (IOException e) {
  139. throw new RuntimeException("Cannot redefine classes", e);
  140. }
  141. setCurrentVersion(outerClass, versionNumber);
  142. }
  143. private static Map<String, File> findClassesWithVersion(Class<?> baseClass, int version) {
  144. Map<String, File> classes = new HashMap<String, File>();
  145. String packageName = baseClass.getPackage().getName().replace('.', '/');
  146. URL url = baseClass.getClassLoader().getResource(packageName);
  147. if (url == null) {
  148. throw new IllegalArgumentException("Cannot find URL corresponding to the package '" + packageName + "'");
  149. }
  150. File folder = new File(url.getFile());
  151. for (File f : folder.listFiles(IsClassFile.INSTANCE)) {
  152. String fileName = f.getName();
  153. String simpleName = f.getName().substring(0, f.getName().length() - CLASS_FILE_SUFFIX.length());
  154. String name = baseClass.getPackage().getName() + '.' + simpleName;
  155. if (isInnerClass(name, baseClass) && parseClassVersion(fileName) == version) {
  156. classes.put(name, f);
  157. }
  158. }
  159. return classes;
  160. }
  161. private enum IsClassFile implements FilenameFilter {
  162. INSTANCE;
  163. @Override
  164. public boolean accept(File dir, String name) {
  165. return name.endsWith(CLASS_FILE_SUFFIX);
  166. }
  167. }
  168. private static boolean isInnerClass(String name, Class<?> baseClass) {
  169. return name.startsWith(baseClass.getName() + "$");
  170. }
  171. private static void setCurrentVersion(Class<?> baseClass, int value) {
  172. currentVersion.put(baseClass, value);
  173. }
  174. /**
  175. * Parse version of the class from the class name. Classes are named in the form of [Name]___[Version]
  176. */
  177. private static int parseClassVersion(String name) {
  178. int index = name.indexOf(IDENTIFIER);
  179. if (index == -1) {
  180. return 0;
  181. }
  182. return Integer.valueOf(name.substring(index + IDENTIFIER.length(), name.length() - CLASS_FILE_SUFFIX.length()));
  183. }
  184. private static String stripVersion(String className) {
  185. int index = className.indexOf(IDENTIFIER);
  186. if (index == -1) {
  187. return className;
  188. }
  189. return className.substring(0, index);
  190. }
  191. public static void resetTimings() {
  192. redefinitionCount = 0;
  193. totalTime = 0;
  194. }
  195. public static int getRedefinitionCount() {
  196. return redefinitionCount;
  197. }
  198. public static long getTotalTime() {
  199. return totalTime;
  200. }
  201. }