package org.aspectj.weaver.tools; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; import java.util.zip.ZipEntry; import org.aspectj.bridge.AbortException; import org.aspectj.bridge.IMessage; import org.aspectj.bridge.IMessageHandler; import org.aspectj.bridge.Message; import org.aspectj.bridge.MessageWriter; import org.aspectj.bridge.SourceLocation; import org.aspectj.bridge.IMessage.Kind; import org.aspectj.util.FileUtil; import org.aspectj.weaver.IClassFileProvider; import org.aspectj.weaver.IWeaveRequestor; import org.aspectj.weaver.bcel.BcelWeaver; import org.aspectj.weaver.bcel.BcelWorld; import org.aspectj.weaver.bcel.UnwovenClassFile; /** * Simple command-line weaver that depends only on aspectjweaver.jar, not any part of the compiler. * Supports all reasonable options */ public class CmdLineWeaver { protected JarOutputStream zos; protected List/*String*/ resources = new ArrayList(); protected static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; protected BcelWorld world; protected BcelWeaver weaver; protected List unwovenClasses = new ArrayList(); protected IClassFileProvider classFileProvider = new WeavingClassFileProvider(); protected IMessageHandler messageHandler = new JarWeaverMessageHandler(new PrintWriter(System.err)); public static String CONFIG_MSG = "weave config error: "; protected class State { public List inpath = new ArrayList(); public List aspectpath = new ArrayList(); //TODO: test pulling in bootstrap classes, not conflicting system classpath classes when specifying -classpath public String classpathStr = null; public String bootClasspathStr = null; public String extdirs = null; public File outjar; public boolean verbose = false; public boolean showWeaveInfo = false; public File xlintFile = null; public boolean xLazyTjp = true; public boolean reweavable = true; public boolean xNoInline = false; public boolean hasErrors = false; private boolean nowarn; public File outputDir = null; public File baseDir = new File(System.getProperty("user.dir")); //$NON-NLS-1$ public void addError(String message) { System.err.println(CONFIG_MSG + message); hasErrors = true; } public boolean hasErrors() { return hasErrors; } /** * * @param args * @return true if no errors */ public boolean parse(String args[]) { boolean showUsage = false; // currentDir = new File("."); //TODO make fully portable for (int i=0; i ++i) { classpathStr = args[i]; } else { addError("missing classpath argument"); } } else if (arg.equals("-bootclasspath")) { if (args.length > ++i) { bootClasspathStr = args[i]; } else { addError("missing bootclasspath argument"); } } else if (arg.equals("-extdirs")) { if (args.length > ++i) { extdirs = args[i]; } else { addError("missing extdirs argument"); } } else if (arg.equals("-aspectpath")) {; aspectpath = new ArrayList(); parsePath(aspectpath, args, ++i, "aspect"); } else if (arg.equals("-outjar")) { ++i; if (args.length > i) { outjar = makeFile(args[i], baseDir); if (FileUtil.hasZipSuffix(outjar)) { try { if (!outjar.exists()) { outjar.createNewFile(); } } catch (IOException ioe) { addError("unable to create outjar file: " + outjar); } } else { addError("invalid -outjar file: " + outjar); } } else { addError("-outjar requires jar path argument"); } } else if (arg.equals("-verbose")) { verbose = true; } else if (arg.equals("-showWeaveInfo")) { showWeaveInfo = true; } else if (arg.equals("-Xlintfile")) { if (args.length > ++i) { xlintFile = makeFile(args[i], baseDir); if (!xlintFile.canRead()) { addError("can't read Xlintfile "+args[i]); } if (!xlintFile.getName().endsWith(".properties")) { addError("-Xlintfile requires .properties file argument"); } } else { addError("-Xlintfile requires file argument"); } } else if (arg.equals("-XlazyTjp")) { showWarning("-XlazyTjp is true by default in CmdLineWeaver"); xLazyTjp = true; } else if (arg.equals("-XnotLazyTjp")) { xLazyTjp = false; } else if (arg.startsWith("-Xreweavable")) { showWarning("-Xreweavable is on by default"); if (arg.endsWith(":compress")) { showWarning("-Xreweavable:compress is no longer available - reweavable is now default"); } } else if (arg.startsWith("-XnotReweavable")) { reweavable = false; } else if (arg.equals("-XnoInline")) { xNoInline = true; } else if (arg.equals("-nowarn")) { nowarn = true; } else if (arg.equals("-help")) { showUsage = true; } else if (arg.equals("-d")) { if (args.length > ++i) { outputDir = new File(args[i]); } else { addError("-d requires directory argument"); } } else { addError("unknown argument: "+arg); showUsage = true; } } if (outjar == null) { if (outputDir == null) { outputDir = baseDir; } if (outputDir.exists()) { if (!outputDir.isDirectory()) { addError("Output directory isn't a directory: "+outputDir.getPath()); } } else { if (!outputDir.mkdirs()) { addError("Can't create output directory: "+outputDir.getPath()); } } } if (showUsage) { usage(); } return !hasErrors || showUsage; } private void parsePath(List path, String args[], int pos, String pathType) { if (args.length > pos) { String pathStr = args[pos]; StringTokenizer st = new StringTokenizer(pathStr, File.pathSeparator); while (st.hasMoreTokens()) { String filename = st.nextToken(); File jarFile = makeFile(filename, baseDir); if (jarFile.exists() && (FileUtil.hasZipSuffix(filename) || jarFile.isDirectory())) { path.add(jarFile); } else if (jarFile.isDirectory()) { path.add(jarFile); } else { addError("bad "+pathType+"path entry: " + filename); } } } else { addError("missing "+pathType+"path argument"); } } public List getClasspath() { List ret = new ArrayList(); if (extdirs == null) { extdirs = System.getProperty("java.ext.dirs", ""); } addExtDirs(extdirs, ret); if (classpathStr == null) { addClasspath(System.getProperty("java.class.path", ""), ret); List fixedList = new ArrayList(); for (Iterator it = ret.iterator(); it.hasNext(); ) { String entry = (String)it.next(); if (!entry.endsWith("aspectjweaver.jar")) { fixedList.add(entry); } } ret = fixedList; } else { addClasspath(classpathStr, ret); } //??? eclipse seems to put outdir on the classpath //??? we're brave and believe we don't need it return ret; } private void addExtDirs(String extdirs, List classpathCollector) { StringTokenizer tokenizer = new StringTokenizer(extdirs, File.pathSeparator); while (tokenizer.hasMoreTokens()) { // classpathCollector.add(tokenizer.nextToken()); File dirFile = new File((String)tokenizer.nextToken()); if (dirFile.canRead() && dirFile.isDirectory()) { File[] files = dirFile.listFiles(FileUtil.ZIP_FILTER); for (int i = 0; i < files.length; i++) { classpathCollector.add(files[i].getAbsolutePath()); } } else { showWarning("invalid -extdirs entry: "+dirFile.getPath()); } } } private void addClasspath(String classpath, List classpathCollector) { StringTokenizer tokenizer = new StringTokenizer(classpath, File.pathSeparator); while (tokenizer.hasMoreTokens()) { classpathCollector.add(tokenizer.nextToken()); } } public List getBootclasspath() { List ret = new ArrayList(); if (bootClasspathStr == null) { addClasspath(System.getProperty("sun.boot.class.path", ""), ret); } else { addClasspath(bootClasspathStr, ret); } return ret; } }; protected State state = new State(); // File currentDir; public static void main(String args[]) throws IOException { CmdLineWeaver jarWeaver = new CmdLineWeaver(); jarWeaver.run(args); } public void showWarning(String string) { messageHandler.handleMessage(new Message(string, null, false, null)); } public void usage() { System.err.println("jarWeaver [-inpath path] -outjar jar [-classpath|-cp path] [-verbose] [-showWeaveInfo] [-Xlintfile file] "+ "[-XnotLazyTjp to change default] [-XnotReweavable] [-XnoInline] [-extdirs] [-noWarn] [-d directory]"); } public void run(String args[]) throws IOException { if (state.parse(args)) { try { prepare(); Collection result = weaver.weave(classFileProvider); if (state.verbose) { System.out.println(); } world.showMessage(IMessage.INFO, "Processed: "+result.size()+" entries ", null, null); } finally { if (zos != null) { zos.close(); } } } } /** method to support testing: could be exposed through a command line arg */ public void setBaseDir(File baseDir) { state.baseDir = baseDir; } /** method to support testing */ public void setMessageHander(IMessageHandler handler) { this.messageHandler = handler; } protected void prepare() throws IOException { if (state.verbose) { messageHandler.dontIgnore(IMessage.INFO); } if (state.showWeaveInfo) { messageHandler.dontIgnore(IMessage.WEAVEINFO); } if (state.nowarn) { //XXX ignore should be added to the MessageHandler interface too, see also Alex's use of dontIgnore // to convey "ignore" for this case. Blech! ((JarWeaverMessageHandler)messageHandler).ignore(IMessage.WARNING); } List classpath = state.getBootclasspath(); classpath.addAll(state.getClasspath()); world = new BcelWorld(classpath,messageHandler,null); world.setXnoInline(state.xNoInline); world.setXlazyTjp(state.xLazyTjp); if (state.xlintFile != null) { world.getLint().setFromProperties(state.xlintFile); } else { world.getLint().loadDefaultProperties(); } weaver = new BcelWeaver(world); weaver.setReweavableMode(!state.reweavable); for (Iterator it=state.aspectpath.iterator(); it.hasNext();) { File aspectPathEntry = (File)(it.next()); weaver.addLibraryJarFile(aspectPathEntry); } //Map binarySourceFiles = new HashMap(); for (Iterator it=state.inpath.iterator(); it.hasNext();) { File inPathEntry = (File)(it.next()); if (inPathEntry.isDirectory()) { unwovenClasses.addAll(weaver.addDirectoryContents(inPathEntry, state.outputDir)); } else { unwovenClasses.addAll(weaver.addJarFile(inPathEntry, state.outputDir, true)); } } if (state.outjar != null) { OutputStream os = FileUtil.makeOutputStream(state.outjar); zos = new JarOutputStream(os,weaver.getManifest(true)); } for (Iterator it=state.inpath.iterator(); it.hasNext();) { File inPathEntry = (File)(it.next()); if (!inPathEntry.isDirectory()) { copyResourcesFromJarFile(inPathEntry); } } weaver.prepareForWeave(); } private class WeavingClassFileProvider implements IClassFileProvider { public Iterator getClassFileIterator() { return unwovenClasses.iterator(); } public IWeaveRequestor getRequestor() { return new IWeaveRequestor() { public void acceptResult(UnwovenClassFile result) { try { String filename = result.getClassName().replace('.', '/') + ".class"; if (zos != null) { writeZipEntry(filename, result.getBytes()); } else { writeDirectoryEntry(filename, result.getBytes()); } } catch (IOException e) { throw new RuntimeException("losing! ", e); } } public void processingReweavableState() { } public void addingTypeMungers() {} public void weavingAspects() {} public void weavingClasses() {} public void weaveCompleted() {} }; } } private void writeDirectoryEntry(String filename, byte[] bytes) throws IOException { File outFile = new File(state.outputDir, filename); BufferedOutputStream os = FileUtil.makeOutputStream(outFile); try { os.write(bytes); } finally { os.close(); } } private void writeZipEntry(String filename, byte[] bytes) throws IOException { ZipEntry newEntry = new ZipEntry(filename); //??? get compression scheme right zos.putNextEntry(newEntry); zos.write(bytes); zos.closeEntry(); } // should be refactored, not copied into here private void copyResourcesFromJarFile(File jarFile) throws IOException { JarInputStream inStream = null; try { inStream = new JarInputStream(new FileInputStream(jarFile)); while (true) { ZipEntry entry = inStream.getNextEntry(); if (entry == null) break; String filename = entry.getName(); // System.out.println("? copyResourcesFromJarFile() filename='" + filename +"'"); if (!entry.isDirectory() && acceptResource(filename)) { byte[] bytes = FileUtil.readAsByteArray(inStream); writeResource(filename,bytes,jarFile); } inStream.closeEntry(); } } finally { if (inStream != null) inStream.close(); } } private void writeResource(String filename, byte[] content, File srcLocation) throws IOException { if (resources.contains(filename)) { IMessage msg = new Message("duplicate resource: '" + filename + "'", IMessage.WARNING, null, new SourceLocation(srcLocation,0)); messageHandler.handleMessage(msg); return; } if (zos != null) { // XXX test this - add a property to the test jar ZipEntry newEntry = new ZipEntry(filename); //??? get compression scheme right zos.putNextEntry(newEntry); zos.write(content); zos.closeEntry(); } resources.add(filename); } private boolean acceptResource(String resourceName) { if ( (resourceName.startsWith("CVS/")) || (resourceName.indexOf("/CVS/") != -1) || (resourceName.endsWith("/CVS")) || (resourceName.endsWith(".class")) || (resourceName.toUpperCase().equals(MANIFEST_NAME)) ) { return false; } else { return true; } } protected static File makeFile(String name, File dir) { name = name.replace('/', File.separatorChar); File ret = new File(name); if (dir != null && !ret.isAbsolute()) { ret = new File(dir, name); } try { ret = ret.getCanonicalFile(); } catch (IOException ioEx) { // proceed without canonicalization // so nothing to do here } return ret; } // copy/paste from BcelWorld private protected static List getPathEntries(String s) { List ret = new ArrayList(); StringTokenizer tok = new StringTokenizer(s, File.pathSeparator); while(tok.hasMoreTokens()) ret.add(tok.nextToken()); return ret; } protected class JarWeaverMessageHandler extends MessageWriter { private Set ignoring = new HashSet(); private IMessage.Kind failKind; public JarWeaverMessageHandler(PrintWriter writer) { super(writer,true); ignore(IMessage.WEAVEINFO); ignore(IMessage.INFO); this.failKind = IMessage.ERROR; } public boolean handleMessage(IMessage message) throws AbortException { boolean result = super.handleMessage(message); if (0 <= message.getKind().compareTo(failKind)) { throw new AbortException(message); } return true; } public boolean isIgnoring (Kind kind) { return ((null != kind) && (ignoring.contains(kind))); } /** * Set a message kind to be ignored from now on */ public void ignore (IMessage.Kind kind) { if ((null != kind) && (!ignoring.contains(kind))) { ignoring.add(kind); } } /** * Remove a message kind from the list of those ignored from now on. */ public void dontIgnore (IMessage.Kind kind) { if (null != kind) { ignoring.remove(kind); } } } }