View Javadoc
1   package org.codehaus.plexus.util;
2   
3   /*
4    * Copyright The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.File;
20  import java.util.ArrayList;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Stack;
24  
25  /**
26   * DirectoryWalker
27   *
28   *
29   */
30  public class DirectoryWalker {
31      /**
32       * DirStackEntry is an Item on the {@link DirectoryWalker#dirStack}
33       */
34      class DirStackEntry {
35          /**
36           * Count of files in the directory.
37           */
38          public int count;
39  
40          /**
41           * Current Directory.
42           */
43          public File dir;
44  
45          /**
46           * Index (or offset) within the directory count.
47           */
48          public int index;
49  
50          /**
51           * Offset for percentage calculations. Based on parent DirStackEntry.
52           */
53          public double percentageOffset;
54  
55          /**
56           * Size of percentage space to work with.
57           */
58          public double percentageSize;
59  
60          /**
61           * Create a DirStackEntry.
62           *
63           * @param d the directory to track
64           * @param length the length of entries in the directory.
65           */
66          public DirStackEntry(File d, int length) {
67              dir = d;
68              count = length;
69          }
70  
71          /**
72           * Calculate the next percentage offset. Used by the next DirStackEntry.
73           *
74           * @return the value for the next percentage offset.
75           */
76          public double getNextPercentageOffset() {
77              return percentageOffset + (index * (percentageSize / count));
78          }
79  
80          /**
81           * Calculate the next percentage size. Used by the next DirStackEntry.
82           *
83           * @return the value for the next percentage size.
84           */
85          public double getNextPercentageSize() {
86              return (percentageSize / count);
87          }
88  
89          /**
90           * The percentage of the DirStackEntry right now. Based on count, index, percentageOffset, and percentageSize.
91           *
92           * @return the percentage right now.
93           */
94          public int getPercentage() {
95              double percentageWithinDir = (double) index / (double) count;
96              return (int) Math.floor(percentageOffset + (percentageWithinDir * percentageSize));
97          }
98  
99          @Override
100         public String toString() {
101             return "DirStackEntry[" + "dir=" + dir.getAbsolutePath() + ",count=" + count + ",index=" + index
102                     + ",percentageOffset=" + percentageOffset + ",percentageSize=" + percentageSize + ",percentage()="
103                     + getPercentage() + ",getNextPercentageOffset()=" + getNextPercentageOffset()
104                     + ",getNextPercentageSize()=" + getNextPercentageSize() + "]";
105         }
106     }
107 
108     private File baseDir;
109 
110     private int baseDirOffset;
111 
112     private Stack<DirectoryWalker.DirStackEntry> dirStack;
113 
114     private List<String> excludes;
115 
116     private List<String> includes;
117 
118     private boolean isCaseSensitive = true;
119 
120     private List<DirectoryWalkListener> listeners;
121 
122     private boolean debugEnabled = false;
123 
124     public DirectoryWalker() {
125         includes = new ArrayList<String>();
126         excludes = new ArrayList<String>();
127         listeners = new ArrayList<DirectoryWalkListener>();
128     }
129 
130     public void addDirectoryWalkListener(DirectoryWalkListener listener) {
131         listeners.add(listener);
132     }
133 
134     public void addExclude(String exclude) {
135         excludes.add(fixPattern(exclude));
136     }
137 
138     public void addInclude(String include) {
139         includes.add(fixPattern(include));
140     }
141 
142     /**
143      * Add's to the Exclude List the default list of SCM excludes.
144      */
145     public void addSCMExcludes() {
146         String scmexcludes[] = AbstractScanner.DEFAULTEXCLUDES;
147         for (String scmexclude : scmexcludes) {
148             addExclude(scmexclude);
149         }
150     }
151 
152     private void fireStep(File file) {
153         DirStackEntry dsEntry = dirStack.peek();
154         int percentage = dsEntry.getPercentage();
155         for (Object listener1 : listeners) {
156             DirectoryWalkListener listener = (DirectoryWalkListener) listener1;
157             listener.directoryWalkStep(percentage, file);
158         }
159     }
160 
161     private void fireWalkFinished() {
162         for (DirectoryWalkListener listener1 : listeners) {
163             listener1.directoryWalkFinished();
164         }
165     }
166 
167     private void fireWalkStarting() {
168         for (DirectoryWalkListener listener1 : listeners) {
169             listener1.directoryWalkStarting(baseDir);
170         }
171     }
172 
173     private void fireDebugMessage(String message) {
174         for (DirectoryWalkListener listener1 : listeners) {
175             listener1.debug(message);
176         }
177     }
178 
179     private String fixPattern(String pattern) {
180         String cleanPattern = pattern;
181 
182         if (File.separatorChar != '/') {
183             cleanPattern = cleanPattern.replace('/', File.separatorChar);
184         }
185 
186         if (File.separatorChar != '\\') {
187             cleanPattern = cleanPattern.replace('\\', File.separatorChar);
188         }
189 
190         return cleanPattern;
191     }
192 
193     public void setDebugMode(boolean debugEnabled) {
194         this.debugEnabled = debugEnabled;
195     }
196 
197     /**
198      * @return Returns the baseDir.
199      */
200     public File getBaseDir() {
201         return baseDir;
202     }
203 
204     /**
205      * @return Returns the excludes.
206      */
207     public List<String> getExcludes() {
208         return excludes;
209     }
210 
211     /**
212      * @return Returns the includes.
213      */
214     public List<String> getIncludes() {
215         return includes;
216     }
217 
218     private boolean isExcluded(String name) {
219         return isMatch(excludes, name);
220     }
221 
222     private boolean isIncluded(String name) {
223         return isMatch(includes, name);
224     }
225 
226     private boolean isMatch(List<String> patterns, String name) {
227         for (String pattern1 : patterns) {
228             if (SelectorUtils.matchPath(pattern1, name, isCaseSensitive)) {
229                 return true;
230             }
231         }
232 
233         return false;
234     }
235 
236     private String relativeToBaseDir(File file) {
237         return file.getAbsolutePath().substring(baseDirOffset + 1);
238     }
239 
240     /**
241      * Removes a DirectoryWalkListener.
242      *
243      * @param listener the listener to remove.
244      */
245     public void removeDirectoryWalkListener(DirectoryWalkListener listener) {
246         listeners.remove(listener);
247     }
248 
249     /**
250      * Performs a Scan against the provided {@link #setBaseDir(File)}
251      */
252     public void scan() {
253         if (baseDir == null) {
254             throw new IllegalStateException("Scan Failure.  BaseDir not specified.");
255         }
256 
257         if (!baseDir.exists()) {
258             throw new IllegalStateException("Scan Failure.  BaseDir does not exist.");
259         }
260 
261         if (!baseDir.isDirectory()) {
262             throw new IllegalStateException("Scan Failure.  BaseDir is not a directory.");
263         }
264 
265         if (includes.isEmpty()) {
266             // default to include all.
267             addInclude("**");
268         }
269 
270         if (debugEnabled) {
271             Iterator<String> it;
272             StringBuilder dbg = new StringBuilder();
273             dbg.append("DirectoryWalker Scan");
274             dbg.append("\n  Base Dir: ").append(baseDir.getAbsolutePath());
275             dbg.append("\n  Includes: ");
276             it = includes.iterator();
277             while (it.hasNext()) {
278                 String include = it.next();
279                 dbg.append("\n    - \"").append(include).append("\"");
280             }
281             dbg.append("\n  Excludes: ");
282             it = excludes.iterator();
283             while (it.hasNext()) {
284                 String exclude = it.next();
285                 dbg.append("\n    - \"").append(exclude).append("\"");
286             }
287             fireDebugMessage(dbg.toString());
288         }
289 
290         fireWalkStarting();
291         dirStack = new Stack<DirStackEntry>();
292         scanDir(baseDir);
293         fireWalkFinished();
294     }
295 
296     private void scanDir(File dir) {
297         File[] files = dir.listFiles();
298 
299         if (files == null) {
300             return;
301         }
302 
303         DirectoryWalker.DirStackEntry curStackEntry = new DirectoryWalker.DirStackEntry(dir, files.length);
304         if (dirStack.isEmpty()) {
305             curStackEntry.percentageOffset = 0;
306             curStackEntry.percentageSize = 100;
307         } else {
308             DirectoryWalker.DirStackEntry previousStackEntry = dirStack.peek();
309             curStackEntry.percentageOffset = previousStackEntry.getNextPercentageOffset();
310             curStackEntry.percentageSize = previousStackEntry.getNextPercentageSize();
311         }
312 
313         dirStack.push(curStackEntry);
314 
315         for (int idx = 0; idx < files.length; idx++) {
316             curStackEntry.index = idx;
317             String name = relativeToBaseDir(files[idx]);
318 
319             if (isExcluded(name)) {
320                 fireDebugMessage(name + " is excluded.");
321                 continue;
322             }
323 
324             if (files[idx].isDirectory()) {
325                 scanDir(files[idx]);
326             } else {
327                 if (isIncluded(name)) {
328                     fireStep(files[idx]);
329                 }
330             }
331         }
332 
333         dirStack.pop();
334     }
335 
336     /**
337      * @param baseDir The baseDir to set.
338      */
339     public void setBaseDir(File baseDir) {
340         this.baseDir = baseDir;
341         baseDirOffset = baseDir.getAbsolutePath().length();
342     }
343 
344     /**
345      * @param entries The excludes to set.
346      */
347     public void setExcludes(List<String> entries) {
348         excludes.clear();
349         if (entries != null) {
350             for (String entry : entries) {
351                 excludes.add(fixPattern(entry));
352             }
353         }
354     }
355 
356     /**
357      * @param entries The includes to set.
358      */
359     public void setIncludes(List<String> entries) {
360         includes.clear();
361         if (entries != null) {
362             for (String entry : entries) {
363                 includes.add(fixPattern(entry));
364             }
365         }
366     }
367 }