View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.finder;
18  
19  import java.io.File;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  
26  /**
27   * Searches for files that match certain criteria in a directory, and
28   * typically also subdirectories.
29   * <p>
30   * This is a programmatic equivalent of the search functions present
31   * in many operating systems. You can be informed dynamically of
32   * events as they occur by adding a listener.
33   * 
34   * @author Henri Yandell
35   * @version $Id: FileFinder.java 437543 2006-08-28 05:47:51Z bayard $
36   * @since 1.1
37   */
38  public class FileFinder implements Finder {
39  
40      /** The list of listeners. */
41      private List findListeners;
42  
43      /**
44       * Constructor.
45       */
46      public FileFinder() {
47          super();
48      }
49  
50      //-----------------------------------------------------------------------
51      /**
52       * Find all files in the specified directory.
53       * 
54       * @param directory  the directory to search in
55       */
56      public File[] find(File directory) {
57          return find(directory, Collections.EMPTY_MAP);
58      }
59  
60      /**
61       * Finds files in the specified directory that match the given options.
62       * 
63       * @param directory  the directory to search in
64       * @param options  the options to use
65       */
66      public File[] find(File directory, Map options) {
67          List retlist = new ArrayList();
68          find(directory, options, retlist);
69          File[] files = (File[]) retlist.toArray(new File[retlist.size()]);
70          return files;
71      }
72  
73      /**
74       * Finds files in the specified directory that match the given options.
75       * The results are added to the non-null list provided.
76       * <p>
77       * Note: To avoid large memory allocations, avoid adding any FileListeners,
78       * and try overwriding List.add(Object o) instead.
79       * 
80       * @param directory  the directory to search in
81       * @param options  the options to use
82       * @param foundFiles  a List to which all found files will be added
83       */
84      public void find(File directory, Map options, List foundFiles) {
85          if (foundFiles == null) {
86              throw new IllegalArgumentException("foundFiles list cannot be null");
87          }
88          
89          notifyDirectoryStarted(directory);
90          boolean depthFirst = toBoolean(options.get(Finder.DEPTH));
91          int maxDepth = toInt(options.get(Finder.MAXDEPTH));
92          int minDepth = toInt(options.get(Finder.MINDEPTH));
93          boolean ignoreHiddenDirs = toBoolean(options.get(Finder.IGNORE_HIDDEN_DIRS));
94          
95          FindingFilter filter = new FindingFilter(options);
96          int startIdx = foundFiles.size();
97          find(directory, filter, depthFirst, foundFiles);
98          if (filter.accept(directory)) {
99              if (depthFirst) {
100                 foundFiles.add(directory);
101             } else {
102                 foundFiles.add(0, directory);
103             }
104         }
105         int endIdx = foundFiles.size();
106         notifyDirectoryFinished(directory, foundFiles, startIdx, endIdx);        
107     }
108 
109     private void find(File directory, FindingFilter filter, boolean depthFirst, List retlist) {
110         // we can't use listFiles(filter) here, directories don't work correctly
111         File[] list = directory.listFiles();
112         if (list == null) {
113             return;
114         }
115         int sz = list.length;
116         for (int i = 0; i < sz; i++) {
117             File tmp = list[i];
118             if (!depthFirst && filter.accept(tmp)) {
119                 retlist.add(tmp);
120                 notifyFileFound(directory, tmp);
121             }
122             if (tmp.isDirectory()) {
123                 notifyDirectoryStarted(tmp);
124                 int startIdx = retlist.size();
125                 find(tmp, filter, depthFirst, retlist);
126                 int endIdx = retlist.size();
127                 notifyDirectoryFinished(tmp, retlist, startIdx, endIdx);
128             }
129             if (depthFirst && filter.accept(tmp)) {
130                 retlist.add(tmp);
131                 notifyFileFound(directory, tmp);
132             }
133         }
134     }
135 
136     //-----------------------------------------------------------------------
137     /**
138      * Adds a FindListener that is notified when a file is found.
139      * 
140      * @param listener  the listener
141      */
142     public void addFindListener(FindListener listener) {
143         if (findListeners == null) {
144             findListeners = new ArrayList();
145         }
146         findListeners.add(listener);
147     }
148 
149     /**
150      * Removes a FindListener.
151      * 
152      * @param listener  the listener
153      */
154     public void removeFindListener(FindListener listener) {
155         if (findListeners != null) {
156             findListeners.remove(listener);
157         }
158     }
159 
160     //-----------------------------------------------------------------------
161     /**
162      * Notify all FindListeners that a directory is being started.
163      * 
164      * @param directory  the directory
165      */
166     protected void notifyDirectoryStarted(File directory) {
167         if (!directory.isDirectory()) {
168             return;
169         }
170         if (findListeners != null) {
171             FindEvent fe = new FindEvent(this, "directoryStarted", directory);
172             Iterator it = findListeners.iterator();
173             while (it.hasNext()) {
174                 FindListener findListener = (FindListener) it.next();
175                 findListener.directoryStarted(fe);
176             }
177         }
178     }
179 
180     /**
181      * Notify all FindListeners that a directory has been finished.
182      * 
183      * @param directory  the directory
184      * @param files  the files that were found in the directory
185      */
186     protected void notifyDirectoryFinished(File directory, File[] files) {
187         if (!directory.isDirectory()) {
188             return;
189         }
190         if (findListeners != null) {
191             FindEvent fe = new FindEvent(this, "directoryFinished", directory, files);
192             Iterator it = findListeners.iterator();
193             while (it.hasNext()) {
194                 FindListener findListener = (FindListener) it.next();
195                 findListener.directoryFinished(fe);
196             }
197         }
198     }
199 
200     private void notifyDirectoryFinished(File directory, List files, int startIdx, int endIdx) {
201         // only allocate a File array if we really have to 
202         if (!directory.isDirectory() || findListeners == null || findListeners.size() == 0) {
203             return;
204         }
205         
206         // we should drop this when/if FindEvent stops dealing with File arrays
207         File[] filesa = (File[]) files.subList(startIdx, endIdx).toArray(new File[endIdx - startIdx]);
208         notifyDirectoryFinished(directory, filesa);
209         
210     }
211 
212     /**
213      * Notifies each FindListeners that a file has been found.
214      * 
215      * @param directory  the directory being processed
216      * @param file  the file that was found
217      */
218     protected void notifyFileFound(File directory, File file) {
219         if (file.isDirectory()) {
220             return;
221         }
222         if (findListeners != null) {
223             FindEvent fe = new FindEvent(this, "fileFound", directory, file);
224             Iterator it = findListeners.iterator();
225             while (it.hasNext()) {
226                 FindListener findListener = (FindListener) it.next();
227                 findListener.fileFound(fe);
228             }
229         }
230     }
231 
232     //-----------------------------------------------------------------------
233     /**
234      * Converts an object to an int.
235      * 
236      * @param obj  the object
237      * @return an int
238      */
239     private static int toInt(Object obj) {
240         if (obj == null) {
241             return 0;
242         } else if (obj instanceof Number) {
243             return ((Number) obj).intValue();
244         } else {
245             String str = obj.toString();
246             try {
247                 return Integer.parseInt(str.toString());
248             } catch(NumberFormatException nfe) {
249                 throw new IllegalArgumentException("String argument " + str + " must be parseable as an integer");
250             }
251         }
252     }
253 
254     /**
255      * Converts an object to a boolean.
256      * 
257      * @param obj  the object
258      * @return a boolean
259      */
260     private static boolean toBoolean(Object obj) {
261         if (obj == null) {
262             return false;
263         } else if (obj instanceof Boolean) {
264             return ((Boolean) obj).booleanValue();
265         } else if (obj instanceof Number) {
266             return ((Number) obj).intValue() != 0;
267         } else {
268             String str = obj.toString();
269             return new Boolean(str).booleanValue();
270         }
271     }
272 
273 }