blob: 48398e65d6906f38a29cc4d3669194752d52c91a [file] [log] [blame]
/*
* 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 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,
String[] skipList, 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 || skipList.length > 0) {
String[] excludeOrSkip = concatenate(excludeList, skipList);
scanner.setExcludes(excludeOrSkip);
}
if (defaultExcludes) {
scanner.addDefaultExcludes();
}
scanner.setCaseSensitive(caseSensitive);
scanner.init();
return scanner;
}
private static String[] concatenate(String[] array1, String[] array2) {
String[] answer = new String[array1.length + array2.length];
int i = 0;
for (String entry : array1) {
answer[i++] = entry;
}
for (String entry : array2) {
answer[i++] = entry;
}
return answer;
}
/* 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[], String skipList[], boolean defaultExcludes,
boolean caseSensitive) {
return getCustomFilter(includeList, excludeList, skipList, defaultExcludes,
caseSensitive, YES_JAVA);
}
public ResourceFilter customResourceFilter(String includeList[],
String excludeList[], String[] skipList, boolean defaultExcludes,
boolean caseSensitive) {
return getCustomFilter(includeList, excludeList, skipList, 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 String skipList[],
final boolean defaultExcludes, final ResourceFilter catchAll,
final boolean isJava) {
assert includeList.length > 0 || excludeList.length > 0 || skipList.length > 0;
final ResourceFilter includeFilter = getFilterPart(includeList, IS_INCLUDES);
final ResourceFilter excludeFilter = getFilterPart(concatenate(excludeList, skipList),
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 String skipList[],
final boolean defaultExcludes, final boolean caseSensitive,
final boolean isJava) {
if (includeList.length == 0 && excludeList.length == 0 &&
skipList.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,
skipList, 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, skipList, 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;
}
if (antPatternString.endsWith("/")) {
/*
* From the DirectoryScanner.html spec: When a pattern ends with a '/' or
* '\', "**" is appended. if ant pattern = testing/, path = testing/foo,
* result = true.
*/
antPatternString = antPatternString + "**";
}
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 {
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");
}
}