| /* |
| * 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; |
| } |
| } |
| } |