/*
* Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.hosted;
import static com.sun.max.vm.hosted.MethodFinder.PatternType.*;
import java.util.*;
import com.sun.max.annotate.*;
import com.sun.max.program.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.type.*;
/**
* A utility for {@linkplain #find(String[], Classpath, ClassLoader, List) finding} a set of methods
* based on a class path and one or more patterns.
* <p>
* A pattern is a class name pattern followed by an optional method name
* pattern separated by a ':' further followed by an optional signature:
* <pre>
* <class name>[:<method name>[:<signature>]]
* </pre>
* For example, the list of patterns:
* <pre>
* "Object:wait", "String", "Util:add:(int,float)"
* </pre>
* will match all methods in a class whose name contains {@code "Object"} where the
* method name contains {@code "wait"}, all methods in a class whose name
* contains {@code "String"} and all methods in any class whose name
* contains {@code "Util"}, the method name contains {@code "add"} and the
* method signature is {@code (int, float)}.
* <p>
* The type of matching performed for a given class/method name is determined
* by the position of '^' in the pattern name as follows:
* <pre>
* Position of '^' | Match algorithm
* ------------------+------------------
* start AND end | Equality
* start | Prefix
* end | Suffix
* absent | Substring
* </pre>
*
* For example, {@code "^java.util:^toString^"} matches all methods named {@code "toString"} in
* any class whose name starts with {@code "java.util"}.
* <p>
* The matching performed on a signature is always a substring test. Signatures can
* specified either in Java source syntax (e.g. {@code "int,String"}) or JVM internal syntax
* (e.g. {@code "IFLjava/lang/String;"}). The latter must always use fully qualified type
* names where as the former must not.
* <p>
* Any pattern starting with {@code '!'} is an exclusion specification. Any class or method
* whose name contains an exclusion string (the exclusion specification minus the
* leading {@code '!'}) is excluded.
*/
public class MethodFinder {
enum PatternType {
EXACT("matching") {
@Override
public boolean matches(String input, String pattern) {
return input.equals(pattern);
}
},
PREFIX("starting with") {
@Override
public boolean matches(String input, String pattern) {
return input.startsWith(pattern);
}
},
SUFFIX("ending with") {
@Override
public boolean matches(String input, String pattern) {
return input.endsWith(pattern);
}
},
CONTAINS("containing") {
@Override
public boolean matches(String input, String pattern) {
return input.contains(pattern);
}
};
final String relationship;
private PatternType(String relationship) {
this.relationship = relationship;
}
abstract boolean matches(String input, String pattern);
@Override
public String toString() {
return relationship;
}
}
public static class PatternMatcher {
public final String pattern;
// 1: exact, 2: prefix, 3: suffix, 4: substring
public final PatternType type;
public PatternMatcher(String pattern) {
if (pattern.startsWith("^") && pattern.endsWith("^") && pattern.length() != 1) {
this.type = EXACT;
this.pattern = pattern.substring(1, pattern.length() - 1);
} else if (pattern.startsWith("^")) {
this.type = PREFIX;
this.pattern = pattern.length() == 1 ? "" : pattern.substring(1);
} else if (pattern.endsWith("^")) {
this.type = SUFFIX;
this.pattern = pattern.substring(0, pattern.length() - 1);
} else {
this.type = CONTAINS;
this.pattern = pattern;
}
}
boolean matches(String input) {
return type.matches(input, pattern);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PatternMatcher) {
PatternMatcher pm = (PatternMatcher) obj;
return pm.pattern.equals(pattern) && pm.type.equals(type);
}
return false;
}
@Override
public int hashCode() {
return pattern.hashCode();
}
}
public MethodFinder() {
}
/**
* Gets the list of methods matching a given set of patterns.
*
* @param patterns a list of patterns used to filter the methods found on the class path
* @param classpath
* @param classLoader
* @param nonFatalErrors list to which non-fatal errors are appended during the search. If {@code null}, then a
* {@linkplain ProgramWarning warning} is logged
*/
public List<MethodActor> find(String[] patterns, Classpath classpath, ClassLoader classLoader, List<Throwable> nonFatalErrors) {
final List<MethodActor> methods = new ArrayList<MethodActor>();
final Set<String> exclusions = new HashSet<String>();
for (int i = 0; i != patterns.length; ++i) {
final String argument = patterns[i];
if (argument.startsWith("!")) {
exclusions.add(argument.substring(1));
patterns[i] = null;
}
}
for (int i = 0; i != patterns.length; ++i) {
final String argument = patterns[i];
if (argument == null) {
continue;
}
final int colonIndex = argument.indexOf(':');
final PatternMatcher classNamePattern = new PatternMatcher(colonIndex == -1 ? argument : argument.substring(0, colonIndex));
// search for matching classes on the class path
final List<String> matchingClasses = new ArrayList<String>();
if (classNamePattern.type == EXACT) {
matchingClasses.add(classNamePattern.pattern);
} else {
new ClassSearch() {
@Override
protected boolean visitClass(String className) {
if (!className.endsWith("package-info")) {
if (classNamePattern.matches(className)) {
for (String exclusion : exclusions) {
if (className.contains(exclusion)) {
return true;
}
}
matchingClasses.add(className);
}
}
return true;
}
}.run(classpath);
}
// for all found classes, search for matching methods
for (String className : matchingClasses) {
try {
Class<?> javaClass = null;
try {
javaClass = Class.forName(className, false, classLoader);
} catch (NoClassDefFoundError noClassDefFoundError) {
throw new ClassNotFoundException(className, noClassDefFoundError);
}
final ClassActor classActor = getClassActor(javaClass);
if (classActor == null) {
continue;
}
if (colonIndex == -1) {
// Class only: select all methods in class
for (MethodActor actor : classActor.localStaticMethodActors()) {
addMethod(methods, actor, exclusions);
}
for (MethodActor methodActor : classActor.localVirtualMethodActors()) {
addMethod(methods, methodActor, exclusions);
}
for (MethodActor methodActor : classActor.localInterfaceMethodActors()) {
addMethod(methods, methodActor, exclusions);
}
} else {
// a method pattern was specified, find matching methods
final int secondColonIndex = argument.indexOf(':', colonIndex + 1);
final PatternMatcher methodNamePattern;
String signature;
if (secondColonIndex == -1) {
methodNamePattern = new PatternMatcher(argument.substring(colonIndex + 1));
signature = null;
} else {
methodNamePattern = new PatternMatcher(argument.substring(colonIndex + 1, secondColonIndex));
signature = argument.substring(secondColonIndex + 1);
// Normalize specified signature to have only a single space after any commas
signature = signature.replaceAll(", *", ", ");
}
addMatchingMethods(methods, classActor, methodNamePattern, signature, classActor.localStaticMethodActors(), exclusions);
addMatchingMethods(methods, classActor, methodNamePattern, signature, classActor.localVirtualMethodActors(), exclusions);
addMatchingMethods(methods, classActor, methodNamePattern, signature, classActor.localInterfaceMethodActors(), exclusions);
}
} catch (ClassNotFoundException classNotFoundException) {
if (nonFatalErrors != null) {
nonFatalErrors.add(classNotFoundException);
} else {
ProgramWarning.message(classNotFoundException.toString() + (classNotFoundException.getCause() == null ? "" : " (cause: " + classNotFoundException.getCause() + ")"));
}
}
}
}
return methods;
}
/**
* Adds a class to the list of classes that will be searched for matching methods.
* Subclasses can override this to implement additional filtering.
*
* @param pattern the pattern that matched {@code className}
* @param className the name of a class that will be processed
* @param matchingClasses the list of classes that will be processed
*/
protected void addClassToProcess(PatternMatcher pattern, String className, List<String> matchingClasses) {
matchingClasses.add(className);
}
/**
* Adds a method to a list of matching methods.
* Subclasses can override this to implement additional filtering.
*
* @param method the method to add
* @param methods the list to which {@code method} is added
*/
protected void addMethod(MethodActor method, List<MethodActor> methods) {
methods.add(method);
}
private void addMethod(List<MethodActor> methods, MethodActor methodActor, Set<String> exclusions) {
for (String exclusion : exclusions) {
if (methodActor.name.string.contains(exclusion)) {
return;
}
}
addMethod(methodActor, methods);
}
/**
* Gets a {@link ClassActor} corresponding to a given {@link Class} instance.
*/
protected ClassActor getClassActor(Class<?> javaClass) {
return ClassActor.fromJava(javaClass);
}
private void addMatchingMethods(final List<MethodActor> methods, final ClassActor classActor, final PatternMatcher methodNamePattern, final String signature, MethodActor[] methodActors, Set<String> exclusions) {
for (final MethodActor method : methodActors) {
if (methodNamePattern.matches(method.name.toString())) {
if (signature != null) {
final SignatureDescriptor methodSignature = method.descriptor();
if (methodSignature.string.contains(signature)) {
addMethod(methods, method, exclusions);
} else {
String javaSignature = methodSignature.toJavaString(false, true);
if (javaSignature.contains(signature)) {
addMethod(methods, method, exclusions);
}
}
} else {
addMethod(methods, method, exclusions);
}
}
}
}
@HOSTED_ONLY
public static class Usage {
public static void main(String[] args) {
}
}
public static void main(String[] args) {
System.out.print("Creating Java prototype... ");
JavaPrototype.initialize(false);
System.out.println("done");
MethodFinder matcher = new MethodFinder();
List<MethodActor> methods = matcher.find(args, Classpath.fromSystem(), MethodFinder.class.getClassLoader(), null);
System.out.println("Matched " + methods.size() + " methods:");
for (MethodActor method : methods) {
System.out.println(method.format(" %H.%n(%p)"));
}
}
}