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}