/* * FindBugs - Find bugs in Java programs * Copyright (C) 2004, University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs; import static edu.umd.cs.findbugs.util.Strings.replace; import java.util.LinkedList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Class to pre-screen class files, so that only a subset are * analyzed. This supports the -onlyAnalyze command line option. * * Modified February 2006 in four ways: * a) don't break windows platform by hard-coding '/' as the directory separator * b) store list of Matchers, not Patterns, so we don't keep instantiating Matchers * c) fix suffix bug, so FooBar and Foo$Bar no longer match Bar * d) addAllowedPackage() can now handle unicode chars in filenames, though we * still may not be handling every case mentioned in section 7.2.1 of the JLS * * @see FindBugs * @author David Hovemeyer */ public class ClassScreener implements IClassScreener { private static final boolean DEBUG = SystemProperties.getBoolean("findbugs.classscreener.debug"); /** regular expression fragment to match a directory separator. note: could use * File.separatorChar instead, but that could be argued to be not general enough */ private static final String SEP = "[/\\\\]"; // could include ':' for classic macOS private static final String START = "(?:^|"+SEP+")"; // (?:) is a non-capturing group /** regular expression fragment to match a char of a class or package name. * Actually, we just allow any char except a dot or a directory separator. */ private static final String JAVA_IDENTIFIER_PART = "[^./\\\\]"; private LinkedList<Matcher> patternList; /** * Constructor. * By default, the ClassScreener will match <em>all</em> class files. * Once addAllowedClass() and addAllowedPackage() are called, * the ClassScreener will only match the explicitly specified classes * and packages. */ public ClassScreener() { this.patternList = new LinkedList<Matcher>(); } /** replace the dots in a fully-qualified class/package name to a * regular expression fragment that will match file names. * @param dotsName such as "java.io" or "java.io.File" * @return regex fragment such as "java[/\\\\]io" (single backslash escaped twice) */ private static String dotsToRegex(String dotsName) { /* oops, next line requires JDK 1.5 return dotsName.replace("$", "\\$").replace(".", SEP); * could use String.replaceAll(regex, repl) but that can be problematic--javadoc says * "Note that backslashes (\) and dollar signs ($) in the replacement string may cause * the results to be different than if it were being treated as a literal replacement" */ String tmp = dotsName.replace("$", "\\$"); return tmp.replace(".", SEP); // note: The original code used the \Q and \E regex quoting constructs to escape $. } /** * Add the name of a class to be matched by the screener. * * @param className name of a class to be matched */ public void addAllowedClass(String className) { String classRegex = START+dotsToRegex(className)+".class$"; if (DEBUG) System.out.println("Class regex: " + classRegex); patternList.add(Pattern.compile(classRegex).matcher("")); } /** * Add the name of a package to be matched by the screener. * All class files that appear to be in the package should be matched. * * @param packageName name of the package to be matched */ public void addAllowedPackage(String packageName) { if (packageName.endsWith(".")) { packageName = packageName.substring(0, packageName.length() - 1); } String packageRegex = START+dotsToRegex(packageName)+SEP+JAVA_IDENTIFIER_PART+"+.class$"; if (DEBUG) System.out.println("Package regex: " + packageRegex); patternList.add(Pattern.compile(packageRegex).matcher("")); } /** * Add the name of a prefix to be matched by the screener. * All class files that appear to be in the package specified * by the prefix, or a more deeply nested package, should be matched. * * @param prefix name of the prefix to be matched */ public void addAllowedPrefix(String prefix) { if (prefix.endsWith(".")) { prefix = prefix.substring(0, prefix.length()-1); } if (DEBUG) System.out.println("Allowed prefix: " + prefix); String packageRegex = START+dotsToRegex(prefix)+SEP; if (DEBUG) System.out.println("Prefix regex: " + packageRegex); patternList.add(Pattern.compile(packageRegex).matcher("")); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IClassScreener#matches(java.lang.String) */ public boolean matches(String fileName) { // Special case: if no classes or packages have been defined, // then the screener matches all class files. if (patternList.isEmpty()) return true; if (DEBUG) System.out.println("Matching: " + fileName); // Scan through list of regexes for (Matcher matcher : patternList) { if (DEBUG) System.out.print("\tTrying [" + matcher.pattern()); matcher.reset(fileName); if (matcher.find()) { if (DEBUG) System.out.println("]: yes!"); return true; } if (DEBUG) System.out.println("]: no"); } return false; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.IClassScreener#vacuous() */ public boolean vacuous() { return patternList.isEmpty(); } } // vim:ts=4