001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 The ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package org.conqat.lib.commons.filesystem; 018 019import java.util.regex.Pattern; 020import java.util.regex.PatternSyntaxException; 021 022import org.conqat.engine.core.core.ConQATException; 023import org.conqat.lib.commons.string.StringUtils; 024 025/** 026 * Utility methods for dealing with Ant pattern as defined at 027 * http://ant.apache.org/manual/dirtasks.html#patterns 028 * 029 * We implement a special version where a trailing '.' can be used to only match 030 * files without file extension (i.e. file names without dot). 031 * 032 * This class is copied in AntPatternUtils.js and adapted to JavaScript. If any 033 * of the code/logic in this class is changed, please make sure to change it in 034 * the JS code as well. 035 */ 036public class AntPatternUtils { 037 038 /** Converts an ANT pattern to a regex pattern. */ 039 public static Pattern convertPattern(String antPattern, boolean caseSensitive) throws PatternSyntaxException { 040 041 antPattern = normalizePattern(antPattern); 042 043 // ant specialty: trailing /** is optional 044 // for example **/e*/** will also match foo/entry 045 boolean addTrailAll = false; 046 if (antPattern.endsWith("/**")) { 047 addTrailAll = true; 048 antPattern = StringUtils.stripSuffix(antPattern, "/**"); 049 } 050 051 StringBuilder patternBuilder = new StringBuilder(); 052 convertPlainPattern(antPattern, patternBuilder); 053 054 if (addTrailAll) { 055 // the tail pattern is optional (i.e. we do not require the '/'), 056 // but the "**" is only in effect if the '/' occurs 057 patternBuilder.append("(/.*)?"); 058 } 059 060 return compileRegex(patternBuilder.toString(), antPattern, caseSensitive); 061 } 062 063 /** Compiles the given regex. */ 064 private static Pattern compileRegex(String regex, String antPattern, boolean caseSensitive) { 065 try { 066 return Pattern.compile(regex, determineRegexFlags(caseSensitive)); 067 } catch (PatternSyntaxException e) { 068 // make pattern syntax exception more understandable 069 throw new PatternSyntaxException( 070 "Error compiling ANT pattern '" + antPattern + "' to regular expression. " + e.getDescription(), 071 e.getPattern(), e.getIndex()); 072 } 073 } 074 075 /** Returns the flags to be used for the regular expression. */ 076 private static int determineRegexFlags(boolean caseSensitive) { 077 // Use DOTALL flag, as on Unix the file names can contain line breaks 078 int flags = Pattern.DOTALL; 079 if (!caseSensitive) { 080 flags |= Pattern.CASE_INSENSITIVE; 081 } 082 return flags; 083 } 084 085 /** 086 * Normalizes the given pattern by ensuring forward slashes and mapping trailing 087 * slash to '/**'. 088 */ 089 private static String normalizePattern(String antPattern) { 090 antPattern = FileSystemUtils.normalizeSeparators(antPattern); 091 092 // ant pattern syntax: if a pattern ends with /, then ** is 093 // appended 094 if (antPattern.endsWith("/")) { 095 antPattern += "**"; 096 } 097 return antPattern; 098 } 099 100 /** 101 * Converts a plain ANT pattern to a regular expression, by replacing special 102 * characters, such as '?', '*', and '**'. The created pattern is appended to 103 * the given {@link StringBuilder}. The pattern must be plain, i.e. all ANT 104 * specialties, such as trailing double stars have to be dealt with beforehand. 105 */ 106 private static void convertPlainPattern(String antPattern, StringBuilder patternBuilder) { 107 for (int i = 0; i < antPattern.length(); ++i) { 108 char c = antPattern.charAt(i); 109 if (c == '?') { 110 patternBuilder.append("[^/]"); 111 } else if (c != '*') { 112 patternBuilder.append(Pattern.quote(Character.toString(c))); 113 } else { 114 i = convertStarSequence(antPattern, patternBuilder, i); 115 } 116 } 117 } 118 119 /** 120 * Converts a sequence of the ant pattern starting with a star at the given 121 * index. Appends the pattern fragment the the builder and returns the index to 122 * continue scanning from. 123 */ 124 private static int convertStarSequence(String antPattern, StringBuilder patternBuilder, int index) { 125 boolean doubleStar = isCharAt(antPattern, index + 1, '*'); 126 if (doubleStar) { 127 // if the double star is followed by a slash, the entire 128 // group becomes optional, as we want "**/foo" to also 129 // match a top-level "foo" 130 boolean doubleStarSlash = isCharAt(antPattern, index + 2, '/'); 131 if (doubleStarSlash) { 132 patternBuilder.append("(.*/)?"); 133 return index + 2; 134 } 135 136 boolean doubleStarDot = isCharAtBeforeSlashOrEnd(antPattern, index + 2, '.'); 137 if (doubleStarDot) { 138 patternBuilder.append("(.*/)?[^/.]*[.]?"); 139 return index + 2; 140 } 141 142 patternBuilder.append(".*"); 143 return index + 1; 144 } 145 146 boolean starDot = isCharAtBeforeSlashOrEnd(antPattern, index + 1, '.'); 147 if (starDot) { 148 patternBuilder.append("[^/.]*[.]?"); 149 return index + 1; 150 } 151 152 patternBuilder.append("[^/]*"); 153 return index; 154 } 155 156 /** 157 * Returns whether the given position exists in the string and equals the given 158 * character, and the given character is either at the end or right before a 159 * slash. 160 */ 161 private static boolean isCharAtBeforeSlashOrEnd(String s, int position, char character) { 162 return isCharAt(s, position, character) && (position + 1 == s.length() || isCharAt(s, position + 1, '/')); 163 } 164 165 /** 166 * Returns whether the given position exists in the string and equals the given 167 * character. 168 */ 169 private static boolean isCharAt(String s, int position, char character) { 170 return position < s.length() && s.charAt(position) == character; 171 } 172 173 /** 174 * Converts an ANT pattern to a regex pattern. This catches a potential pattern 175 * syntax exception and wraps it into a {@link ConQATException}. 176 */ 177 public static Pattern convertPatternSafe(String antPattern, boolean caseSensitive) throws ConQATException { 178 try { 179 return convertPattern(antPattern, caseSensitive); 180 } catch (PatternSyntaxException e) { 181 throw new ConQATException(e.getMessage(), e); 182 } 183 } 184}