This patch provides custom implementations for pattern matching of resources
that are much faster than the ant implementations they replace, in the common
cases. For uncommon cases, the implementations defer to Ant's pattern matching.
Patch by: amitmanjhi
Review by: jat (desk review)
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4824 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
index 6ab4a8f..3cfe57f 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -26,16 +26,14 @@
import com.google.gwt.dev.javac.JavaSourceOracle;
import com.google.gwt.dev.javac.impl.JavaSourceOracleImpl;
import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.resource.impl.DefaultFilters;
import com.google.gwt.dev.resource.impl.PathPrefix;
import com.google.gwt.dev.resource.impl.PathPrefixSet;
-import com.google.gwt.dev.resource.impl.ResourceFilter;
import com.google.gwt.dev.resource.impl.ResourceOracleImpl;
import com.google.gwt.dev.util.Empty;
import com.google.gwt.dev.util.PerfLogger;
import com.google.gwt.dev.util.Util;
-import org.apache.tools.ant.types.ZipScanner;
-
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -54,11 +52,6 @@
* XML for unit tests.
*/
public class ModuleDef implements PublicOracle {
- /**
- * Default to recursive inclusion of java files if no explicit include
- * directives are specified.
- */
- private static final String[] DEFAULT_SOURCE_FILE_INCLUDES_LIST = new String[] {"**/*.java"};
private static final Comparator<Map.Entry<String, ?>> REV_NAME_CMP = new Comparator<Map.Entry<String, ?>>() {
public int compare(Map.Entry<String, ?> entry1, Map.Entry<String, ?> entry2) {
@@ -123,9 +116,11 @@
private PathPrefixSet sourcePrefixSet = new PathPrefixSet();
private final Styles styles = new Styles();
+ private final DefaultFilters defaultFilters;
public ModuleDef(String name) {
this.name = name;
+ defaultFilters = new DefaultFilters();
}
public synchronized void addEntryPointTypeName(String typeName) {
@@ -155,15 +150,9 @@
if (lazyPublicOracle != null) {
throw new IllegalStateException("Already normalized");
}
-
- final ZipScanner scanner = getScanner(includeList, excludeList,
- defaultExcludes, caseSensitive);
-
- publicPrefixSet.add(new PathPrefix(publicPackage, new ResourceFilter() {
- public boolean allows(String path) {
- return scanner.match(path);
- }
- }, true));
+ publicPrefixSet.add(new PathPrefix(publicPackage,
+ defaultFilters.customResourceFilter(includeList, excludeList,
+ defaultExcludes, caseSensitive), true));
}
public void addSourcePackage(String sourcePackage, String[] includeList,
@@ -178,25 +167,9 @@
if (lazySourceOracle != null) {
throw new IllegalStateException("Already normalized");
}
-
- if (includeList.length == 0) {
- /*
- * If no includes list was provided then, use the default.
- */
- includeList = DEFAULT_SOURCE_FILE_INCLUDES_LIST;
- }
-
- final ZipScanner scanner = getScanner(includeList, excludeList,
- defaultExcludes, caseSensitive);
-
- ResourceFilter sourceFileFilter = new ResourceFilter() {
- public boolean allows(String path) {
- return path.endsWith(".java") && scanner.match(path);
- }
- };
-
- PathPrefix pathPrefix = new PathPrefix(sourcePackage, sourceFileFilter,
- isSuperSource);
+ PathPrefix pathPrefix = new PathPrefix(sourcePackage,
+ defaultFilters.customJavaFilter(includeList, excludeList,
+ defaultExcludes, caseSensitive), isSuperSource);
sourcePrefixSet.add(pathPrefix);
}
@@ -468,27 +441,4 @@
PerfLogger.end();
}
- private ZipScanner getScanner(String[] includeList, String[] excludeList,
- boolean defaultExcludes, boolean caseSensitive) {
- /*
- * Hijack Ant's ZipScanner to handle inclusions/exclusions exactly as Ant
- * does. We're only using its pattern-matching capabilities; the code path
- * I'm using never tries to hit the filesystem in Ant 1.6.5.
- */
- ZipScanner scanner = new ZipScanner();
- if (includeList.length > 0) {
- scanner.setIncludes(includeList);
- }
- if (excludeList.length > 0) {
- scanner.setExcludes(excludeList);
- }
- if (defaultExcludes) {
- scanner.addDefaultExcludes();
- }
- scanner.setCaseSensitive(caseSensitive);
- scanner.init();
-
- return scanner;
- }
-
}
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/DefaultFilters.java b/dev/core/src/com/google/gwt/dev/resource/impl/DefaultFilters.java
new file mode 100644
index 0000000..3ff40d8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/DefaultFilters.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.resource.impl;
+
+import org.apache.tools.ant.types.ZipScanner;
+
+import java.util.regex.Pattern;
+
+/**
+ * A singleton class that provides blazingly fast implementation of the default
+ * excludes of Ant's {@link org.apache.tools.ant.DirectoryScanner}, assuming
+ * case-sensitiveness.
+ *
+ * TODO: this class needs to be revisited, when Gwt's Ant is upgraded.
+ *
+ * Currently, we do not go to ant if (a) the filterList is empty, or (b) the
+ * filterList has "common" patterns. Exception: When pattern or path ends in
+ * '/', we defer to ant.
+ *
+ * TODO: This code could be made more general and cleaner by removing the
+ * dependency on Ant completely. All ant patterns could be compiled into
+ * reg-exps. That could also make the code faster. Plus, at several places,
+ * Ant's documentation seems to be incomplete. Instead, perhaps, we should
+ * specify our own rules for writing patterns.
+ */
+public class DefaultFilters {
+
+ private static final boolean IS_EXCLUDES = false;
+ private static final boolean IS_INCLUDES = true;
+ private static final boolean NOT_JAVA = false;
+ private static final boolean YES_JAVA = true;
+
+ static ZipScanner getScanner(String[] includeList, String[] excludeList,
+ boolean defaultExcludes, boolean caseSensitive) {
+ /*
+ * Hijack Ant's ZipScanner to handle inclusions/exclusions exactly as Ant
+ * does. We're only using its pattern-matching capabilities; the code path
+ * I'm using never tries to hit the filesystem in Ant 1.6.5.
+ */
+ ZipScanner scanner = new ZipScanner();
+ if (includeList.length > 0) {
+ scanner.setIncludes(includeList);
+ }
+ if (excludeList.length > 0) {
+ scanner.setExcludes(excludeList);
+ }
+ if (defaultExcludes) {
+ scanner.addDefaultExcludes();
+ }
+ scanner.setCaseSensitive(caseSensitive);
+ scanner.init();
+
+ return scanner;
+ }
+
+ /* used when defaultExcludes is true */
+ final ResourceFilter defaultResourceFilter = new ResourceFilter() {
+
+ public boolean allows(String path) {
+ return defaultAntIncludes.allows(path)
+ && !defaultExcludesPattern.matcher(path).matches();
+ }
+ };
+
+ /* used when defaultExcludes is true */
+ final ResourceFilter defaultJavaFilter = new ResourceFilter() {
+
+ public boolean allows(String path) {
+ return justJavaFilter.allows(path)
+ && !defaultJavaExcludesPattern.matcher(path).matches();
+ }
+
+ };
+ /* used when defaultExcludes is false */
+ final ResourceFilter justResourceFilter = new ResourceFilter() {
+
+ public boolean allows(String path) {
+ return defaultAntIncludes.allows(path);
+ }
+ };
+
+ /* used when defaultExcludes is false */
+ final ResourceFilter justJavaFilter = new ResourceFilter() {
+
+ public boolean allows(String path) {
+ return defaultAntIncludes.allows(path) && isJavaFile(path);
+ }
+ };
+
+ private final Pattern defaultExcludesPattern;
+ private final Pattern defaultJavaExcludesPattern;
+ // \w (word character), ., $, /, -, *, ~, #, %
+ private final Pattern antPattern = Pattern.compile("^[\\w\\.\\$/\\-\\*~#%]*$");
+
+ // accepts all but paths starting with '/'. Default include list is '**'
+ private final ResourceFilter defaultAntIncludes = new ResourceFilter() {
+ public boolean allows(String path) {
+ return path.charAt(0) != '/';
+ }
+ };
+
+ private final ResourceFilter rejectAll = new ResourceFilter() {
+ public boolean allows(String path) {
+ return false;
+ }
+ };
+
+ public DefaultFilters() {
+
+ /*
+ * list copied from {@link org.apache.tools.ant.DirectoryScanner}
+ */
+ String defaultExcludes[] = new String[] {
+ // Miscellaneous typical temporary files
+ "**/*~", "**/#*#", "**/.#*", "**/%*%", "**/._*",
+
+ // CVS
+ "**/CVS", "**/CVS/**",
+ // to not hit the weird formatting error.
+ "**/.cvsignore",
+
+ // SCCS
+ "**/SCCS", "**/SCCS/**",
+
+ // Visual SourceSafe
+ "**/vssver.scc",
+
+ // Subversion
+ "**/.svn", "**/.svn/**",
+
+ // Mac
+ "**/.DS_Store",};
+
+ defaultExcludesPattern = getPatternFromAntStrings(defaultExcludes);
+
+ String defaultExcludesJava[] = new String[] {
+ // Miscellaneous typical temporary files
+ "**/.#*", "**/._*",
+
+ // CVS
+ "**/CVS/**",
+
+ // SCCS
+ "**/SCCS/**",
+
+ // Subversion
+ "**/.svn/**",};
+ defaultJavaExcludesPattern = getPatternFromAntStrings(defaultExcludesJava);
+ }
+
+ public ResourceFilter customJavaFilter(String includeList[],
+ String excludeList[], boolean defaultExcludes, boolean caseSensitive) {
+ return getCustomFilter(includeList, excludeList, defaultExcludes,
+ caseSensitive, YES_JAVA);
+ }
+
+ public ResourceFilter customResourceFilter(String includeList[],
+ String excludeList[], boolean defaultExcludes, boolean caseSensitive) {
+
+ return getCustomFilter(includeList, excludeList, defaultExcludes,
+ caseSensitive, NOT_JAVA);
+ }
+
+ /**
+ * return a customResourceFiter that handles all the argument. If unable to
+ * create a customResourceFilter that handles the arguments, catchAll is used
+ * as the final ResourceFilter.
+ */
+ ResourceFilter customFilterWithCatchAll(final String includeList[],
+ final String excludeList[], final boolean defaultExcludes,
+ final ResourceFilter catchAll, final boolean isJava) {
+
+ assert includeList.length > 0 || excludeList.length > 0;
+
+ final ResourceFilter includeFilter = getFilterPart(includeList, IS_INCLUDES);
+ final ResourceFilter excludeFilter = getFilterPart(excludeList, IS_EXCLUDES);
+
+ if (includeFilter == null || excludeFilter == null) {
+ return catchAll;
+ }
+ // another common-case
+ ResourceFilter filter = new ResourceFilter() {
+ public boolean allows(String path) {
+ // do not handle the case when pattern ends in '/'
+ if (path.endsWith("/")) {
+ return catchAll.allows(path);
+ }
+ return isPathAllowedByDefaults(path, defaultExcludes, isJava)
+ && includeFilter.allows(path) && !excludeFilter.allows(path);
+ }
+
+ private boolean isPathAllowedByDefaults(String path,
+ boolean defaultExcludes, boolean isJava) {
+ if (defaultExcludes) {
+ return isJava ? !defaultJavaExcludesPattern.matcher(path).matches()
+ && isJavaFile(path)
+ : !defaultExcludesPattern.matcher(path).matches();
+ }
+ return isJava ? isJavaFile(path) : true;
+ }
+ };
+ return filter;
+ }
+
+ ResourceFilter getCustomFilter(final String includeList[],
+ final String excludeList[], final boolean defaultExcludes,
+ final boolean caseSensitive, final boolean isJava) {
+ if (includeList.length == 0 && excludeList.length == 0 && caseSensitive) {
+ // optimize for the common case.
+ return getMatchingDefaultFilter(defaultExcludes, isJava);
+ }
+
+ // don't create a catchAll in default cases
+ ResourceFilter catchAll = new ResourceFilter() {
+ ZipScanner scanner = getScanner(includeList, excludeList,
+ defaultExcludes, caseSensitive);
+
+ public boolean allows(String path) {
+ if (isJava) {
+ return isJavaFile(path) && scanner.match(path);
+ }
+ return scanner.match(path);
+ }
+ };
+
+ // for now, don't handle case sensitivity
+ if (!caseSensitive) {
+ return catchAll;
+ }
+ return customFilterWithCatchAll(includeList, excludeList, defaultExcludes,
+ catchAll, isJava);
+ }
+
+ ResourceFilter getFilterPart(final String list[], final boolean defaultValue) {
+ if (list.length == 0) {
+ return defaultValue ? defaultAntIncludes : rejectAll;
+ }
+
+ String patternStrings[] = new String[list.length];
+ int count = 0;
+ for (String antPatternString : list) {
+ String patternString = getPatternFromAntPattern(antPatternString);
+ if (patternString == null) {
+ return null;
+ }
+ patternStrings[count++] = patternString;
+ }
+
+ final Pattern pattern = getPatternFromStrings(patternStrings);
+ return new ResourceFilter() {
+ public boolean allows(String path) {
+ return pattern.matcher(path).matches();
+ }
+ };
+ }
+
+ /**
+ * Returns a pattern string that can be passed in Java Pattern.compile(..).
+ * For spec, see <a href="http://www.jajakarta.org/ant/ant-1.6.1/docs/ja/manual/api/org/apache/tools/ant/DirectoryScanner.html"
+ * >DirectoryScanner</a> From the spec: There is a special case regarding the
+ * use of File.separators at the beginning of the pattern and the string to
+ * match: When a pattern starts with a File.separator, the string to match
+ * must also start with a File.separator. When a pattern does not start with a
+ * File.separator, the string to match may not start with a File.separator.
+ *
+ * </p>
+ *
+ * TODO: This method could accept all ant patterns, but then all characters
+ * that have a special meaning in Java's regular expression would need to be
+ * escaped.
+ *
+ * @param antPatternString the ant pattern String.
+ * @return a pattern string that can be passed in Java's Pattern.compile(..),
+ * null if cannot process the pattern.
+ */
+ String getPatternFromAntPattern(String antPatternString) {
+ if (!antPattern.matcher(antPatternString).matches()) {
+ return null;
+ }
+ // do not handle patterns that have ***
+ if (antPatternString.indexOf("***") != -1) {
+ return null;
+ }
+ StringBuffer sb = new StringBuffer();
+ int length = antPatternString.length();
+ for (int i = 0; i < length; i++) {
+ char c = antPatternString.charAt(i);
+ switch (c) {
+ case '.':
+ sb.append("\\.");
+ break;
+ case '$':
+ sb.append("\\$");
+ break;
+ case '/':
+ // convert /** to (/[^/]*)* except when / is the first char.
+ if (i != 0 && i + 2 < length && antPatternString.charAt(i + 1) == '*'
+ && antPatternString.charAt(i + 2) == '*') {
+ sb.append("(/[^/]*)*");
+ i += 2; // handled 2 more chars than usual
+ } else {
+ /*
+ * TODO: handle patterns that end in /. For now, ant's matching seem
+ * inconsistent.
+ *
+ * ant pattern = testing/, path = testing/foo, result = true.
+ */
+ if (i == length - 1) {
+ return null;
+ }
+ sb.append(c);
+ }
+ break;
+ case '*':
+ // ** to .*
+ if (i + 1 < length && antPatternString.charAt(i + 1) == '*') {
+ if (i + 2 < length && antPatternString.charAt(i + 2) == '/') {
+ if (i == 0) {
+ /*
+ * When a pattern does not start with a File.separator, the
+ * string to match may not start with a File.separator.
+ */
+ sb.append("([^/]+/)*");
+ } else {
+ // convert **/ to ([^/]*/)*
+ sb.append("([^/]*/)*");
+ }
+ i += 2;
+ } else {
+ if (i == 0) {
+ /*
+ * When a pattern does not start with a File.separator, the
+ * string to match may not start with a File.separator.
+ */
+ sb.append("([^/].*)*");
+ } else {
+ sb.append(".*");
+ }
+ i++;
+ }
+ } else {
+ sb.append("[^/]*");
+ }
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Obtain the appropriate resourceFilter based on defaultExcludes and isJava
+ * values. Assumptions: caseSensitive = true,and the includesList and
+ * excludesList are empty
+ */
+ private ResourceFilter getMatchingDefaultFilter(boolean defaultExcludes,
+ boolean isJava) {
+ if (defaultExcludes) {
+ return isJava ? defaultJavaFilter : defaultResourceFilter;
+ }
+ return isJava ? justJavaFilter : justResourceFilter;
+ }
+
+ private Pattern getPatternFromAntStrings(String... antPatterns) {
+ String patternStrings[] = new String[antPatterns.length];
+ int count = 0;
+ for (String antPatternString : antPatterns) {
+ String patternString = getPatternFromAntPattern(antPatternString);
+ if (patternString == null) {
+ throw new RuntimeException("Unable to convert " + antPatternString
+ + " to java code");
+ }
+ patternStrings[count++] = patternString;
+ }
+ return getPatternFromStrings(patternStrings);
+ }
+
+ private Pattern getPatternFromStrings(String... patterns) {
+ StringBuffer entirePattern = new StringBuffer("^");
+ int length = patterns.length;
+ int count = 0;
+ for (String pattern : patterns) {
+ entirePattern.append("(" + pattern + ")");
+ if (count < length - 1) {
+ entirePattern.append("|");
+ }
+ count++;
+ }
+ entirePattern.append("$");
+ return Pattern.compile(entirePattern.toString());
+ }
+
+ private boolean isJavaFile(String path) {
+ return path.endsWith(".java");
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/DefaultFiltersTest.java b/dev/core/test/com/google/gwt/dev/resource/impl/DefaultFiltersTest.java
new file mode 100644
index 0000000..3059de3
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/DefaultFiltersTest.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.resource.impl;
+
+import junit.framework.TestCase;
+
+import org.apache.tools.ant.types.ZipScanner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * An implementation for DefaultExcludesFilterTest.
+ *
+ * Tests:
+ *
+ * 1. The filter conversion from ant to Java regex. Which cases can we handle,
+ * and confirm that we perform correctly in all cases.
+ *
+ * 2. checking the 4 defaultFilters, one for each combination of defaultExcludes
+ * and isJava.
+ *
+ * 3. Checking whether customFilter work correctly, both in presence and absence
+ * of Ant.
+ *
+ * TODO (amitmanjhi): clean up this test code.
+ */
+public class DefaultFiltersTest extends TestCase {
+
+ static class AdvancedPaths {
+ String baseIncluded[] = {
+ "Baz", "test/foo/Foo", "test/bar/Bar", "test/baz/Foo"};
+
+ String baseExcludedTesting[] = {"foo/testing/Baz"};
+ String baseExcludedExact[] = {"Foo", "Bar",};
+ String baseExcludedDoubleStar[] = {"fooz/Baz", "barz/hello/Baz"};
+ String baseExcluded[] = mergeArrays(baseExcludedTesting, baseExcludedExact,
+ baseExcludedDoubleStar);
+
+ void testAdvancedJavaPath(ResourceFilterString expected,
+ ResourceFilterString actual) {
+ for (String path : mergeArrays(baseIncluded, baseExcluded, getMiscPaths(
+ "testing", false).toArray(EMPTY_ARRAY),
+ getMiscPaths("a/bc/de", false).toArray(EMPTY_ARRAY))) {
+ assertEquals(path + ".java", expected, actual);
+ }
+ }
+
+ void testAdvancedJavaPathAnt(ResourceFilterString expected,
+ ResourceFilterString actual) {
+ for (String path : mergeArrays(baseIncluded, baseExcluded, getMiscPaths(
+ "testing", true).toArray(EMPTY_ARRAY),
+ getMiscPaths("a/bc/de", true).toArray(EMPTY_ARRAY))) {
+ assertEquals(excludedChars + path + excludedChars + javaSuffix,
+ expected, actual);
+ assertEquals(path + excludedChars + javaSuffix, expected, actual);
+ assertEquals(path + javaSuffix, expected, actual);
+ }
+ testAdvancedJavaPath(expected, actual);
+ new BasicPaths().testBasicJavaPath(expected, actual);
+ }
+
+ void testAdvancedPath(ResourceFilterString expected,
+ ResourceFilterString actual) {
+ for (String path : mergeArrays(baseIncluded, baseExcluded, getMiscPaths(
+ "testing", false).toArray(EMPTY_ARRAY),
+ getMiscPaths("a/bc/de", false).toArray(EMPTY_ARRAY))) {
+ assertEquals(path, expected, actual);
+ }
+ }
+
+ void testAdvancedPathAnt(ResourceFilterString expected,
+ ResourceFilterString actual) {
+ for (String path : mergeArrays(baseIncluded, baseExcluded, getMiscPaths(
+ "testing", true).toArray(EMPTY_ARRAY),
+ getMiscPaths("a/bc/de", true).toArray(EMPTY_ARRAY))) {
+ assertEquals(path, expected, actual);
+ assertEquals(path + excludedChars, expected, actual);
+ assertEquals(excludedChars + path + excludedChars, expected, actual);
+ }
+ testAdvancedPath(expected, actual);
+ new BasicPaths().testBasicPath(expected, actual);
+ }
+ }
+
+ static class BasicPaths {
+ String baseIncluded[] = {
+ "foo", "/foo", "foo/bar", "/foo/bar", "/foo/bar", "/foo$/$", "/foo-_",
+ "123FOO123", "cvs", "cvs/cvs/svn", ".foo_bar$", "foo/asvn"};
+ String baseExcluded[] = {"foo/CVS/bar", "foo/.svn/bar", "foo/SCCS/bar",};
+ String baseSuffixExcluded[] = {
+ "foo/.cvsignore", "foo/CVS", "foo/.svn", "foo/SCCS",
+ "foo/bar/vssver.scc", "/foo/bar/.DS_Store"};
+
+ void testBasicJavaPath(ResourceFilterString expected,
+ ResourceFilterString actual) {
+ for (String str : mergeArrays(baseIncluded, baseExcluded,
+ baseSuffixExcluded,
+ getMiscPaths("testing", true).toArray(EMPTY_ARRAY), getMiscPaths(
+ "a/bc/de", true).toArray(EMPTY_ARRAY))) {
+ assertEquals(str, expected, actual);
+ assertEquals(str + javaSuffix, expected, actual);
+ assertEquals(excludedChars + str, expected, actual);
+ assertEquals(excludedChars + str + excludedChars, expected, actual);
+ assertEquals(excludedChars + str + javaSuffix, expected, actual);
+ assertEquals(excludedChars + str + excludedChars + javaSuffix,
+ expected, actual);
+ }
+ }
+
+ void testBasicPath(ResourceFilterString expected,
+ ResourceFilterString actual) {
+ for (String str : mergeArrays(baseIncluded, baseExcluded,
+ baseSuffixExcluded,
+ getMiscPaths("testing", true).toArray(EMPTY_ARRAY), getMiscPaths(
+ "a/bc/de", true).toArray(EMPTY_ARRAY))) {
+ assertEquals(str, expected, actual);
+ assertEquals(excludedChars + str, expected, actual);
+ assertEquals(excludedChars + str + excludedChars, expected, actual);
+ }
+ }
+ }
+
+ /*
+ * Sole purpose of this class is to get a useful debug message.
+ */
+ private static class ResourceFilterString {
+ final ResourceFilter filter;
+ final String stringRepresentation;
+
+ ResourceFilterString(ResourceFilter filter, String stringRepresentation) {
+ this.filter = filter;
+ this.stringRepresentation = stringRepresentation;
+ }
+
+ }
+
+ private static final String EMPTY_ARRAY[] = new String[0];
+ private static final boolean DEFAULT_EXCLUDES = true;
+ private static final boolean DEFAULT_INCLUDES = false;
+ private static final boolean NOT_JAVA = false;
+ private static final boolean YES_JAVA = true;
+
+ private static final String mergedPatterns[] = {
+ "**/testing/**", "Foo", "Bar", "fooz/**", "barz/hello/**"};
+
+ // careful that the pattern is still permitted by ant.
+ private static final String excludedChars = "#~%*";
+
+ private static final String javaSuffix = ".java";
+
+ private static void assertEquals(String path, ResourceFilterString expected,
+ ResourceFilterString actual) {
+ boolean scanResult = expected.filter.allows(path);
+ assertEquals("String to be matched = " + path + ", actual filter = "
+ + actual.stringRepresentation + " should yied " + scanResult
+ + ", expected Filter = " + expected.stringRepresentation, scanResult,
+ actual.filter.allows(path));
+ }
+
+ private static List<String> getMiscPaths(String middleString,
+ boolean endInSlash) {
+ List<String> testPaths = new ArrayList<String>();
+ testPaths.addAll(Arrays.asList(new String[] {
+ "Foo", "Bar", "foo/xyz", "afoo/xyz-_$", "b/foo/x",
+ "foo/BarTestabc.java", "foo/xyz/BarTestabc.java", "a/b/testing/c/d",
+ "a/testing/b/c/FooBazBarTest.java", "a/testing/b/Foo/BazBar.java",
+ "a/testing/b/Foo$-_$Bar.class", "a/testing/b/Foo$/$.class"}));
+
+ String pathPrefixes[] = {"", "/", "foo/", "/foo/", "bar/foo/", "/bar/foo/"};
+ List<String> pathSuffixes = new ArrayList<String>();
+ if (endInSlash) {
+ // special handling because currently we don't handle paths that end in /
+ pathSuffixes.addAll(Arrays.asList(new String[] {
+ "", "/", "/foo", "/foo/", "/foo/bar", "/foo/bar/"}));
+ } else {
+ pathSuffixes.addAll(Arrays.asList(new String[] {"", "/foo", "/foo/bar",}));
+ }
+ for (String pathPrefix : pathPrefixes) {
+ for (String pathSuffix : pathSuffixes) {
+ testPaths.add(pathPrefix + middleString + pathSuffix);
+ }
+ }
+ return testPaths;
+ }
+
+ private static String[] mergeArrays(String[]... baseArrays) {
+ int count = 0;
+ for (String arrayElement[] : baseArrays) {
+ count += arrayElement.length;
+ }
+ String retArray[] = new String[count];
+ count = 0;
+ for (String arrayElement[] : baseArrays) {
+ for (String element : arrayElement) {
+ retArray[count++] = element;
+ }
+ }
+ return retArray;
+ }
+
+ public void testEmptyFilters() {
+ BasicPaths basicPaths = new BasicPaths();
+ DefaultFilters defaultFilters = new DefaultFilters();
+
+ // first arg: ant filter, second arg: our custom filter
+ basicPaths.testBasicPath(getAntFilter(EMPTY_ARRAY, EMPTY_ARRAY,
+ DEFAULT_EXCLUDES, NOT_JAVA, "antDefaultFilter"),
+ new ResourceFilterString(defaultFilters.defaultResourceFilter,
+ "defaultFilter"));
+ basicPaths.testBasicPath(getAntFilter(EMPTY_ARRAY, EMPTY_ARRAY,
+ DEFAULT_INCLUDES, NOT_JAVA, "antDefaultIncludesFilter"),
+ new ResourceFilterString(defaultFilters.justResourceFilter,
+ "defaultIncludesFilter"));
+
+ basicPaths.testBasicJavaPath(getAntFilter(EMPTY_ARRAY, EMPTY_ARRAY,
+ DEFAULT_EXCLUDES, YES_JAVA, "antDefaultJavaFilter"),
+ new ResourceFilterString(defaultFilters.defaultJavaFilter,
+ "defaultJavaFilter"));
+ basicPaths.testBasicJavaPath(getAntFilter(EMPTY_ARRAY, EMPTY_ARRAY,
+ DEFAULT_INCLUDES, YES_JAVA, "antJustJavaFilter"),
+ new ResourceFilterString(defaultFilters.justJavaFilter,
+ "justJavaFilter"));
+ }
+
+ /**
+ * (a) test that filters are correctly converted to non-null and null
+ * patterns. (b) test that filters match the same String.
+ */
+ public void testFilterConversion() {
+ List<String> nullFilters = Arrays.asList(new String[] {
+ "***/testing/**", "**/{/**", "**/}/**", "**/+/**", "**/testing/",
+ "**/testing/**/"});
+ List<String> okayFilters = new ArrayList<String>();
+ okayFilters.addAll(Arrays.asList(new String[] {
+ "**/#/**", "**/~/**", "Foo", "Bar", "foo/**", "foo/*Test*java",
+ "**/testing/**", "**/testing/**/Foo*Bar*.java",
+ "**/testing/**/Foo$*r.class",}));
+ String doubleStarPrefixes[] = {"", "/", "**/", "/**/", "foo**/", "/foo**/"};
+ // TODO: uncomment when we handle prefixes that end in /
+ // String doubleStarSuffixes[] = {"", "/", "/**", "/**/", "/**foo",
+ // "/**foo/"};
+ String doubleStarSuffixes[] = {"", "/**", "/**foo"};
+ String middleString = "testing";
+ for (String doubleStarPrefix : doubleStarPrefixes) {
+ for (String doubleStarSuffix : doubleStarSuffixes) {
+ okayFilters.add(doubleStarPrefix + middleString + doubleStarSuffix);
+ }
+ }
+
+ List<String> testPaths = getMiscPaths("testing", false);
+ /*
+ * TODO: investigate inconsistencies.
+ *
+ * ant pattern = testing/, path = testing/foo, result = true.
+ */
+ DefaultFilters filters = new DefaultFilters();
+ for (String filter : nullFilters) {
+ assertNull(filter + " conversion should be null",
+ filters.getPatternFromAntPattern(filter));
+ }
+
+ for (String filter : okayFilters) {
+ String pattern = filters.getPatternFromAntPattern(filter);
+ assertNotNull(filter + " conversion should be non-null", pattern);
+
+ ResourceFilterString antFilterString = getAntFilter(
+ new String[] {filter}, EMPTY_ARRAY, DEFAULT_EXCLUDES, NOT_JAVA, "ant_"
+ + filter);
+ ResourceFilterString customFilterString = new ResourceFilterString(
+ filters.customFilterWithCatchAll(new String[] {filter}, EMPTY_ARRAY,
+ true, null, NOT_JAVA), "custom_" + pattern);
+ for (String path : testPaths) {
+ assertEquals(path, antFilterString, customFilterString);
+ }
+ }
+ }
+
+ public void testFilterParts() {
+ AdvancedPaths advancedPaths = new AdvancedPaths();
+ ResourceFilter filter = null;
+
+ // everything except those starting with '/' should be included
+ filter = new DefaultFilters().getFilterPart(EMPTY_ARRAY, true);
+ advancedPaths.testAdvancedPath(getAntFilter(EMPTY_ARRAY, EMPTY_ARRAY,
+ DEFAULT_INCLUDES, NOT_JAVA, "antDefaultFilter"),
+ new ResourceFilterString(filter, "defaultFilter"));
+
+ // everything should be excluded
+ filter = new DefaultFilters().getFilterPart(EMPTY_ARRAY, false);
+ advancedPaths.testAdvancedPath(getAntFilter(new String[] {"a/1/2/3"},
+ new String[] {"**", "/**"}, DEFAULT_INCLUDES, NOT_JAVA,
+ "antDefaultFilter"), new ResourceFilterString(filter, "defaultFilter"));
+
+ filter = new DefaultFilters().getFilterPart(mergedPatterns, true);
+ advancedPaths.testAdvancedPath(getAntFilter(mergedPatterns, EMPTY_ARRAY,
+ DEFAULT_INCLUDES, NOT_JAVA, "antMergedPatterns"),
+ new ResourceFilterString(filter, "customMergedPatterns"));
+ }
+
+ // no ant, catchAll filter is null
+ public void testNonEmptyFilters() {
+ AdvancedPaths advancedPaths = new AdvancedPaths();
+
+ ResourceFilter filter = null;
+ // pass empty includeArray. Matches everything that is not excluded.
+ filter = getFilterWithoutCatchAll(EMPTY_ARRAY, mergedPatterns, NOT_JAVA);
+ advancedPaths.testAdvancedPath(getAntFilter(EMPTY_ARRAY, mergedPatterns,
+ DEFAULT_EXCLUDES, NOT_JAVA, "ant_emptyArray_mergedPatterns"),
+ new ResourceFilterString(filter, "custom_emptyArray_mergedPatterns"));
+
+ // pass empty excludeArray. Matches everything that is included.
+ filter = getFilterWithoutCatchAll(mergedPatterns, EMPTY_ARRAY, NOT_JAVA);
+ advancedPaths.testAdvancedPath(getAntFilter(mergedPatterns, EMPTY_ARRAY,
+ DEFAULT_EXCLUDES, NOT_JAVA, "ant_mergedPatterns_emptyArray"),
+ new ResourceFilterString(filter, "custom_mergedPatterns_emptyArray"));
+
+ // pass non-empty include and exclude array. Matches nothing
+ filter = getFilterWithoutCatchAll(mergedPatterns, mergedPatterns, NOT_JAVA);
+ advancedPaths.testAdvancedPath(
+ getAntFilter(mergedPatterns, mergedPatterns, DEFAULT_EXCLUDES, NOT_JAVA,
+ "ant_mergedPatterns_mergedPatterns"),
+ new ResourceFilterString(filter, "custom_mergedPatterns_mergedPatterns"));
+ }
+
+ // finish, catchAll filter is not-null
+ public void testNonEmptyFiltersAnt() {
+ AdvancedPaths advancedPaths = new AdvancedPaths();
+
+ ResourceFilter filter = null;
+ // pass empty includeArray. Matches everything that is not excluded.
+ filter = getFilterWithCatchAll(EMPTY_ARRAY, mergedPatterns, NOT_JAVA);
+ advancedPaths.testAdvancedPathAnt(getAntFilter(EMPTY_ARRAY, mergedPatterns,
+ DEFAULT_EXCLUDES, NOT_JAVA, "ant_emptyArray_mergedPatterns"),
+ new ResourceFilterString(filter, "custom_emptyArray_mergedPatterns"));
+
+ // pass empty excludeArray. Matches everything that is included.
+ filter = getFilterWithCatchAll(mergedPatterns, EMPTY_ARRAY, NOT_JAVA);
+ advancedPaths.testAdvancedPathAnt(getAntFilter(mergedPatterns, EMPTY_ARRAY,
+ DEFAULT_EXCLUDES, NOT_JAVA, "ant_emptyArray_mergedPatterns"),
+ new ResourceFilterString(filter, "custom_emptyArray_mergedPatterns"));
+
+ // pass non-empty include and exclude array. Matches nothing
+ filter = getFilterWithCatchAll(mergedPatterns, mergedPatterns, NOT_JAVA);
+ advancedPaths.testAdvancedPathAnt(getAntFilter(mergedPatterns,
+ mergedPatterns, DEFAULT_EXCLUDES, NOT_JAVA,
+ "ant_mergedPatterns_mergedPatterns"), new ResourceFilterString(filter,
+ "custom_mergedPatterns_mergedPatterns"));
+ }
+
+ // no ant, catchAll filter is null
+ public void testNonEmptyJavaFilters() {
+ AdvancedPaths advancedPaths = new AdvancedPaths();
+
+ String newMergedPatterns[] = new String[mergedPatterns.length];
+ for (int i = 0; i < mergedPatterns.length; i++) {
+ if (mergedPatterns[i].endsWith("*")) {
+ newMergedPatterns[i] = mergedPatterns[i];
+ } else {
+ newMergedPatterns[i] = mergedPatterns[i] + ".java";
+ }
+ }
+ ResourceFilter filter = null;
+ // pass empty includeArray. Means catch all
+ filter = getFilterWithoutCatchAll(EMPTY_ARRAY, newMergedPatterns, YES_JAVA);
+ advancedPaths.testAdvancedJavaPath(getAntFilter(EMPTY_ARRAY,
+ newMergedPatterns, DEFAULT_EXCLUDES, YES_JAVA,
+ "ant_emptyArray_newMergedPatterns"), new ResourceFilterString(filter,
+ "custom_emptyArray_newMergedPatterns"));
+
+ // pass empty excludeArray. Means catch only the pattern
+ filter = getFilterWithoutCatchAll(newMergedPatterns, EMPTY_ARRAY, YES_JAVA);
+ advancedPaths.testAdvancedJavaPath(getAntFilter(newMergedPatterns,
+ EMPTY_ARRAY, DEFAULT_EXCLUDES, YES_JAVA,
+ "ant_newMergedPatterns_emptyArray"), new ResourceFilterString(filter,
+ "custom_newMergedPatterns_emptyArray"));
+
+ // pass non-empty include and exclude array.
+ filter = getFilterWithoutCatchAll(newMergedPatterns, newMergedPatterns,
+ YES_JAVA);
+ advancedPaths.testAdvancedJavaPath(getAntFilter(newMergedPatterns,
+ newMergedPatterns, DEFAULT_EXCLUDES, YES_JAVA,
+ "ant_newMergedPatterns_newMergedPatterns"), new ResourceFilterString(
+ filter, "custom_newMergedPatterns_newMergedPatterns"));
+ }
+
+ public void testNonEmptyJavaFiltersAnt() {
+ AdvancedPaths advancedPaths = new AdvancedPaths();
+
+ String newMergedPatterns[] = new String[mergedPatterns.length];
+ for (int i = 0; i < mergedPatterns.length; i++) {
+ if (mergedPatterns[i].endsWith("*")) {
+ newMergedPatterns[i] = mergedPatterns[i];
+ } else {
+ newMergedPatterns[i] = mergedPatterns[i] + ".java";
+ }
+ }
+ ResourceFilter filter = null;
+ // pass empty includeArray. Means catch all
+ filter = getFilterWithCatchAll(EMPTY_ARRAY, newMergedPatterns, YES_JAVA);
+ advancedPaths.testAdvancedJavaPathAnt(getAntFilter(EMPTY_ARRAY,
+ newMergedPatterns, DEFAULT_EXCLUDES, YES_JAVA,
+ "ant_emptyArray_newMergedPatterns"), new ResourceFilterString(filter,
+ "custom_emptyArray_newMergedPatterns"));
+
+ // pass empty excludeArray. Means catch only the pattern
+ filter = getFilterWithCatchAll(newMergedPatterns, EMPTY_ARRAY, YES_JAVA);
+ advancedPaths.testAdvancedJavaPathAnt(getAntFilter(newMergedPatterns,
+ EMPTY_ARRAY, DEFAULT_EXCLUDES, YES_JAVA,
+ "ant_newMergedPatterns_emptyArray"), new ResourceFilterString(filter,
+ "custom_newMergedPatterns_emptyArray"));
+
+ // pass non-empty include and exclude array.
+ filter = getFilterWithCatchAll(newMergedPatterns, newMergedPatterns,
+ YES_JAVA);
+ advancedPaths.testAdvancedJavaPathAnt(getAntFilter(newMergedPatterns,
+ newMergedPatterns, DEFAULT_EXCLUDES, YES_JAVA,
+ "ant_newMergedPatterns_newMergedPatterns"), new ResourceFilterString(
+ filter, "custom_newMergedPatterns_newMergedPatterns"));
+ }
+
+ private ResourceFilterString getAntFilter(String includes[],
+ String excludes[], boolean defaultExcludes, final boolean isJava,
+ String tag) {
+ final ZipScanner scanner = DefaultFilters.getScanner(includes, excludes,
+ defaultExcludes, true);
+ return new ResourceFilterString(new ResourceFilter() {
+ public boolean allows(String path) {
+ if (isJava && !path.endsWith(".java")) {
+ return false;
+ }
+ return scanner.match(path);
+ }
+ }, tag != null ? tag : "includes: " + includes + ", excludes: " + excludes);
+ }
+
+ private ResourceFilter getFilterWithCatchAll(String includesList[],
+ String excludesList[], boolean isJava) {
+ if (isJava) {
+ return new DefaultFilters().customJavaFilter(includesList, excludesList,
+ true, true);
+ }
+ return new DefaultFilters().customResourceFilter(includesList,
+ excludesList, true, true);
+ }
+
+ // caseSensitive and excludeDefaults are set
+ private ResourceFilter getFilterWithoutCatchAll(String includesList[],
+ String excludesList[], boolean isJava) {
+ return new DefaultFilters().customFilterWithCatchAll(includesList,
+ excludesList, true, null, isJava);
+ }
+}