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 a filter is explained <a href="MethodFilterHelp.txt">here</a>.
35 */
36public class MethodFilter {
37
38    private final Pattern clazz;
39    private final Pattern methodName;
40    private final Pattern[] signature;
41
42    /**
43     * Parses a string containing list of comma separated filter patterns into an array of
44     * {@link MethodFilter}s.
45     */
46    public static MethodFilter[] parse(String commaSeparatedPatterns) {
47        String[] filters = commaSeparatedPatterns.split(",");
48        MethodFilter[] methodFilters = new MethodFilter[filters.length];
49        for (int i = 0; i < filters.length; i++) {
50            methodFilters[i] = new MethodFilter(filters[i]);
51        }
52        return methodFilters;
53    }
54
55    /**
56     * Determines if a given method is matched by a given array of filters.
57     */
58    public static boolean matches(MethodFilter[] filters, JavaMethod method) {
59        for (MethodFilter filter : filters) {
60            if (filter.matches(method)) {
61                return true;
62            }
63        }
64        return false;
65    }
66
67    /**
68     * Determines if a given class name is matched by a given array of filters.
69     */
70    public static boolean matchesClassName(MethodFilter[] filters, String className) {
71        for (MethodFilter filter : filters) {
72            if (filter.matchesClassName(className)) {
73                return true;
74            }
75        }
76        return false;
77    }
78
79    public MethodFilter(String sourcePattern) {
80        String pattern = sourcePattern.trim();
81
82        // extract parameter part
83        int pos = pattern.indexOf('(');
84        if (pos != -1) {
85            if (pattern.charAt(pattern.length() - 1) != ')') {
86                throw new IllegalArgumentException("missing ')' at end of method filter pattern: " + pattern);
87            }
88            String[] signatureClasses = pattern.substring(pos + 1, pattern.length() - 1).split(";", -1);
89            signature = new Pattern[signatureClasses.length];
90            for (int i = 0; i < signatureClasses.length; i++) {
91                signature[i] = createClassGlobPattern(signatureClasses[i].trim());
92            }
93            pattern = pattern.substring(0, pos);
94        } else {
95            signature = null;
96        }
97
98        // If there is at least one "." then everything before the last "." is the class name.
99        // Otherwise, the pattern contains only the method name.
100        pos = pattern.lastIndexOf('.');
101        if (pos != -1) {
102            clazz = createClassGlobPattern(pattern.substring(0, pos));
103            methodName = Pattern.compile(createGlobString(pattern.substring(pos + 1)));
104        } else {
105            clazz = null;
106            methodName = Pattern.compile(createGlobString(pattern));
107        }
108    }
109
110    public static String createGlobString(String pattern) {
111        return Pattern.quote(pattern).replace("?", "\\E.\\Q").replace("*", "\\E.*\\Q");
112    }
113
114    private static Pattern createClassGlobPattern(String pattern) {
115        if (pattern.length() == 0) {
116            return null;
117        } else if (pattern.contains(".")) {
118            return Pattern.compile(createGlobString(pattern));
119        } else {
120            return Pattern.compile("([^\\.\\$]*[\\.\\$])*" + createGlobString(pattern));
121        }
122    }
123
124    public boolean hasSignature() {
125        return signature != null;
126    }
127
128    /**
129     * Determines if the class part of this filter matches a given class name.
130     */
131    public boolean matchesClassName(String className) {
132        return clazz == null || clazz.matcher(className).matches();
133    }
134
135    public boolean matches(JavaMethod o) {
136        // check method name first, since MetaUtil.toJavaName is expensive
137        if (methodName != null && !methodName.matcher(o.getName()).matches()) {
138            return false;
139        }
140        if (clazz != null && !clazz.matcher(o.getDeclaringClass().toJavaName()).matches()) {
141            return false;
142        }
143        return matchesSignature(o.getSignature());
144    }
145
146    private boolean matchesSignature(Signature sig) {
147        if (signature == null) {
148            return true;
149        }
150        if (sig.getParameterCount(false) != signature.length) {
151            return false;
152        }
153        for (int i = 0; i < signature.length; i++) {
154            JavaType type = sig.getParameterType(i, null);
155            String javaName = type.toJavaName();
156            if (signature[i] != null && !signature[i].matcher(javaName).matches()) {
157                return false;
158            }
159        }
160        return true;
161    }
162
163    public boolean matches(String javaClassName, String name, Signature sig) {
164        assert sig != null || signature == null;
165        // check method name first, since MetaUtil.toJavaName is expensive
166        if (methodName != null && !methodName.matcher(name).matches()) {
167            return false;
168        }
169        if (clazz != null && !clazz.matcher(javaClassName).matches()) {
170            return false;
171        }
172        return matchesSignature(sig);
173    }
174
175    @Override
176    public String toString() {
177        StringBuilder buf = new StringBuilder("MethodFilter[");
178        String sep = "";
179        if (clazz != null) {
180            buf.append(sep).append("clazz=").append(clazz);
181            sep = ", ";
182        }
183        if (methodName != null) {
184            buf.append(sep).append("methodName=").append(methodName);
185            sep = ", ";
186        }
187        if (signature != null) {
188            buf.append(sep).append("signature=").append(Arrays.toString(signature));
189            sep = ", ";
190        }
191        return buf.append("]").toString();
192    }
193}
194