blob: 8ea958a6e2c58e2dbec3c2ef7a5cbc08731d4a55 [file] [log] [blame]
/*
* Copyright (c) 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 org.graalvm.compiler.debug;
import java.util.Arrays;
import java.util.regex.Pattern;
import jdk.vm.ci.meta.JavaMethod;
import jdk.vm.ci.meta.JavaType;
import jdk.vm.ci.meta.Signature;
/**
* This class implements a method filter that can filter based on class name, method name and
* parameters. The syntax for the source pattern that is passed to the constructor is as follows:
*
* <pre>
* SourcePatterns = SourcePattern ["," SourcePatterns] .
* SourcePattern = [ Class "." ] method [ "(" [ Parameter { ";" Parameter } ] ")" ] .
* Parameter = Class | "int" | "long" | "float" | "double" | "short" | "char" | "boolean" .
* Class = { package "." } class .
* </pre>
*
*
* Glob pattern matching (*, ?) is allowed in all parts of the source pattern. Examples for valid
* filters are:
*
* <ul>
* <li>
*
* <pre>
* visit(Argument;BlockScope)
* </pre>
*
* Matches all methods named "visit", with the first parameter of type "Argument", and the second
* parameter of type "BlockScope". The packages of the parameter types are irrelevant.</li>
* <li>
*
* <pre>
* arraycopy(Object;;;;)
* </pre>
*
* Matches all methods named "arraycopy", with the first parameter of type "Object", and four more
* parameters of any type. The packages of the parameter types are irrelevant.</li>
* <li>
*
* <pre>
* org.graalvm.compiler.core.graph.PostOrderNodeIterator.*
* </pre>
*
* Matches all methods in the class "org.graalvm.compiler.core.graph.PostOrderNodeIterator".</li>
* <li>
*
* <pre>
* *
* </pre>
*
* Matches all methods in all classes</li>
* <li>
*
* <pre>
* org.graalvm.compiler.core.graph.*.visit
* </pre>
*
* Matches all methods named "visit" in classes in the package "org.graalvm.compiler.core.graph".
* <li>
*
* <pre>
* arraycopy,toString
* </pre>
*
* Matches all methods named "arraycopy" or "toString", meaning that ',' acts as an <i>or</i>
* operator.</li>
* </ul>
*/
public class MethodFilter {
private final Pattern clazz;
private final Pattern methodName;
private final Pattern[] signature;
/**
* Parses a string containing list of comma separated filter patterns into an array of
* {@link MethodFilter}s.
*/
public static MethodFilter[] parse(String commaSeparatedPatterns) {
String[] filters = commaSeparatedPatterns.split(",");
MethodFilter[] methodFilters = new MethodFilter[filters.length];
for (int i = 0; i < filters.length; i++) {
methodFilters[i] = new MethodFilter(filters[i]);
}
return methodFilters;
}
/**
* Determines if a given method is matched by a given array of filters.
*/
public static boolean matches(MethodFilter[] filters, JavaMethod method) {
for (MethodFilter filter : filters) {
if (filter.matches(method)) {
return true;
}
}
return false;
}
/**
* Determines if a given class name is matched by a given array of filters.
*/
public static boolean matchesClassName(MethodFilter[] filters, String className) {
for (MethodFilter filter : filters) {
if (filter.matchesClassName(className)) {
return true;
}
}
return false;
}
public MethodFilter(String sourcePattern) {
String pattern = sourcePattern.trim();
// extract parameter part
int pos = pattern.indexOf('(');
if (pos != -1) {
if (pattern.charAt(pattern.length() - 1) != ')') {
throw new IllegalArgumentException("missing ')' at end of method filter pattern: " + pattern);
}
String[] signatureClasses = pattern.substring(pos + 1, pattern.length() - 1).split(";", -1);
signature = new Pattern[signatureClasses.length];
for (int i = 0; i < signatureClasses.length; i++) {
signature[i] = createClassGlobPattern(signatureClasses[i].trim());
}
pattern = pattern.substring(0, pos);
} else {
signature = null;
}
// If there is at least one "." then everything before the last "." is the class name.
// Otherwise, the pattern contains only the method name.
pos = pattern.lastIndexOf('.');
if (pos != -1) {
clazz = createClassGlobPattern(pattern.substring(0, pos));
methodName = Pattern.compile(createGlobString(pattern.substring(pos + 1)));
} else {
clazz = null;
methodName = Pattern.compile(createGlobString(pattern));
}
}
public static String createGlobString(String pattern) {
return Pattern.quote(pattern).replace("?", "\\E.\\Q").replace("*", "\\E.*\\Q");
}
private static Pattern createClassGlobPattern(String pattern) {
if (pattern.length() == 0) {
return null;
} else if (pattern.contains(".")) {
return Pattern.compile(createGlobString(pattern));
} else {
return Pattern.compile("([^\\.\\$]*[\\.\\$])*" + createGlobString(pattern));
}
}
public boolean hasSignature() {
return signature != null;
}
/**
* Determines if the class part of this filter matches a given class name.
*/
public boolean matchesClassName(String className) {
return clazz == null || clazz.matcher(className).matches();
}
public boolean matches(JavaMethod o) {
// check method name first, since MetaUtil.toJavaName is expensive
if (methodName != null && !methodName.matcher(o.getName()).matches()) {
return false;
}
if (clazz != null && !clazz.matcher(o.getDeclaringClass().toJavaName()).matches()) {
return false;
}
return matchesSignature(o.getSignature());
}
private boolean matchesSignature(Signature sig) {
if (signature == null) {
return true;
}
if (sig.getParameterCount(false) != signature.length) {
return false;
}
for (int i = 0; i < signature.length; i++) {
JavaType type = sig.getParameterType(i, null);
String javaName = type.toJavaName();
if (signature[i] != null && !signature[i].matcher(javaName).matches()) {
return false;
}
}
return true;
}
public boolean matches(String javaClassName, String name, Signature sig) {
assert sig != null || signature == null;
// check method name first, since MetaUtil.toJavaName is expensive
if (methodName != null && !methodName.matcher(name).matches()) {
return false;
}
if (clazz != null && !clazz.matcher(javaClassName).matches()) {
return false;
}
return matchesSignature(sig);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder("MethodFilter[");
String sep = "";
if (clazz != null) {
buf.append(sep).append("clazz=").append(clazz);
sep = ", ";
}
if (methodName != null) {
buf.append(sep).append("methodName=").append(methodName);
sep = ", ";
}
if (signature != null) {
buf.append(sep).append("signature=").append(Arrays.toString(signature));
sep = ", ";
}
return buf.append("]").toString();
}
}