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