/* * Janino - An embedded Java[TM] compiler * * Copyright (c) 2001-2010, Arno Unkrig * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the * following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.codehaus.janino.tools; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.codehaus.commons.compiler.CompileException; import org.codehaus.commons.compiler.CompilerFactoryFactory; import org.codehaus.commons.compiler.ICompilerFactory; import org.codehaus.commons.compiler.IExpressionEvaluator; import org.codehaus.commons.compiler.UncheckedCompileException; import org.codehaus.janino.Descriptor; import org.codehaus.janino.ExpressionEvaluator; import org.codehaus.janino.IClass; import org.codehaus.janino.IClassLoader; import org.codehaus.janino.Java; import org.codehaus.janino.Java.CompilationUnit; import org.codehaus.janino.Parser; import org.codehaus.janino.Scanner; import org.codehaus.janino.UnitCompiler; import org.codehaus.janino.util.Benchmark; import org.codehaus.janino.util.ClassFile; import org.codehaus.janino.util.StringPattern; import org.codehaus.janino.util.Traverser; import org.codehaus.janino.util.enumerator.Enumerator; import org.codehaus.janino.util.iterator.DirectoryIterator; import org.codehaus.janino.util.resource.PathResourceFinder; /** * Reads a set of compilation units from the file system and searches it for specific * Java™ constructs, e.g. invocations of a particular method. * * Usage: *
 * java org.codehaus.janino.JGrep \
 *           [ -dirs directory-name-patterns ] \
 *           [ -files file-name-patterns ] \
 *           { directory-path } \
 *           -method-invocation class.method(arg-types)
 * java org.codehaus.janino.JGrep -help
 * 
* * If "-dirs" is not given, then all directory-pathes are scanned for files. * The directory-name-patterns work as described in * {@link org.codehaus.janino.util.StringPattern#parseCombinedPattern(String)}. *

* If "-files" is not given, then all files ending in ".java" are read. The * file-name-patterns work as described in * {@link org.codehaus.janino.util.StringPattern#parseCombinedPattern(String)}. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public // SUPPRESS CHECKSTYLE HideUtilityClassConstructor class JGrep { private static final boolean DEBUG = false; private final List parsedCompilationUnits = new ArrayList(); /** Command line interface. */ public static void main(String[] args) { int idx = 0; StringPattern[] directoryNamePatterns = StringPattern.PATTERNS_ALL; StringPattern[] fileNamePatterns = new StringPattern[] { new StringPattern("*.java") }; File[] classPath = new File[] { new File(".") }; File[] optionalExtDirs = null; File[] optionalBootClassPath = null; String optionalCharacterEncoding = null; boolean verbose = false; for (; idx < args.length; ++idx) { String arg = args[idx]; if (arg.charAt(0) != '-') break; if ("-dirs".equals(arg)) { directoryNamePatterns = StringPattern.parseCombinedPattern(args[++idx]); } else if ("-files".equals(arg)) { fileNamePatterns = StringPattern.parseCombinedPattern(args[++idx]); } else if ("-classpath".equals(arg)) { classPath = PathResourceFinder.parsePath(args[++idx]); } else if ("-extdirs".equals(arg)) { optionalExtDirs = PathResourceFinder.parsePath(args[++idx]); } else if ("-bootclasspath".equals(arg)) { optionalBootClassPath = PathResourceFinder.parsePath(args[++idx]); } else if ("-encoding".equals(arg)) { optionalCharacterEncoding = args[++idx]; } else if ("-verbose".equals(arg)) { verbose = true; } else if ("-help".equals(arg)) { for (String s : JGrep.USAGE) System.out.println(s); System.exit(1); } else { System.err.println("Unexpected command-line argument \"" + arg + "\", try \"-help\"."); System.exit(1); return; /* NEVER REACHED */ } } // { directory-path } File[] rootDirectories; { int first = idx; for (; idx < args.length && args[idx].charAt(0) != '-'; ++idx); if (idx == first) { System.err.println("No es given, try \"-help\"."); System.exit(1); return; /* NEVER REACHED */ } rootDirectories = new File[idx - first]; for (int i = first; i < idx; ++i) rootDirectories[i - first] = new File(args[i]); } // Create the JGrep object. final JGrep jGrep = new JGrep( classPath, optionalExtDirs, optionalBootClassPath, optionalCharacterEncoding, verbose ); List mits = new ArrayList(); for (; idx < args.length; ++idx) { String arg = args[idx]; if ("-method-invocation".equals(arg)) { MethodInvocationTarget mit; try { mit = JGrep.parseMethodInvocationPattern(args[++idx]); } catch (Exception ex) { System.err.println("Parsing method invocation pattern \"" + args[idx] + "\": " + ex.getMessage()); System.exit(1); return; /* NEVER REACHED */ } while (idx < args.length - 1) { arg = args[idx + 1]; if (arg.startsWith("predicate:")) { String predicateExpression = arg.substring(10); try { IExpressionEvaluator ee = new ExpressionEvaluator(); ee.setClassName(JGrep.class.getName() + "PE"); mit.predicates.add((MethodInvocationPredicate) ee.createFastEvaluator( predicateExpression, MethodInvocationPredicate.class, new String[] { "uc", "invocation", "method" } )); } catch (Exception ex) { System.err.println( "Compiling predicate expression \"" + predicateExpression + "\": " + ex.getMessage() ); System.exit(1); return; /* NEVER REACHED */ } } else if (arg.startsWith("action:")) { String action = arg.substring(7); try { mit.actions.add(Action.getMethodInvocationAction(action)); } catch (Exception ex) { System.err.println( "Compiling method invocation action \"" + action + "\": " + ex.getMessage() ); System.exit(1); return; /* NEVER REACHED */ } } else { break; } ++idx; } mits.add(mit); } else { System.err.println("Unexpected command-line argument \"" + arg + "\", try \"-help\"."); System.exit(1); return; /* NEVER REACHED */ } } // JGrep the root directories. try { jGrep.jGrep( rootDirectories, directoryNamePatterns, fileNamePatterns, mits // methodInvocationTargets ); } catch (Exception e) { System.err.println(e.toString()); System.exit(1); } } private static final class Action extends Enumerator { private Action(String name) { super(name); } static MethodInvocationAction getMethodInvocationAction(String action) throws CompileException { if ("print-location-and-match".equals(action)) { return new MethodInvocationAction() { @Override public void execute(UnitCompiler uc, Java.Invocation invocation, IClass.IMethod method) { System.out.println(invocation.getLocation() + ": " + method); } }; } else if ("print-location".equals(action)) { return new MethodInvocationAction() { @Override public void execute(UnitCompiler uc, Java.Invocation invocation, IClass.IMethod method) { System.out.println(invocation.getLocation()); } }; } else { ICompilerFactory cf; try { cf = CompilerFactoryFactory.getDefaultCompilerFactory(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); // SUPPRESS CHECKSTYLE AvoidHidingCause } return (MethodInvocationAction) cf.newScriptEvaluator().createFastEvaluator( action, // script MethodInvocationAction.class, // interfaceToImplement new String[] { "uc", "invocation", "method" } // parameterNames ); } } } private static MethodInvocationTarget parseMethodInvocationPattern(String mip) throws CompileException, IOException { MethodInvocationTarget mit = new MethodInvocationTarget(); Scanner scanner = new Scanner(null, new StringReader(mip)); Parser parser = new Parser(scanner); for (;;) { String s = JGrep.readIdentifierPattern(parser); if (parser.peekRead("(")) { mit.methodNamePattern = s; List l = new ArrayList(); if (!parser.peekRead(")")) { for (;;) { l.add(JGrep.readIdentifierPattern(parser)); if (parser.peek(")")) break; parser.read(","); } } mit.optionalArgumentTypeNamePatterns = (String[]) l.toArray(new String[l.size()]); return mit; } else if (parser.peekRead(".")) { if (mit.optionalClassNamePattern == null) { mit.optionalClassNamePattern = s; } else { mit.optionalClassNamePattern += '.' + s; } } else if (parser.peekEof()) { mit.methodNamePattern = s; return mit; } } } private static String readIdentifierPattern(Parser p) throws CompileException, IOException { StringBuilder sb = new StringBuilder(); if (p.peekRead("*")) { sb.append('*'); } else { sb.append(p.readIdentifier()); } for (;;) { if (p.peekRead("*")) { sb.append('*'); } else if (p.peekIdentifier() != null) { sb.append(p.readIdentifier()); } else { return sb.toString(); } } } private static class MethodInvocationTarget { String optionalClassNamePattern; String methodNamePattern; String[] optionalArgumentTypeNamePatterns; List predicates = new ArrayList(); List actions = new ArrayList(); void apply(UnitCompiler uc, Java.Invocation invocation, IClass.IMethod method) throws CompileException { // Verify that the class declaring the invoked method matches. if (this.optionalClassNamePattern != null) { if (!JGrep.typeMatches( this.optionalClassNamePattern, Descriptor.toClassName(method.getDeclaringIClass().getDescriptor()) )) return; } // Verify that the name of the invoked method matches. if (!new StringPattern(this.methodNamePattern).matches(method.getName())) return; // Verify that the parameter count and types of the invoked method match. IClass[] fpts = method.getParameterTypes(); if (this.optionalArgumentTypeNamePatterns != null) { String[] atnps = this.optionalArgumentTypeNamePatterns; if (atnps.length != fpts.length) return; for (int i = 0; i < atnps.length; ++i) { if (!new StringPattern(atnps[i]).matches(Descriptor.toClassName(fpts[i].getDescriptor()))) return; } } // Verify that all predicates (JANINO expressions) return TRUE. for (MethodInvocationPredicate mip : this.predicates) { try { if (!mip.evaluate(uc, invocation, method)) return; } catch (Exception ex) { return; // Treat exception as a "false" predicate. } } // Now that all checks were successful, execute all method invocation actions. for (MethodInvocationAction mia : this.actions) { try { mia.execute(uc, invocation, method); } catch (Exception ex) { ; // Ignore action throwing an exception. } } } } /** A predicate that examines a method invocation. */ interface MethodInvocationPredicate { /** @return Whether the method incovation met some criterion */ boolean evaluate(UnitCompiler uc, Java.Invocation invocation, IClass.IMethod method) throws Exception; } /** An entity that does something with a method invocation, e.g. report where it occurred. */ interface MethodInvocationAction { /** Executes some action for a method invocation. */ void execute(UnitCompiler uc, Java.Invocation invocation, IClass.IMethod method) throws Exception; } /** * @return Whether the fully qualified {@code typeName} matches the {@code pattern}, or, iff the pattern does not * contain a period, the simple type name of {@code typeName} matches the {@code pattern} */ static boolean typeMatches(String pattern, String typeName) { return new StringPattern(pattern).matches( pattern.indexOf('.') == -1 ? typeName.substring(typeName.lastIndexOf('.') + 1) : typeName ); } private static final String[] USAGE = { "Usage:", "", " java org.codehaus.janino.tools.JGrep [

* The inputStream is closed before the method returns. * @return the parsed compilation unit */ private Java.CompilationUnit parseCompilationUnit(File sourceFile, String optionalCharacterEncoding) throws CompileException, IOException { InputStream is = new BufferedInputStream(new FileInputStream(sourceFile)); try { Parser parser = new Parser(new Scanner(sourceFile.getPath(), is, optionalCharacterEncoding)); this.benchmark.beginReporting("Parsing \"" + sourceFile + "\""); try { return parser.parseCompilationUnit(); } finally { this.benchmark.endReporting(); } } finally { try { is.close(); } catch (IOException ex) {} } } /** * Construct the name of a file that could store the byte code of the class with the given * name. *

* If optionalDestinationDirectory is non-null, the returned path is the * optionalDestinationDirectory plus the package of the class (with dots replaced * with file separators) plus the class name plus ".class". Example: * "destdir/pkg1/pkg2/Outer$Inner.class" *

* If optionalDestinationDirectory is null, the returned path is the * directory of the sourceFile plus the class name plus ".class". Example: * "srcdir/Outer$Inner.class" * @param className E.g. "pkg1.pkg2.Outer$Inner" * @param sourceFile E.g. "srcdir/Outer.java" * @param optionalDestinationDirectory E.g. "destdir" */ public static File getClassFile(String className, File sourceFile, File optionalDestinationDirectory) { if (optionalDestinationDirectory != null) { return new File(optionalDestinationDirectory, ClassFile.getClassFileResourceName(className)); } else { int idx = className.lastIndexOf('.'); return new File( sourceFile.getParentFile(), ClassFile.getClassFileResourceName(className.substring(idx + 1)) ); } } /** * A specialized {@link IClassLoader} that loads {@link IClass}es from the following * sources: *

    *
  1. An already-parsed compilation unit *
  2. A class file in the output directory (if existant and younger than source file) *
  3. A source file in any of the source path directories *
  4. The parent class loader *
* Notice that the {@link JGrepIClassLoader} is an inner class of {@link JGrep} and * heavily uses {@link JGrep}'s members. */ private class JGrepIClassLoader extends IClassLoader { /** * @param optionalParentIClassLoader The {@link IClassLoader} through which {@link IClass}es are to be loaded */ public JGrepIClassLoader(IClassLoader optionalParentIClassLoader) { super(optionalParentIClassLoader); super.postConstruct(); } /** @param type Field descriptor of the {@IClass} to load, e.g. "Lpkg1/pkg2/Outer$Inner;" */ @Override protected IClass findIClass(final String type) { if (JGrep.DEBUG) System.out.println("type = " + type); // Class type. String className = Descriptor.toClassName(type); // E.g. "pkg1.pkg2.Outer$Inner" if (JGrep.DEBUG) System.out.println("2 className = \"" + className + "\""); // Do not attempt to load classes from package "java". if (className.startsWith("java.")) return null; // Check the already-parsed compilation units. for (int i = 0; i < JGrep.this.parsedCompilationUnits.size(); ++i) { UnitCompiler uc = (UnitCompiler) JGrep.this.parsedCompilationUnits.get(i); IClass res = uc.findClass(className); if (res != null) { this.defineIClass(res); return res; } } return null; } } }