001/*
002 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.debug;
024
025import java.util.*;
026import java.util.regex.*;
027
028import jdk.internal.jvmci.meta.*;
029
030/**
031 * This class implements a method filter that can filter based on class name, method name and
032 * parameters. The syntax for the source pattern that is passed to the constructor is as follows:
033 *
034 * <pre>
035 * SourcePatterns = SourcePattern ["," SourcePatterns] .
036 * SourcePattern = [ Class "." ] method [ "(" [ Parameter { ";" Parameter } ] ")" ] .
037 * Parameter = Class | "int" | "long" | "float" | "double" | "short" | "char" | "boolean" .
038 * Class = { package "." } class .
039 * </pre>
040 *
041 *
042 * Glob pattern matching (*, ?) is allowed in all parts of the source pattern. Examples for valid
043 * filters are:
044 *
045 * <ul>
046 * <li>
047 *
048 * <pre>
049 * visit(Argument;BlockScope)
050 * </pre>
051 *
052 * Matches all methods named "visit", with the first parameter of type "Argument", and the second
053 * parameter of type "BlockScope". The packages of the parameter types are irrelevant.</li>
054 * <li>
055 *
056 * <pre>
057 * arraycopy(Object;;;;)
058 * </pre>
059 *
060 * Matches all methods named "arraycopy", with the first parameter of type "Object", and four more
061 * parameters of any type. The packages of the parameter types are irrelevant.</li>
062 * <li>
063 *
064 * <pre>
065 * com.oracle.graal.compiler.graph.PostOrderNodeIterator.*
066 * </pre>
067 *
068 * Matches all methods in the class "com.oracle.graal.compiler.graph.PostOrderNodeIterator".</li>
069 * <li>
070 *
071 * <pre>
072 * *
073 * </pre>
074 *
075 * Matches all methods in all classes</li>
076 * <li>
077 *
078 * <pre>
079 * com.oracle.graal.compiler.graph.*.visit
080 * </pre>
081 *
082 * Matches all methods named "visit" in classes in the package "com.oracle.graal.compiler.graph".
083 * <li>
084 *
085 * <pre>
086 * arraycopy,toString
087 * </pre>
088 *
089 * Matches all methods named "arraycopy" or "toString", meaning that ',' acts as an <i>or</i>
090 * operator.</li>
091 * </ul>
092 */
093public class MethodFilter {
094
095    private final Pattern clazz;
096    private final Pattern methodName;
097    private final Pattern[] signature;
098
099    /**
100     * Parses a string containing list of comma separated filter patterns into an array of
101     * {@link MethodFilter}s.
102     */
103    public static MethodFilter[] parse(String commaSeparatedPatterns) {
104        String[] filters = commaSeparatedPatterns.split(",");
105        MethodFilter[] methodFilters = new MethodFilter[filters.length];
106        for (int i = 0; i < filters.length; i++) {
107            methodFilters[i] = new MethodFilter(filters[i]);
108        }
109        return methodFilters;
110    }
111
112    /**
113     * Determines if a given method is matched by a given array of filters.
114     */
115    public static boolean matches(MethodFilter[] filters, JavaMethod method) {
116        for (MethodFilter filter : filters) {
117            if (filter.matches(method)) {
118                return true;
119            }
120        }
121        return false;
122    }
123
124    /**
125     * Determines if a given class name is matched by a given array of filters.
126     */
127    public static boolean matchesClassName(MethodFilter[] filters, String className) {
128        for (MethodFilter filter : filters) {
129            if (filter.matchesClassName(className)) {
130                return true;
131            }
132        }
133        return false;
134    }
135
136    public MethodFilter(String sourcePattern) {
137        String pattern = sourcePattern.trim();
138
139        // extract parameter part
140        int pos = pattern.indexOf('(');
141        if (pos != -1) {
142            if (pattern.charAt(pattern.length() - 1) != ')') {
143                throw new IllegalArgumentException("missing ')' at end of method filter pattern: " + pattern);
144            }
145            String[] signatureClasses = pattern.substring(pos + 1, pattern.length() - 1).split(";", -1);
146            signature = new Pattern[signatureClasses.length];
147            for (int i = 0; i < signatureClasses.length; i++) {
148                signature[i] = createClassGlobPattern(signatureClasses[i].trim());
149            }
150            pattern = pattern.substring(0, pos);
151        } else {
152            signature = null;
153        }
154
155        // If there is at least one "." then everything before the last "." is the class name.
156        // Otherwise, the pattern contains only the method name.
157        pos = pattern.lastIndexOf('.');
158        if (pos != -1) {
159            clazz = createClassGlobPattern(pattern.substring(0, pos));
160            methodName = Pattern.compile(createGlobString(pattern.substring(pos + 1)));
161        } else {
162            clazz = null;
163            methodName = Pattern.compile(createGlobString(pattern));
164        }
165    }
166
167    public static String createGlobString(String pattern) {
168        return Pattern.quote(pattern).replace("?", "\\E.\\Q").replace("*", "\\E.*\\Q");
169    }
170
171    private static Pattern createClassGlobPattern(String pattern) {
172        if (pattern.length() == 0) {
173            return null;
174        } else if (pattern.contains(".")) {
175            return Pattern.compile(createGlobString(pattern));
176        } else {
177            return Pattern.compile("([^\\.\\$]*[\\.\\$])*" + createGlobString(pattern));
178        }
179    }
180
181    /**
182     * Determines if the class part of this filter matches a given class name.
183     */
184    public boolean matchesClassName(String className) {
185        return clazz == null || clazz.matcher(className).matches();
186    }
187
188    public boolean matches(JavaMethod o) {
189        // check method name first, since MetaUtil.toJavaName is expensive
190        if (methodName != null && !methodName.matcher(o.getName()).matches()) {
191            return false;
192        }
193        if (clazz != null && !clazz.matcher(o.getDeclaringClass().toJavaName()).matches()) {
194            return false;
195        }
196        if (signature != null) {
197            Signature sig = o.getSignature();
198            if (sig.getParameterCount(false) != signature.length) {
199                return false;
200            }
201            for (int i = 0; i < signature.length; i++) {
202                JavaType type = sig.getParameterType(i, null);
203                String javaName = type.toJavaName();
204                if (signature[i] != null && !signature[i].matcher(javaName).matches()) {
205                    return false;
206                }
207            }
208        }
209        return true;
210    }
211
212    @Override
213    public String toString() {
214        StringBuilder buf = new StringBuilder("MethodFilter[");
215        String sep = "";
216        if (clazz != null) {
217            buf.append(sep).append("clazz=").append(clazz);
218            sep = ", ";
219        }
220        if (methodName != null) {
221            buf.append(sep).append("methodName=").append(methodName);
222            sep = ", ";
223        }
224        if (signature != null) {
225            buf.append(sep).append("signature=").append(Arrays.toString(signature));
226            sep = ", ";
227        }
228        return buf.append("]").toString();
229    }
230}