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.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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. import java.util.regex.Matcher;
  35. import java.util.regex.Pattern;
  36. /**
  37. * @author Thomas Wuerthinger
  38. * @author Kerstin Breiteneder
  39. * @author Christoph Wimberger
  40. * @author Ivan Dubrov
  41. */
  42. public class HotSwapTool {
  43. /**
  44. * Prefix for the version number in the class name. The class bytes are modified that this string including
  45. * the following number is removed. This means that e.g. A___2 is treated as A anywhere in the source code. This is introduced
  46. * to make the IDE not complain about multiple defined classes.
  47. */
  48. public static final Pattern VERSION_PATTERN = Pattern.compile("___([0-9]+)");
  49. public static final Pattern VERSION_MATCH = Pattern.compile(".*(___([0-9]+))$");
  50. private static final String CLASS_FILE_SUFFIX = ".class";
  51. private static Map<Class<?>, Integer> currentVersion = new Hashtable<Class<?>, Integer>();
  52. private static Redefiner redefiner;
  53. private static int redefinitionCount;
  54. private static long totalTime;
  55. static {
  56. try {
  57. //redefiner = new JDIRedefiner(4000);
  58. redefiner = new InstrumentationRedefiner();
  59. } catch (Exception e) {
  60. throw new IllegalStateException(e);
  61. }
  62. }
  63. /**
  64. * Returns the current version of the inner classes of a specified outer class.
  65. *
  66. * @param baseClass the outer class whose version is queried
  67. * @return the version of the inner classes of the specified outer class
  68. */
  69. public static int getCurrentVersion(Class<?> baseClass) {
  70. if (!currentVersion.containsKey(baseClass)) {
  71. currentVersion.put(baseClass, 0);
  72. }
  73. return currentVersion.get(baseClass);
  74. }
  75. /**
  76. * Performs an explicit shutdown and disconnects from the VM.
  77. */
  78. public static void shutdown() throws IOException {
  79. redefiner.close();
  80. redefiner = null;
  81. }
  82. private static Map<Class<?>, byte[]> buildRedefinitionMap(Map<String, File> classes) throws IOException, ClassNotFoundException {
  83. // Collect rename rules
  84. // Also, makes sure all classes are loaded in the VM, before they are redefined
  85. final Map<String, String> typeMappings = new HashMap<String, String>();
  86. for (String name : classes.keySet()) {
  87. Class<?> clazz = Class.forName(name); // FIXME: classloader?
  88. ClassRedefinitionPolicy policy = clazz.getAnnotation(ClassRedefinitionPolicy.class);
  89. Class<?> replacement = (policy != null && policy.alias() != ClassRedefinitionPolicy.NoClass.class) ?
  90. policy.alias() : clazz;
  91. typeMappings.put(Type.getInternalName(clazz), stripVersion(Type.getInternalName(replacement)));
  92. }
  93. Map<Class<?>, byte[]> classesMap = new HashMap<Class<?>, byte[]>();
  94. for (File file : classes.values()) {
  95. loadAdaptedClass(file, typeMappings, classesMap);
  96. }
  97. return classesMap;
  98. }
  99. private static void loadAdaptedClass(File file, Map<String, String> typeMappnigs, Map<Class<?>, byte[]> result) throws IOException, ClassNotFoundException {
  100. ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
  101. TestClassAdapter adapter = new TestClassAdapter(writer, typeMappnigs);
  102. InputStream in = new FileInputStream(file);
  103. try {
  104. new ClassReader(in).accept(adapter, ClassReader.EXPAND_FRAMES);
  105. } finally {
  106. try {
  107. in.close();
  108. } catch (IOException e) {
  109. // Ignore.
  110. }
  111. }
  112. byte[] bytes = writer.toByteArray();
  113. String className = adapter.getClassName().replace('/', '.');
  114. result.put(Class.forName(className), bytes); // FIXME: ClassLoader...
  115. }
  116. /**
  117. * Redefines all inner classes of a outer class to a specified version. Inner classes who do not have a particular
  118. * representation for a version remain unchanged.
  119. *
  120. * @param outerClass the outer class whose inner classes should be redefined
  121. * @param versionNumber the target version number
  122. */
  123. public static void toVersion(Class<?> outerClass, int versionNumber, Class<?>... extraClasses) {
  124. assert versionNumber >= 0;
  125. if (versionNumber == getCurrentVersion(outerClass)) {
  126. // Nothing to do!
  127. return;
  128. }
  129. Map<String, File> files = findClassesWithVersion(outerClass, versionNumber);
  130. for (Class<?> extra : extraClasses) {
  131. if (parseClassVersion(extra.getSimpleName()) == versionNumber) {
  132. String packageName = extra.getPackage().getName().replace('.', '/');
  133. URL url = extra.getClassLoader().getResource(packageName);
  134. if (url == null) {
  135. throw new IllegalArgumentException("Cannot find URL corresponding to the package '" + packageName + "'");
  136. }
  137. File file = new File(url.getFile(), extra.getSimpleName() + ".class");
  138. files.put(extra.getName(), file);
  139. }
  140. }
  141. try {
  142. Map<Class<?>, byte[]> map = buildRedefinitionMap(files);
  143. long startTime = System.currentTimeMillis();
  144. redefiner.redefineClasses(map);
  145. long curTime = System.currentTimeMillis() - startTime;
  146. totalTime += curTime;
  147. redefinitionCount++;
  148. } catch (UnmodifiableClassException e) {
  149. throw new UnsupportedOperationException(e);
  150. } catch (ClassNotFoundException e) {
  151. throw new RuntimeException("Cannot redefine classes", e);
  152. } catch (IOException e) {
  153. throw new RuntimeException("Cannot redefine classes", e);
  154. }
  155. setCurrentVersion(outerClass, versionNumber);
  156. }
  157. private static Map<String, File> findClassesWithVersion(Class<?> baseClass, int version) {
  158. Map<String, File> classes = new HashMap<String, File>();
  159. String packageName = baseClass.getPackage().getName().replace('.', '/');
  160. URL url = baseClass.getClassLoader().getResource(packageName);
  161. if (url == null) {
  162. throw new IllegalArgumentException("Cannot find URL corresponding to the package '" + packageName + "'");
  163. }
  164. File folder = new File(url.getFile());
  165. for (File f : folder.listFiles(IsClassFile.INSTANCE)) {
  166. String simpleName = f.getName().substring(0, f.getName().length() - CLASS_FILE_SUFFIX.length());
  167. String name = baseClass.getPackage().getName() + '.' + simpleName;
  168. if (isInnerClass(name, baseClass) && parseClassVersion(simpleName) == version) {
  169. classes.put(name, f);
  170. }
  171. }
  172. return classes;
  173. }
  174. private enum IsClassFile implements FilenameFilter {
  175. INSTANCE;
  176. @Override
  177. public boolean accept(File dir, String name) {
  178. return name.endsWith(CLASS_FILE_SUFFIX);
  179. }
  180. }
  181. private static boolean isInnerClass(String name, Class<?> baseClass) {
  182. return name.startsWith(baseClass.getName() + "$");
  183. }
  184. private static void setCurrentVersion(Class<?> baseClass, int value) {
  185. currentVersion.put(baseClass, value);
  186. }
  187. /**
  188. * Parse version of the class from the class name. Classes are named in the form of [Name]___[Version]
  189. */
  190. private static int parseClassVersion(String simpleName) {
  191. Matcher m = VERSION_MATCH.matcher(simpleName);
  192. return m.matches() ? Integer.valueOf(m.group(2)) : 0;
  193. }
  194. private static String stripVersion(String className) {
  195. Matcher m = VERSION_MATCH.matcher(className);
  196. if (m.matches()) {
  197. return className.substring(0, m.start(1)) + className.substring(m.end(1));
  198. } else {
  199. return className;
  200. }
  201. }
  202. public static void resetTimings() {
  203. redefinitionCount = 0;
  204. totalTime = 0;
  205. }
  206. public static int getRedefinitionCount() {
  207. return redefinitionCount;
  208. }
  209. public static long getTotalTime() {
  210. return totalTime;
  211. }
  212. }