KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > spi > project > support > ant > PathMatcher


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.spi.project.support.ant;
21
22 import java.io.File JavaDoc;
23 import java.util.Collections JavaDoc;
24 import java.util.HashSet JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.Set JavaDoc;
27 import java.util.SortedSet JavaDoc;
28 import java.util.StringTokenizer JavaDoc;
29 import java.util.TreeSet JavaDoc;
30 import java.util.regex.Matcher JavaDoc;
31 import java.util.regex.Pattern JavaDoc;
32
33 /**
34  * Utility to match Ant-style file patterns with extended glob syntax.
35  * <p>
36  * A path matcher can be given an optional list of include patterns,
37  * and an optional list of exclude patterns. A given file path
38  * matches the pattern if it is matched by at least one include
39  * pattern (or there is a null includes list), and is not matched by
40  * any of the exclude patterns (if this list is not null).
41  * </p>
42  * <p>
43  * The format is based on Ant patterns. Some details:
44  * </p>
45  * <ul>
46  * <li>A file path to be matched must be a <samp>/</samp>-separated
47  * relative path from an unspecified base directory. A path representing
48  * a folder must end in <samp>/</samp>, except for the path representing
49  * the root folder, which is the empty string. Thus, the full path to a file
50  * is always the simple concatenation of the path to its containing folder,
51  * and the file's basename.
52  * <li>An include or exclude list, if not null, is a list of nonempty patterns separated
53  * by spaces and/or commas. It may be an empty list; this is equivalent to null in the
54  * case of excludes, but in the case of includes means that nothing matches.
55  * <li>A pattern may use either <samp>/</samp> or <samp>\</samp> as a path separator
56  * interchangeably.
57  * <li>Most characters in a pattern match literally, and match a complete file path.
58  * A folder path ends in <samp>/</samp>, so the pattern <samp>foo</samp> will <em>not</em>
59  * match a folder named <samp>foo</samp>.
60  * <li><samp>*</samp> in a pattern matches zero or more characters within a path component
61  * (i.e. not including <samp>/</samp>).
62  * <li><samp>**</samp> matches zero or more complete path components. It must be preceded
63  * by a slash (or be at the beginning of the pattern) and be followed by a slash (or be at
64  * the end of the pattern).
65  * <li><samp>foo/</samp> is treated the same as <samp>foo/**</samp> and matches the whole
66  * tree rooted at the folder <samp>foo</samp>.
67  * <li><samp>/**<!---->/</samp> can match just a single <samp>/</samp>. <samp>**<!---->/</samp>
68  * and <samp>/**</samp> can match the empty string at path boundaries.
69  * </ul>
70  * <p>
71  * Some example patterns:
72  * </p>
73  * <dl>
74  * <dt><samp>foo/bar/</samp>
75  * <dd>The folder <samp>foo/bar</samp> and anything inside it.
76  * <dt><samp>foo/bar/baz</samp>
77  * <dd>The file <samp>foo/bar/baz</samp>.
78  * <dt><samp>**<!---->/foo/</samp>
79  * <dd>Any folder named <samp>foo</samp> and anything inside it.
80  * <dt><samp>**<!---->/*.java</samp>
81  * <dd>Any Java source file (even in the default package).
82  * </dl>
83  * @since org.netbeans.modules.project.ant/1 1.15
84  * @author Jesse Glick
85  */

86 public final class PathMatcher {
87
88     private final String JavaDoc includes, excludes;
89     private final Pattern JavaDoc includePattern, excludePattern;
90     private final File JavaDoc base;
91     private final Set JavaDoc<String JavaDoc> knownIncludes;
92
93     /**
94      * Create a path matching object.
95      * It is faster to create one matcher and call {@link #matches} multiple times
96      * than to recreate a matcher for each query.
97      * @param includes a list of paths to match, or null to match everything by default
98      * @param excludes a list of paths to not match, or null
99      * @param base a base directory to scan for known include roots (see {@link #findIncludedRoots}), or null if unknown
100      */

101     public PathMatcher(String JavaDoc includes, String JavaDoc excludes, File JavaDoc base) {
102         this.includes = includes;
103         this.excludes = excludes;
104         includePattern = computePattern(includes);
105         excludePattern = computePattern(excludes);
106         this.base = base;
107         knownIncludes = computeKnownIncludes();
108     }
109
110     private Pattern JavaDoc computePattern(String JavaDoc patterns) {
111         if (patterns == null) {
112             return null;
113         }
114         StringBuilder JavaDoc rx = new StringBuilder JavaDoc();
115         StringTokenizer JavaDoc patternstok = new StringTokenizer JavaDoc(patterns, ", "); // NOI18N
116
if (!patternstok.hasMoreTokens()) {
117             return Pattern.compile("<cannot match>"); // NOI18N
118
}
119         while (patternstok.hasMoreTokens()) {
120             String JavaDoc pattern = patternstok.nextToken().replace('\\', '/');
121             if (rx.length() > 0) {
122                 rx.append('|');
123             }
124             if (pattern.endsWith("/")) { // NOI18N
125
pattern += "**"; // NOI18N
126
}
127             if (pattern.equals("**")) { // NOI18N
128
rx.append(".*"); // NOI18N
129
break;
130             }
131             Matcher JavaDoc m = Pattern.compile("/\\*\\*/|/\\*\\*|\\*\\*/|\\*|/|[^*/]+").matcher(pattern); // NOI18N
132
while (m.find()) {
133                 String JavaDoc t = m.group();
134                 if (t.equals("/**")) {
135                     rx.append("/.*");
136                 } else if (t.equals("**/")) {
137                     rx.append("(.*/|)");
138                 } else if (t.equals("/**/")) {
139                     rx.append("(/.*/|/)");
140                 } else if (t.equals("*")) {
141                     rx.append("[^/]*");
142                 } else {
143                     rx.append(Pattern.quote(t));
144                 }
145             }
146         }
147         String JavaDoc rxs = rx.toString();
148         return Pattern.compile(rxs);
149     }
150
151     /**
152      * Check whether a given path matches some includes (if not null) and no excludes.
153      * @param path a relative file path as described in class Javadoc
154      * @param useKnownIncludes true to also match in case this path is a parent of some known included root
155      * @return true for a match
156      */

157     public boolean matches(String JavaDoc path, boolean useKnownIncludes) {
158         if (path == null) {
159             throw new NullPointerException JavaDoc();
160         }
161         if (excludePattern != null && excludePattern.matcher(path).matches()) {
162             return false;
163         }
164         if (includePattern != null) {
165             if (includePattern.matcher(path).matches()) {
166                 return true;
167             }
168             if (useKnownIncludes && (path.length() == 0 || path.endsWith("/"))) {
169                 for (String JavaDoc incl : knownIncludes) {
170                     if (incl.startsWith(path)) {
171                         return true;
172                     }
173                 }
174             }
175             return false;
176         } else {
177             return true;
178         }
179     }
180
181     /**
182      * Find folders which match although their parent folders do not; or folders
183      * which do not match but which contain files which do.
184      * <ul>
185      * <li>Wildcard-free folder include paths, such as <samp>foo/bar/</samp> (or the
186      * equivalent <samp>foo/bar/**</samp>), are returned directly if they are not excluded.
187      * <li>Wildcard-using paths trigger a scan of the provided root directory.
188      * Any actual files or folders found beneath that root which {@link #matches match}
189      * are noted, and their minimal paths are returned.
190      * <li>If a file matches but its containing folder does not, and the file exists,
191      * the folder is listed as an include root.
192      * <li>If this matcher has a null includes list, just the root folder is returned.
193      * </ul>
194      * @return a set of minimal included folders
195      */

196     public Set JavaDoc<File JavaDoc> findIncludedRoots() throws IllegalArgumentException JavaDoc {
197         if (includes == null) {
198             return Collections.singleton(base);
199         }
200         Set JavaDoc<File JavaDoc> roots = new HashSet JavaDoc<File JavaDoc>();
201         if (base != null) {
202             for (String JavaDoc incl : knownIncludes) {
203                 roots.add(new File JavaDoc(base, incl.replace('/', File.separatorChar)));
204             }
205         }
206         return roots;
207     }
208
209     private Set JavaDoc<String JavaDoc> computeKnownIncludes() {
210         if (includes == null) {
211             return Collections.emptySet();
212         }
213         SortedSet JavaDoc<String JavaDoc> roots = new TreeSet JavaDoc<String JavaDoc>();
214         StringTokenizer JavaDoc patternstok = new StringTokenizer JavaDoc(includes, ", "); // NOI18N
215
boolean search = false;
216         while (patternstok.hasMoreTokens()) {
217             String JavaDoc pattern = patternstok.nextToken().replace('\\', '/').replaceFirst("/\\*\\*$", "/"); // NOI18N
218
if (pattern.equals("**")) { // NOI18N
219
roots.add(""); // NOI18N
220
} else if (pattern.indexOf('*') == -1 && pattern.endsWith("/")) { // NOI18N
221
// Optimize in case all includes are wildcard-free paths from root.
222
if (excludePattern == null || !excludePattern.matcher(pattern).matches()) {
223                     String JavaDoc parent = pattern.substring(0, pattern.lastIndexOf('/', pattern.length() - 2) + 1);
224                     if (!includePattern.matcher(parent).matches()) {
225                         roots.add(pattern);
226                     }
227                 }
228             } else if (base != null) {
229                 // Optimization failed. Need to search for actual matches.
230
search = true;
231             }
232         }
233         // Verify that roots really exist, even if they are wilcard-free.
234
if (base != null && base.isDirectory()) {
235             Iterator JavaDoc<String JavaDoc> it = roots.iterator();
236             while (it.hasNext()) {
237                 if (!new File JavaDoc(base, it.next().replace('/', File.separatorChar)).isDirectory()) {
238                     it.remove();
239                 }
240             }
241         }
242         if (search) {
243             // Find what dirs inside root actually match the path, so we known which parents to include later.
244
// XXX note that this fails to listen to file creations & deletions inside the root so the result
245
// can become inaccurate. Not clear how to efficiently solve that.
246
findMatches(base, "", roots);
247         }
248         return roots;
249     }
250
251     private void findMatches(File JavaDoc dir, String JavaDoc prefix, Set JavaDoc<String JavaDoc> roots) {
252         assert prefix.length() == 0 || prefix.endsWith("/");
253         assert includes != null;
254         String JavaDoc[] childnames = dir.list();
255         if (childnames == null) {
256             return;
257         }
258         for (String JavaDoc childname : childnames) {
259             File JavaDoc child = new File JavaDoc(dir, childname);
260             boolean isdir = child.isDirectory();
261             String JavaDoc path = prefix + childname;
262             if (isdir) {
263                 path += "/"; // NOI18N
264
}
265             if (excludePattern != null && excludePattern.matcher(path).matches()) {
266                 continue; // prune
267
}
268             if (includePattern.matcher(path).matches()) {
269                 if (isdir) {
270                     roots.add(path);
271                 } else {
272                     roots.add(prefix);
273                 }
274             } else if (isdir) {
275                 findMatches(child, path, roots);
276             }
277         }
278     }
279
280     @Override JavaDoc
281     public String JavaDoc toString() {
282         return "PathMatcher[includes=" + includes + ",excludes=" + excludes + "]"; // NOI18N
283
}
284
285 }
286
Popular Tags