/*
* 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 org.napile.idea.thermit.dom;
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;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.xml.reflect.DomAttributeChildDescription;
/**
* @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.thermit.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;
}
}
}