1/*
2 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.graalvm.compiler.debug;
24
25import java.util.Arrays;
26import java.util.regex.Pattern;
27
28import jdk.vm.ci.meta.JavaMethod;
29import jdk.vm.ci.meta.JavaType;
30import jdk.vm.ci.meta.Signature;
31
32/**
33 * This class implements a method filter that can filter based on class name, method name and
34 * parameters. The syntax for the source pattern that is passed to the constructor is as follows:
35 *
36 * <pre>
37 * SourcePatterns = SourcePattern ["," SourcePatterns] .
38 * SourcePattern = [ Class "." ] method [ "(" [ Parameter { ";" Parameter } ] ")" ] .
39 * Parameter = Class | "int" | "long" | "float" | "double" | "short" | "char" | "boolean" .
40 * Class = { package "." } class .
41 * </pre>
42 *
43 *
44 * Glob pattern matching (*, ?) is allowed in all parts of the source pattern. Examples for valid
45 * filters are:
46 *
47 * <ul>
48 * <li>
49 *
50 * <pre>
51 * visit(Argument;BlockScope)
52 * </pre>
53 *
54 * Matches all methods named "visit", with the first parameter of type "Argument", and the second
55 * parameter of type "BlockScope". The packages of the parameter types are irrelevant.</li>
56 * <li>
57 *
58 * <pre>
59 * arraycopy(Object;;;;)
60 * </pre>
61 *
62 * Matches all methods named "arraycopy", with the first parameter of type "Object", and four more
63 * parameters of any type. The packages of the parameter types are irrelevant.</li>
64 * <li>
65 *
66 * <pre>
67 * org.graalvm.compiler.core.graph.PostOrderNodeIterator.*
68 * </pre>
69 *
70 * Matches all methods in the class "org.graalvm.compiler.core.graph.PostOrderNodeIterator".</li>
71 * <li>
72 *
73 * <pre>
74 * *
75 * </pre>
76 *
77 * Matches all methods in all classes</li>
78 * <li>
79 *
80 * <pre>
81 * org.graalvm.compiler.core.graph.*.visit
82 * </pre>
83 *
84 * Matches all methods named "visit" in classes in the package "org.graalvm.compiler.core.graph".
85 * <li>
86 *
87 * <pre>
88 * arraycopy,toString
89 * </pre>
90 *
91 * Matches all methods named "arraycopy" or "toString", meaning that ',' acts as an <i>or</i>
92 * operator.</li>
93 * </ul>
94 */
95public class MethodFilter {
96
97    private final Pattern clazz;
98    private final Pattern methodName;
99    private final Pattern[] signature;
100
101    /**
102     * Parses a string containing list of comma separated filter patterns into an array of
103     * {@link MethodFilter}s.
104     */
105    public static MethodFilter[] parse(String commaSeparatedPatterns) {
106        String[] filters = commaSeparatedPatterns.split(",");
107        MethodFilter[] methodFilters = new MethodFilter[filters.length];
108        for (int i = 0; i < filters.length; i++) {
109            methodFilters[i] = new MethodFilter(filters[i]);
110        }
111        return methodFilters;
112    }
113
114    /**
115     * Determines if a given method is matched by a given array of filters.
116     */
117    public static boolean matches(MethodFilter[] filters, JavaMethod method) {
118        for (MethodFilter filter : filters) {
119            if (filter.matches(method)) {
120                return true;
121            }
122        }
123        return false;
124    }
125
126    /**
127     * Determines if a given class name is matched by a given array of filters.
128     */
129    public static boolean matchesClassName(MethodFilter[] filters, String className) {
130        for (MethodFilter filter : filters) {
131            if (filter.matchesClassName(className)) {
132                return true;
133            }
134        }
135        return false;
136    }
137
138    public MethodFilter(String sourcePattern) {
139        String pattern = sourcePattern.trim();
140
141        // extract parameter part
142        int pos = pattern.indexOf('(');
143        if (pos != -1) {
144            if (pattern.charAt(pattern.length() - 1) != ')') {
145                throw new IllegalArgumentException("missing ')' at end of method filter pattern: " + pattern);
146            }
147            String[] signatureClasses = pattern.substring(pos + 1, pattern.length() - 1).split(";", -1);
148            signature = new Pattern[signatureClasses.length];
149            for (int i = 0; i < signatureClasses.length; i++) {
150                signature[i] = createClassGlobPattern(signatureClasses[i].trim());
151            }
152            pattern = pattern.substring(0, pos);
153        } else {
154            signature = null;
155        }
156
157        // If there is at least one "." then everything before the last "." is the class name.
158        // Otherwise, the pattern contains only the method name.
159        pos = pattern.lastIndexOf('.');
160        if (pos != -1) {
161            clazz = createClassGlobPattern(pattern.substring(0, pos));
162            methodName = Pattern.compile(createGlobString(pattern.substring(pos + 1)));
163        } else {
164            clazz = null;
165            methodName = Pattern.compile(createGlobString(pattern));
166        }
167    }
168
169    public static String createGlobString(String pattern) {
170        return Pattern.quote(pattern).replace("?", "\\E.\\Q").replace("*", "\\E.*\\Q");
171    }
172
173    private static Pattern createClassGlobPattern(String pattern) {
174        if (pattern.length() == 0) {
175            return null;
176        } else if (pattern.contains(".")) {
177            return Pattern.compile(createGlobString(pattern));
178        } else {
179            return Pattern.compile("([^\\.\\$]*[\\.\\$])*" + createGlobString(pattern));
180        }
181    }
182
183    public boolean hasSignature() {
184        return signature != null;
185    }
186
187    /**
188     * Determines if the class part of this filter matches a given class name.
189     */
190    public boolean matchesClassName(String className) {
191        return clazz == null || clazz.matcher(className).matches();
192    }
193
194    public boolean matches(JavaMethod o) {
195        // check method name first, since MetaUtil.toJavaName is expensive
196        if (methodName != null && !methodName.matcher(o.getName()).matches()) {
197            return false;
198        }
199        if (clazz != null && !clazz.matcher(o.getDeclaringClass().toJavaName()).matches()) {
200            return false;
201        }
202        return matchesSignature(o.getSignature());
203    }
204
205    private boolean matchesSignature(Signature sig) {
206        if (signature == null) {
207            return true;
208        }
209        if (sig.getParameterCount(false) != signature.length) {
210            return false;
211        }
212        for (int i = 0; i < signature.length; i++) {
213            JavaType type = sig.getParameterType(i, null);
214            String javaName = type.toJavaName();
215            if (signature[i] != null && !signature[i].matcher(javaName).matches()) {
216                return false;
217            }
218        }
219        return true;
220    }
221
222    public boolean matches(String javaClassName, String name, Signature sig) {
223        assert sig != null || signature == null;
224        // check method name first, since MetaUtil.toJavaName is expensive
225        if (methodName != null && !methodName.matcher(name).matches()) {
226            return false;
227        }
228        if (clazz != null && !clazz.matcher(javaClassName).matches()) {
229            return false;
230        }
231        return matchesSignature(sig);
232    }
233
234    @Override
235    public String toString() {
236        StringBuilder buf = new StringBuilder("MethodFilter[");
237        String sep = "";
238        if (clazz != null) {
239            buf.append(sep).append("clazz=").append(clazz);
240            sep = ", ";
241        }
242        if (methodName != null) {
243            buf.append(sep).append("methodName=").append(methodName);
244            sep = ", ";
245        }
246        if (signature != null) {
247            buf.append(sep).append("signature=").append(Arrays.toString(signature));
248            sep = ", ";
249        }
250        return buf.append("]").toString();
251    }
252}
253