KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jruby > util > Glob


1 /***** BEGIN LICENSE BLOCK *****
2  * Version: CPL 1.0/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Common Public
5  * License Version 1.0 (the "License"); you may not use this file
6  * except in compliance with the License. You may obtain a copy of
7  * the License at http://www.eclipse.org/legal/cpl-v10.html
8  *
9  * Software distributed under the License is distributed on an "AS
10  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11  * implied. See the License for the specific language governing
12  * rights and limitations under the License.
13  *
14  * Copyright (C) 2002 Jan Arne Petersen <jpetersen@uni-bonn.de>
15  * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
16  * Copyright (C) 2004-2005 Thomas E Enebo <enebo@acm.org>
17  * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
18  * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
19  * Copyright (C) 2006 Ola Bini <ola.bini@ki.se>
20  * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
21  *
22  * Alternatively, the contents of this file may be used under the terms of
23  * either of the GNU General Public License Version 2 or later (the "GPL"),
24  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
25  * in which case the provisions of the GPL or the LGPL are applicable instead
26  * of those above. If you wish to allow use of your version of this file only
27  * under the terms of either the GPL or the LGPL, and not to allow others to
28  * use your version of this file under the terms of the CPL, indicate your
29  * decision by deleting the provisions above and replace them with the notice
30  * and other provisions required by the GPL or the LGPL. If you do not delete
31  * the provisions above, a recipient may use your version of this file under
32  * the terms of any one of the CPL, the GPL or the LGPL.
33  ***** END LICENSE BLOCK *****/

34 package org.jruby.util;
35
36 import java.io.File JavaDoc;
37 import java.io.FileFilter JavaDoc;
38 import java.io.IOException JavaDoc;
39 import java.util.ArrayList JavaDoc;
40 import java.util.Collection JavaDoc;
41 import java.util.Iterator JavaDoc;
42 import java.util.List JavaDoc;
43 import java.util.TreeSet JavaDoc;
44 import java.util.regex.Matcher JavaDoc;
45 import java.util.regex.Pattern JavaDoc;
46 import java.util.regex.PatternSyntaxException JavaDoc;
47
48 // TODO: Literal escaping does not work because on windows (See bug #1280905)
49
public class Glob {
50     private final List JavaDoc patterns;
51     // TODO: If '{' or '}' is just a literal this is broken.
52
private static final Pattern JavaDoc BRACE_PATTERN = Pattern.compile("(.*)\\{([^\\{\\}]*)\\}(.*)");
53
54     public Glob(String JavaDoc cwd, String JavaDoc pattern) {
55         List JavaDoc expansion = new ArrayList JavaDoc();
56         expansion.add(pattern);
57
58         // We pre-expand the pattern list into multiple patterns (see GlobPattern for more info).
59
expansion = splitPatternBraces(expansion);
60         
61         cwd = canonicalize(cwd);
62         
63         int size = expansion.size();
64         patterns = new ArrayList JavaDoc(size);
65         for (int i = 0; i < size; i++) {
66             String JavaDoc newPattern = (String JavaDoc) expansion.get(i);
67             
68             patterns.add(i, new GlobPattern(cwd, newPattern));
69         }
70     }
71     
72     private static String JavaDoc canonicalize(String JavaDoc path) {
73         try {
74             return new NormalizedFile(path).getCanonicalPath();
75         } catch (IOException JavaDoc e) {
76             return path;
77         }
78     }
79     
80     private static List JavaDoc splitPatternBraces(List JavaDoc dirs) {
81         // Remove current dir and add all expanded to bottom of list.
82
// Our test condition is dynamic (dirs.size() each iteration), so this is ok.
83
for (int i = 0; i < dirs.size(); i++) {
84             String JavaDoc fragment = (String JavaDoc) dirs.get(i);
85             Matcher JavaDoc matcher = BRACE_PATTERN.matcher(fragment);
86
87             // Found a set of braces
88
if (matcher.find()) {
89                 dirs.remove(i);
90                 String JavaDoc beforeBrace = matcher.group(1);
91                 String JavaDoc[] subElementList = matcher.group(2).split(",");
92                 String JavaDoc afterBrace = matcher.group(3);
93
94                 for (int j = 0; j < subElementList.length; j++) {
95                     dirs.add(beforeBrace + subElementList[j] + afterBrace);
96                 }
97             }
98         }
99         
100         return dirs;
101     }
102     
103     /**
104      * Get file objects for glob; made private to prevent it being used directly in the future
105      */

106     private void getFiles() {
107         // we always use / to normalize all file paths internally
108
String JavaDoc pathSplitter = "/";
109         
110         for (Iterator JavaDoc iter = patterns.iterator(); iter.hasNext();) {
111             GlobPattern globPattern = (GlobPattern) iter.next();
112             String JavaDoc[] dirs = globPattern.getPattern().split(pathSplitter);
113             NormalizedFile root = new NormalizedFile(dirs[0]);
114             int idx = 1;
115             if (glob2Regexp(dirs[0]) != null) {
116                 root = new NormalizedFile(".");
117                 idx = 0;
118             }
119             for (int size = dirs.length; idx < size; idx++) {
120                 if (glob2Regexp(dirs[idx]) == null) {
121                     root = new NormalizedFile(root, dirs[idx]);
122                 } else {
123                     break;
124                 }
125             }
126             ArrayList JavaDoc matchingFiles = new ArrayList JavaDoc();
127
128             if (idx == dirs.length) {
129                 if (root.exists()) {
130                     matchingFiles.add(root);
131                 }
132
133                 globPattern.setMatchedFiles(matchingFiles);
134                 continue;
135             }
136             
137             matchingFiles.add(root);
138             for (int length = dirs.length; idx < length; idx++) {
139                 ArrayList JavaDoc currentMatchingFiles = new ArrayList JavaDoc();
140                 for (int i = 0, size = matchingFiles.size(); i < size; i++) {
141                     boolean isDirectory = idx + 1 != length;
142                     String JavaDoc pattern = dirs[idx];
143                     NormalizedFile parent = (NormalizedFile) matchingFiles.get(i);
144                     currentMatchingFiles.addAll(getMatchingFiles(parent, pattern, isDirectory));
145                 }
146                 matchingFiles = currentMatchingFiles;
147             }
148             globPattern.setMatchedFiles(matchingFiles);
149         }
150     }
151     
152     private static Collection JavaDoc getMatchingFiles(final NormalizedFile parent, final String JavaDoc pattern, final boolean isDirectory) {
153         String JavaDoc expandedPattern = glob2Regexp(pattern);
154         if (expandedPattern == null) expandedPattern = pattern;
155         
156         final Pattern JavaDoc p = Pattern.compile(expandedPattern);
157         
158         final boolean firstDot = pattern.length()>0 && pattern.charAt(0) == '.';
159
160         FileFilter JavaDoc filter = new FileFilter JavaDoc() {
161             public boolean accept(File JavaDoc pathname) {
162                 String JavaDoc n = pathname.getName();
163                 return (pathname.isDirectory() || !isDirectory) && p.matcher(n).matches() && (firstDot || (n.length()==0 || n.charAt(0) != '.'));
164             }
165         };
166         
167         NormalizedFile[] matchArray = (NormalizedFile[])parent.listFiles(filter);
168         Collection JavaDoc matchingFiles = new ArrayList JavaDoc();
169
170         if (matchArray != null) {
171             for (int i = 0; i < matchArray.length; i++) {
172                 matchingFiles.add(matchArray[i]);
173             
174                 if (pattern.equals("**")) {
175                     // recurse into dirs
176
if (matchArray[i].isDirectory()) {
177                         matchingFiles.addAll(getMatchingFiles(matchArray[i], pattern, isDirectory));
178                     }
179                 }
180             }
181         }
182         
183         if("**".equals(pattern)) {
184             matchingFiles.add(parent);
185         }
186         
187         return matchingFiles;
188     }
189     
190     public String JavaDoc[] getNames() {
191         try {
192             getFiles();
193         } catch (PatternSyntaxException JavaDoc e) {
194             // This can happen if someone does Dir.glob("{") or similiar.
195
return new String JavaDoc[] {};
196         }
197         
198         Collection JavaDoc allMatchedNames = new TreeSet JavaDoc();
199         for (Iterator JavaDoc iter = patterns.iterator(); iter.hasNext();) {
200             GlobPattern pattern = (GlobPattern) iter.next();
201             
202             allMatchedNames.addAll(pattern.getMatchedFiles());
203         }
204         
205         return (String JavaDoc[]) allMatchedNames.toArray(new String JavaDoc[allMatchedNames.size()]);
206     }
207     
208     /**
209      * Converts a glob pattern into a normal regexp pattern.
210      * <pre>* =&gt; .*
211      * ? =&gt; .
212      * [...] =&gt; [...]
213      * . + ( ) =&gt; \. \+ \( \)</pre>
214      */

215     private static String JavaDoc glob2Regexp(String JavaDoc s) {
216         StringBuffer JavaDoc t = new StringBuffer JavaDoc(s.length());
217         boolean pattern = false;
218         int mode = 0;
219         boolean escape = false;
220         for (int i = 0; i < s.length(); i++) {
221             char c = s.charAt(i);
222             if (c == '\\') {
223                 escape = true;
224                 continue;
225             }
226             if (escape) {
227                 t.append(c);
228                 escape = false;
229                 continue;
230             }
231             switch (mode) {
232             case 0: //normal
233
switch (c) {
234                 case '*':
235                     pattern = true;
236                     t.append(".*");
237                     break;
238                 case '?':
239                     pattern = true;
240                     t.append('.');
241                     break;
242                 case '[':
243                     pattern = true;
244                     t.append(c);
245                     mode = 1;
246                     break;
247                 case '.': case '(': case ')': case '+': case '|': case '^': case '$':
248                     t.append('\\');
249                     // fall through
250
default:
251                     t.append(c);
252                 }
253                 break;
254             case 1: //inside []
255
t.append(c);
256                 if (c == ']') {
257                     mode = 0;
258                 }
259                 break;
260             default:
261                 throw new Error JavaDoc();//illegal state
262
}
263         }
264         return pattern ? t.toString() : null;
265     }
266     
267     /*
268      * Glob breaks up a glob expression into multiple glob patterns. This is needed when dealing
269      * with glob patterns like '{/home,foo}/enebo/*.rb' or '/home/enebo/{foo,bar/}' So for every
270      * embedded '{}' pair we create a new glob pattern and then determine whether that pattern
271      * ends with a delimeter or is an absolute pattern. Pathological globs could lead to many
272      * many patterns (e.g. '{a{b,{d,e},{g,h}}}') where each nested set of curlies will double the
273      * amount of patterns.
274      *
275      * Glob and GlobPattern do a lot of list copying and transforming. This could be done better.
276      */

277     private class GlobPattern {
278         private String JavaDoc pattern;
279         private String JavaDoc cwd;
280         private boolean endsWithDelimeter;
281         private boolean patternIsRelative;
282         private ArrayList JavaDoc files = null;
283         
284         public GlobPattern(String JavaDoc cwd, String JavaDoc pattern) {
285             this.cwd = cwd;
286             
287             // Java File will strip trailing path delimeter.
288
// Make a boolean for this special case (how about multiple slashes?)
289
this.endsWithDelimeter = pattern.endsWith("/") || pattern.endsWith("\\");
290             
291             if (new NormalizedFile(pattern).isAbsolute()) {
292                 this.patternIsRelative = false;
293                 this.pattern = canonicalize(pattern);
294             } else {
295                 // pattern is relative, but we need to consider cwd; add cwd but remember it's relative so we chop it off later
296
this.patternIsRelative = true;
297                 this.pattern = canonicalize(new NormalizedFile(cwd, pattern).getAbsolutePath());
298             }
299         }
300
301         public ArrayList JavaDoc getMatchedFiles() {
302             ArrayList JavaDoc fileNames = new ArrayList JavaDoc();
303             int size = files.size();
304             int offset = cwd.endsWith("/") ? 0 : 1;
305             
306             for (int i = 0; i < size; i++) {
307                 String JavaDoc path = ((NormalizedFile) files.get(i)).getPath();
308                 String JavaDoc name;
309
310                 if (patternIsRelative && !path.equals(cwd) && path.startsWith(cwd)) {
311                     // chop off cwd when returning results
312
name = path.substring(cwd.length() + offset);
313                 } else {
314                     name = path;
315                 }
316                 if (endsWithDelimeter) {
317                     name += "/";
318                 }
319
320                 
321                 fileNames.add(name.replace('\\', '/'));
322             }
323             
324             return fileNames;
325         }
326
327         public void setMatchedFiles(ArrayList JavaDoc files) {
328             this.files = files;
329         }
330
331         public String JavaDoc getPattern() {
332             return pattern;
333         }
334     }
335 }
336
Popular Tags