blob: 0b73ad56dff8cdd0dec14d0e85e26b989b262451 [file] [log] [blame]
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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.intellij.lang.ant.dom;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.xml.reflect.DomAttributeChildDescription;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
/**
* @author Eugene Zhuravlev
* Date: May 2, 2007
*/
public class AntDomPattern extends AntDomRecursiveVisitor {
private static final List<Pattern> ourDefaultExcludes = new ArrayList<Pattern>(getDefaultExcludes(true));
private static final List<Pattern> ourCaseInsensitiveDefaultExcludes = new ArrayList<Pattern>(getDefaultExcludes(false));
private final boolean myCaseSensitive;
private static final String ourSeparatorPattern = Pattern.quote("/");
private static List<Pattern> getDefaultExcludes(final boolean caseSensitive) {
return Arrays.asList(
convertToRegexPattern("**/*~", caseSensitive),
convertToRegexPattern("**/#*#", caseSensitive),
convertToRegexPattern("**/.#*", caseSensitive),
convertToRegexPattern("**/%*%", caseSensitive),
convertToRegexPattern("**/._*", caseSensitive),
convertToRegexPattern("**/CVS", caseSensitive),
convertToRegexPattern("**/CVS/**", caseSensitive),
convertToRegexPattern("**/.cvsignore", caseSensitive),
convertToRegexPattern("**/SCCS", caseSensitive),
convertToRegexPattern("**/SCCS/**", caseSensitive),
convertToRegexPattern("**/vssver.scc", caseSensitive),
convertToRegexPattern("**/.svn", caseSensitive),
convertToRegexPattern("**/.svn/**", caseSensitive),
convertToRegexPattern("**/_svn", caseSensitive),
convertToRegexPattern("**/_svn/**", caseSensitive),
convertToRegexPattern("**/.DS_Store", caseSensitive)
);
}
private final List<Pattern> myIncludePatterns = new ArrayList<Pattern>();
private final List<Pattern> myExcludePatterns = new ArrayList<Pattern>();
private final List<PrefixItem[]> myCouldBeIncludedPatterns = new ArrayList<PrefixItem[]>();
AntDomPattern(final boolean caseSensitive) {
myCaseSensitive = caseSensitive;
}
public boolean hasIncludePatterns() {
return myIncludePatterns.size() > 0;
}
public void visitAntDomElement(AntDomElement element) {
// todo: add support to includefile and excludefile
if ("include".equals(element.getXmlElementName()) && !(element instanceof AntDomInclude)) {
if (isEnabled(element)) {
final String value = getAttributeValue(element, "name");
if (value != null) {
addIncludePattern(value);
}
}
}
else if ("exclude".equals(element.getXmlElementName())) {
if (isEnabled(element)) {
final String value = getAttributeValue(element, "name");
if (value != null) {
addExcludePattern(value);
}
}
}
else {
// todo: add support to includesfile and excludesfile
final String includeAttribs = getAttributeValue(element, "includes");
if (includeAttribs != null) {
addPatterns(true, includeAttribs);
}
final String excludeAttribs = getAttributeValue(element, "excludes");
if (excludeAttribs != null) {
addPatterns(false, excludeAttribs);
}
}
final AntDomElement referred = element.getRefId().getValue();
if (referred != null) {
referred.accept(this);
}
super.visitAntDomElement(element);
}
@Nullable
private static String getAttributeValue(AntDomElement element, final String attributeName) {
final DomAttributeChildDescription description = element.getGenericInfo().getAttributeChildDescription(attributeName);
if (description == null) {
return null;
}
return description.getDomAttributeValue(element).getStringValue();
}
public final void addExcludePattern(final String antPattern) {
myExcludePatterns.add(convertToRegexPattern(antPattern, myCaseSensitive));
}
public final void addIncludePattern(final String antPattern) {
myIncludePatterns.add(convertToRegexPattern(antPattern, myCaseSensitive));
String normalizedPattern = antPattern.endsWith("/") || antPattern.endsWith(File.separator)? antPattern.replace(File.separatorChar, '/') + "**" : antPattern.replace(File.separatorChar, '/');
if (normalizedPattern.startsWith("/") && normalizedPattern.length() > 1) {
// cut first leading slash if any
normalizedPattern = normalizedPattern.substring(1, normalizedPattern.length());
}
if (!normalizedPattern.startsWith("/")) {
final String[] patDirs = normalizedPattern.split(ourSeparatorPattern);
final PrefixItem[] items = new PrefixItem[patDirs.length];
for (int i = 0; i < patDirs.length; i++) {
items[i] = new PrefixItem(patDirs[i]);
}
myCouldBeIncludedPatterns.add(items);
}
}
public boolean acceptPath(final String relativePath) {
final String path = relativePath.replace('\\', '/');
boolean accepted = myIncludePatterns.size() == 0;
for (Pattern includePattern : myIncludePatterns) {
if (includePattern.matcher(path).matches()) {
accepted = true;
break;
}
}
if (accepted) {
for (Pattern excludePattern : myExcludePatterns) {
if (excludePattern.matcher(path).matches()) {
accepted = false;
break;
}
}
}
return accepted;
}
private static boolean isEnabled(AntDomElement element) {
final String ifProperty = getAttributeValue(element, "if");
if (ifProperty != null && PropertyResolver.resolve(element.getContextAntProject(), ifProperty, element).getFirst() == null) {
return false;
}
final String unlessProperty = getAttributeValue(element, "unless");
if (unlessProperty != null && PropertyResolver.resolve(element.getContextAntProject(), unlessProperty, element).getFirst() != null) {
return false;
}
return true;
}
private void addPatterns(final boolean addToIncludes, final String patternString) {
final StringTokenizer tokenizer = new StringTokenizer(patternString, ", \t", false);
while (tokenizer.hasMoreTokens()) {
final String pattern = tokenizer.nextToken();
if (pattern.length() > 0) {
if (addToIncludes) {
addIncludePattern(pattern);
}
else {
addExcludePattern(pattern);
}
}
}
}
private static Pattern convertToRegexPattern(@NonNls final String antPattern, final boolean caseSensitive) {
return Pattern.compile(FileUtil.convertAntToRegexp(antPattern), caseSensitive? 0 : Pattern.CASE_INSENSITIVE);
}
public static AntDomPattern create(AntDomElement element, final boolean honorDefaultExcludes, final boolean caseSensitive) {
final AntDomPattern antPattern = new AntDomPattern(caseSensitive);
element.accept(antPattern);
if (honorDefaultExcludes) {
antPattern.myExcludePatterns.addAll(caseSensitive? ourDefaultExcludes : ourCaseInsensitiveDefaultExcludes);
}
return antPattern;
}
// from org.apache.tools.ant.DirectoryScanner
protected static boolean matchPatternStart(PrefixItem[] patDirs, String str) {
final String[] strDirs = str.split(ourSeparatorPattern);
int patIdxStart = 0;
final int patIdxEnd = patDirs.length-1;
int strIdxStart = 0;
final int strIdxEnd = strDirs.length-1;
// up to first '**'
while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
final AntDomPattern.PrefixItem item = patDirs[patIdxStart];
if ("**".equals(item.getStrPattern())) {
break;
}
if (!item.getPattern().matcher(strDirs[strIdxStart]).matches()) {
return false;
}
patIdxStart++;
strIdxStart++;
}
if (strIdxStart > strIdxEnd) {
// String is exhausted
return true;
}
if (patIdxStart > patIdxEnd) {
// String not exhausted, but pattern is. Failure.
return false;
}
// pattern now holds ** while string is not exhausted
// this will generate false positives but we can live with that.
return true;
}
public boolean couldBeIncluded(String relativePath) {
if (myIncludePatterns.size() == 0) {
return true;
}
for (PrefixItem[] couldBeIncludedPattern : myCouldBeIncludedPatterns) {
if (matchPatternStart(couldBeIncludedPattern, relativePath)) {
return true;
}
}
return false;
}
private class PrefixItem {
private final String myStrPattern;
private Pattern myCompiledPattern;
public PrefixItem(String strPattern) {
myStrPattern = strPattern;
}
public String getStrPattern() {
return myStrPattern;
}
public Pattern getPattern() {
if (myCompiledPattern == null) {
myCompiledPattern = Pattern.compile(FileUtil.convertAntToRegexp(myStrPattern), myCaseSensitive ? 0 : Pattern.CASE_INSENSITIVE);
}
return myCompiledPattern;
}
}
}