/src/kilim/tools/Weaver.java

http://github.com/kilim/kilim · Java · 325 lines · 224 code · 33 blank · 68 comment · 49 complexity · 90550abedb33b4a54cdc3753e8e5e2c4 MD5 · raw file

  1. /* Copyright (c) 2006, Sriram Srinivasan, 2016 nqzero
  2. *
  3. * You may distribute this software under the terms of the license
  4. * specified in the file "License"
  5. */
  6. package kilim.tools;
  7. import kilim.analysis.KilimContext;
  8. import java.io.BufferedInputStream;
  9. import java.io.ByteArrayInputStream;
  10. import java.io.File;
  11. import java.io.FileInputStream;
  12. import java.io.FileOutputStream;
  13. import java.io.IOException;
  14. import java.io.InputStream;
  15. import java.net.URL;
  16. import java.net.URLClassLoader;
  17. import java.util.ArrayList;
  18. import java.util.List;
  19. import java.util.regex.Pattern;
  20. import kilim.KilimException;
  21. import kilim.WeavingClassLoader;
  22. import kilim.analysis.ClassInfo;
  23. import kilim.analysis.ClassWeaver;
  24. import kilim.analysis.FileLister;
  25. import kilim.mirrors.CachedClassMirrors;
  26. /**
  27. * This class supports both command-line and run time weaving of Kilim bytecode.
  28. */
  29. public class Weaver {
  30. public static String outputDir = null;
  31. public static boolean verbose = true;
  32. public static boolean force = false;
  33. public static boolean proxy = true;
  34. public static Pattern excludePattern = null;
  35. static int err = 0;
  36. public KilimContext context;
  37. public Weaver(KilimContext $context) {
  38. context = $context==null ? new KilimContext() : $context;
  39. }
  40. /**
  41. * <pre>
  42. * Usage: java kilim.tools.Weaver -d &lt;output directory&gt; {source classe, jar, directory ...}
  43. * </pre>
  44. *
  45. * If directory names or jar files are given, all classes in that container are processed. It is
  46. * perfectly fine to specify the same directory for source and output like this:
  47. * <pre>
  48. * java kilim.tools.Weaver -d ./classes ./classes
  49. * </pre>
  50. *
  51. * by default, each element is added to the classpath (use -c to suppress classpath augmentation)
  52. *
  53. * arguments:
  54. * <ul>
  55. * <li>-d directory: write output to directory (required)</li>
  56. * <li>-f: force, write output even if output file is newer than source</li>
  57. * <li>-c: don't add source class list to the classpath</li>
  58. * <li>-h: print help info</li>
  59. * <li>-q: quiet</li>
  60. * <li>-x regex: exclude, skip classes matching regex</li>
  61. * </ul>
  62. *
  63. * Ensure that all classes to be woven are in the classpath. The output directory does not have to be
  64. * in the classpath during weaving.
  65. *
  66. * @see #weave(List) for run-time weaving.
  67. */
  68. public static void main(String[] args) throws IOException {
  69. ArrayList<String> names = parseArgs(args);
  70. doMain(names.toArray(new String [] {}),null);
  71. if (err > 0) System.exit(err);
  72. }
  73. private static String [] concat(String [] a,String [] b) {
  74. String [] c = new String[a.length + b.length];
  75. System.arraycopy(a,0,c,0,a.length);
  76. System.arraycopy(b,0,c,a.length,b.length);
  77. return c;
  78. }
  79. public static int doMain(String [] names,String [] classpath) throws IOException {
  80. mkdir(outputDir);
  81. Weaver weaver;
  82. if (proxy) {
  83. ClassLoader current = Weaver.class.getClassLoader();
  84. String [] composite = classpath==null ? names : concat(names,classpath);
  85. URL [] paths = WeavingClassLoader.getURLs(composite);
  86. CachedClassMirrors ccm = new CachedClassMirrors(new URLClassLoader(paths,current));
  87. weaver = new Weaver(new KilimContext(ccm));
  88. }
  89. else
  90. weaver = new Weaver(null);
  91. String currentName = null;
  92. for (String name : names) {
  93. try {
  94. if (name.endsWith(".class")) {
  95. if (exclude(name))
  96. continue;
  97. currentName = name;
  98. weaver.weaveFile(name, new BufferedInputStream(new FileInputStream(name)));
  99. } else if (name.endsWith(".jar")) {
  100. for (FileLister.Entry fe : new FileLister(name)) {
  101. currentName = fe.getFileName();
  102. if (currentName.endsWith(".class")) {
  103. currentName = currentName.substring(0, currentName.length() - 6)
  104. .replace('/', '.');
  105. if (exclude(currentName))
  106. continue;
  107. weaver.weaveFile(currentName, fe.getInputStream());
  108. }
  109. }
  110. } else if (new File(name).isDirectory()) {
  111. for (FileLister.Entry fe : new FileLister(name)) {
  112. currentName = fe.getFileName();
  113. if (currentName.endsWith(".class")) {
  114. if (exclude(currentName))
  115. continue;
  116. if (!force && fe.check(outputDir))
  117. continue;
  118. weaver.weaveFile(currentName, fe.getInputStream());
  119. }
  120. }
  121. } else {
  122. System.out.println("skipping class (support removed): " + name);
  123. }
  124. } catch (KilimException ke) {
  125. System.err.println("Error weaving " + currentName + ". " + ke.getMessage());
  126. // ke.printStackTrace();
  127. System.exit(1);
  128. } catch (IOException ioe) {
  129. System.err.println("Unable to find/process '" + currentName + "'");
  130. System.exit(1);
  131. } catch (Throwable t) {
  132. System.err.println("Error weaving " + currentName);
  133. t.printStackTrace();
  134. System.exit(1);
  135. }
  136. }
  137. return err;
  138. }
  139. static boolean exclude(String name) {
  140. return excludePattern == null ? false : excludePattern.matcher(name).find();
  141. }
  142. // non-static to allow easy usage from alternative classloaders
  143. public ClassWeaver weave(InputStream is) {
  144. ClassWeaver cw = null;
  145. if (is==null) return null;
  146. try {
  147. cw = new ClassWeaver(context,is);
  148. cw.weave();
  149. if (outputDir != null)
  150. writeClasses(cw);
  151. }
  152. catch (IOException ex) {}
  153. return cw;
  154. }
  155. public void weaveFile(String name, InputStream is) throws IOException {
  156. try {
  157. ClassWeaver cw = new ClassWeaver(context,is);
  158. cw.weave();
  159. writeClasses(cw);
  160. } catch (KilimException ke) {
  161. System.err.println("***** Error weaving " + name + ". " + ke.getMessage());
  162. // ke.printStackTrace();
  163. err = 1;
  164. } catch (RuntimeException re) {
  165. System.err.println("***** Error weaving " + name + ". " + re.getMessage());
  166. re.printStackTrace();
  167. err = 1;
  168. } catch (IOException ioe) {
  169. err = 1;
  170. System.err.println("***** Unable to find/process '" + name + "'\n" + ioe.getMessage());
  171. }
  172. }
  173. static void writeClasses(ClassWeaver cw) throws IOException {
  174. List<ClassInfo> cis = cw.getClassInfos();
  175. if (cis.size() > 0) {
  176. for (ClassInfo ci : cis) {
  177. writeClass(ci);
  178. }
  179. }
  180. }
  181. public static void writeClass(ClassInfo ci) throws IOException {
  182. String className = ci.className.replace('.', File.separatorChar);
  183. String dir = outputDir + File.separatorChar + getDirName(className);
  184. mkdir(dir);
  185. // Convert name to fully qualified file name
  186. className = outputDir + File.separatorChar + className + ".class";
  187. if (ci.className.startsWith("kilim.S_")) {
  188. // Check if we already have that file
  189. if (new File(className).exists())
  190. return;
  191. }
  192. FileOutputStream fos = new FileOutputStream(className);
  193. fos.write(ci.bytes);
  194. fos.close();
  195. if (verbose) {
  196. System.out.println("Wrote: " + className);
  197. }
  198. }
  199. static void mkdir(String dir) throws IOException {
  200. File f = new File(dir);
  201. if (!f.exists()) {
  202. if (!f.mkdirs()) {
  203. throw new IOException("Unable to create directory: " + dir);
  204. }
  205. }
  206. }
  207. static String getDirName(String className) {
  208. int end = className.lastIndexOf(File.separatorChar);
  209. return (end == -1) ? "" : className.substring(0, end);
  210. }
  211. static void help() {
  212. System.err.println("java kilim.tools.Weaver opts -d <outputDir> (class/directory/jar)+");
  213. System.err.println(" where opts are -q : quiet");
  214. System.err.println(" -x <regex> : exclude all classes matching regex");
  215. System.err.println(" -f : weave even if up to date");
  216. System.err.println(" -c : don't add targets to classpath");
  217. System.exit(1);
  218. }
  219. public static ArrayList<String> parseArgs(String[] args) throws IOException {
  220. if (args.length == 0)
  221. help();
  222. ArrayList<String> ret = new ArrayList<String>(args.length);
  223. String regex = null;
  224. for (int i = 0; i < args.length; i++) {
  225. String arg = args[i];
  226. if (arg.equals("-d")) {
  227. outputDir = args[++i];
  228. } else if (arg.equals("-q")) {
  229. verbose = false;
  230. } else if (arg.equals("-f")) {
  231. force = true;
  232. } else if (arg.equals("-c")) {
  233. proxy = false;
  234. } else if (arg.equals("-h")) {
  235. help();
  236. } else if (arg.equals("-x")) {
  237. regex = args[++i];
  238. excludePattern = Pattern.compile(regex);
  239. } else {
  240. ret.add(arg);
  241. }
  242. }
  243. if (outputDir == null) {
  244. System.err.println("Specify output directory with -d option");
  245. System.exit(1);
  246. }
  247. return ret;
  248. }
  249. /**
  250. * Analyzes the list of supplied classes and inserts Kilim-related bytecode if necessary. If a
  251. * supplied class is dependent upon another class X, it is the caller's responsibility to ensure
  252. * that X is either in the classpath, or loaded by the context classloader, or has been seen in
  253. * an earlier invocation of weave().
  254. *
  255. * Since weave() remembers method signatures from earlier invocations, the woven classes do not
  256. * have to be classloaded to help future invocations of weave.
  257. *
  258. * If two classes A and B are not in the classpath, and are mutually recursive, they can be woven
  259. * only if supplied in the same input list.
  260. *
  261. * This method is thread safe.
  262. *
  263. * @param classes A list of (className, byte[]) pairs. The first part is a fully qualified class
  264. * name, and the second part is the bytecode for the class.
  265. *
  266. * @return A list of (className, byte[]) pairs. Some of the classes may or may not have been
  267. * modified, and new ones may be added.
  268. *
  269. * @throws KilimException
  270. */
  271. public List<ClassInfo> weave(List<ClassInfo> classes) throws KilimException, IOException {
  272. // save the detector attached to this thread, if any. It will be restored
  273. // later.
  274. ArrayList<ClassInfo> ret = new ArrayList<ClassInfo>(classes.size());
  275. try {
  276. // First cache all the method signatures from the supplied classes to allow
  277. // the weaver to lookup method signatures from mutually recursive classes.
  278. for (ClassInfo cl : classes) {
  279. context.detector.mirrors.mirror(cl.bytes);
  280. }
  281. // Now weave them individually
  282. for (ClassInfo cl : classes) {
  283. InputStream is = new ByteArrayInputStream(cl.bytes);
  284. ClassWeaver cw = new ClassWeaver(context,is);
  285. cw.weave();
  286. ret.addAll(cw.getClassInfos()); // one class file can result in multiple classes
  287. }
  288. return ret;
  289. } finally {
  290. }
  291. }
  292. }