/* Copyright (c) 2006, Sriram Srinivasan * * You may distribute this software under the terms of the license * specified in the file "License" */ package kilim.tools; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import kilim.KilimException; import kilim.analysis.ClassInfo; import kilim.analysis.ClassWeaver; import kilim.analysis.FileLister; import kilim.mirrors.CachedClassMirrors; import kilim.mirrors.Detector; /** * This class supports both command-line and run time weaving of Kilim bytecode. */ public class Weaver { public static String outputDir = null; public static boolean verbose = true; public static Pattern excludePattern = null; static int err = 0; /** * <pre> * Usage: java kilim.tools.Weaver -d <output directory> {source classe, jar, directory ...} * </pre> * * If directory names or jar files are given, all classes in that container are processed. It is * perfectly fine to specify the same directory for source and output like this: * <pre> * java kilim.tools.Weaver -d ./classes ./classes * </pre> * Ensure that all classes to be woven are in the classpath. The output directory does not have to be * in the classpath during weaving. * * @see #weave(List) for run-time weaving. */ public static void main(String[] args) throws IOException { // System.out.println(System.getProperty("java.class.path")); Detector detector = Detector.DEFAULT; String currentName = null; for (String name : parseArgs(args)) { try { if (name.endsWith(".class")) { if (exclude(name)) continue; currentName = name; weaveFile(name, new BufferedInputStream(new FileInputStream(name)), detector); } else if (name.endsWith(".jar")) { for (FileLister.Entry fe : new FileLister(name)) { currentName = fe.getFileName(); if (currentName.endsWith(".class")) { currentName = currentName.substring(0, currentName.length() - 6) .replace('/', '.'); if (exclude(currentName)) continue; weaveFile(currentName, fe.getInputStream(), detector); } } } else if (new File(name).isDirectory()) { for (FileLister.Entry fe : new FileLister(name)) { currentName = fe.getFileName(); if (currentName.endsWith(".class")) { if (exclude(currentName)) continue; weaveFile(currentName, fe.getInputStream(), detector); } } } else { weaveClass(name, detector); } } catch (KilimException ke) { System.err.println("Error weaving " + currentName + ". " + ke.getMessage()); // ke.printStackTrace(); System.exit(1); } catch (IOException ioe) { System.err.println("Unable to find/process '" + currentName + "'"); System.exit(1); } catch (Throwable t) { System.err.println("Error weaving " + currentName); t.printStackTrace(); System.exit(1); } } System.exit(err); } static boolean exclude(String name) { return excludePattern == null ? false : excludePattern.matcher(name).find(); } static void weaveFile(String name, InputStream is, Detector detector) throws IOException { try { ClassWeaver cw = new ClassWeaver(is, detector); cw.weave(); writeClasses(cw); } catch (KilimException ke) { System.err.println("***** Error weaving " + name + ". " + ke.getMessage()); // ke.printStackTrace(); err = 1; } catch (RuntimeException re) { System.err.println("***** Error weaving " + name + ". " + re.getMessage()); re.printStackTrace(); err = 1; } catch (IOException ioe) { err = 1; System.err.println("***** Unable to find/process '" + name + "'\n" + ioe.getMessage()); } } static void weaveClass(String name, Detector detector) { try { ClassWeaver cw = new ClassWeaver(name, detector); writeClasses(cw); } catch (KilimException ke) { err = 1; System.err.println("***** Error weaving " + name + ". " + ke.getMessage()); // ke.printStackTrace(); } catch (IOException ioe) { err = 1; System.err.println("***** Unable to find/process '" + name + "'\n" + ioe.getMessage()); } } /** public only for testing purposes */ public static void weaveClass2(String name, Detector detector) throws IOException { try { ClassWeaver cw = new ClassWeaver(name, detector); cw.weave(); writeClasses(cw); } catch (KilimException ke) { err = 1; System.err.println("***** Error weaving " + name + ". " + ke.getMessage()); // ke.printStackTrace(); throw ke; } catch (IOException ioe) { err = 1; System.err.println("***** Unable to find/process '" + name + "'\n" + ioe.getMessage()); throw ioe; } } static void writeClasses(ClassWeaver cw) throws IOException { List<ClassInfo> cis = cw.getClassInfos(); if (cis.size() > 0) { for (ClassInfo ci : cis) { writeClass(ci); } } } static void writeClass(ClassInfo ci) throws IOException { String className = ci.className.replace('.', File.separatorChar); String dir = outputDir + File.separatorChar + getDirName(className); mkdir(dir); // Convert name to fully qualified file name className = outputDir + File.separatorChar + className + ".class"; if (ci.className.startsWith("kilim.S_")) { // Check if we already have that file if (new File(className).exists()) return; } FileOutputStream fos = new FileOutputStream(className); fos.write(ci.bytes); fos.close(); if (verbose) { System.out.println("Wrote: " + className); } } static void mkdir(String dir) throws IOException { File f = new File(dir); if (!f.exists()) { if (!f.mkdirs()) { throw new IOException("Unable to create directory: " + dir); } } } static String getDirName(String className) { int end = className.lastIndexOf(File.separatorChar); return (end == -1) ? "" : className.substring(0, end); } static void help() { System.err.println("java kilim.tools.Weaver opts -d <outputDir> (class/directory/jar)+"); System.err.println(" where opts are -q : quiet"); System.err.println(" -x <regex> : exclude all classes matching regex"); System.exit(1); } static ArrayList<String> parseArgs(String[] args) throws IOException { if (args.length == 0) help(); ArrayList<String> ret = new ArrayList<String>(args.length); String regex = null; for (int i = 0; i < args.length; i++) { String arg = args[i]; if (arg.equals("-d")) { outputDir = args[++i]; } else if (arg.equals("-q")) { verbose = false; } else if (arg.equals("-h")) { help(); } else if (arg.equals("-x")) { regex = args[++i]; excludePattern = Pattern.compile(regex); } else { ret.add(arg); } } if (outputDir == null) { System.err.println("Specify output directory with -d option"); System.exit(1); } mkdir(outputDir); return ret; } private Detector detector; private CachedClassMirrors mirrors; public Weaver() { this(Thread.currentThread().getContextClassLoader()); } public Weaver(ClassLoader cl) { mirrors = new CachedClassMirrors(cl); detector = new Detector(mirrors); } /** * See #weave(List<ClassInfo>) */ public List<ClassInfo> weave(ClassInfo cl) throws KilimException { List<ClassInfo> ret = new ArrayList<ClassInfo>(1); ret.add(cl); ret = weave(ret); return ret; } /** * Analyzes the list of supplied classes and inserts Kilim-related bytecode if necessary. If a * supplied class is dependent upon another class X, it is the caller's responsibility to ensure * that X is either in the classpath, or loaded by the context classloader, or has been seen in * an earlier invocation of weave(). * * Since weave() remembers method signatures from earlier invocations, the woven classes do not * have to be classloaded to help future invocations of weave. * * If two classes A and B are not in the classpath, and are mutually recursive, they can be woven * only if supplied in the same input list. * * This method is thread safe. * * @param classes A list of (className, byte[]) pairs. The first part is a fully qualified class * name, and the second part is the bytecode for the class. * * @return A list of (className, byte[]) pairs. Some of the classes may or may not have been * modified, and new ones may be added. * * @throws KilimException */ public List<ClassInfo> weave(List<ClassInfo> classes) throws KilimException { // save the detector attached to this thread, if any. It will be restored // later. ArrayList<ClassInfo> ret = new ArrayList<ClassInfo>(classes.size()); Detector origDetector = Detector.getDetector(); Detector.setDetector(detector); // / set thread local detector. try { // First cache all the method signatures from the supplied classes to allow // the weaver to lookup method signatures from mutually recursive classes. for (ClassInfo cl : classes) { detector.mirrors.mirror(cl.className, cl.bytes); } // Now weave them individually for (ClassInfo cl : classes) { ClassWeaver cw = new ClassWeaver(cl.bytes, detector); cw.weave(); ret.addAll(cw.getClassInfos()); // one class file can result in multiple classes } return ret; } finally { Detector.setDetector(origDetector); } } }