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      /**
33       * DirStackEntry is an Item on the {@link DirectoryWalker#dirStack}
34       */
35      class DirStackEntry
36      {
37          /**
38           * Count of files in the directory.
39           */
40          public int count;
41  
42          /**
43           * Current Directory.
44           */
45          public File dir;
46  
47          /**
48           * Index (or offset) within the directory count.
49           */
50          public int index;
51  
52          /**
53           * Offset for percentage calculations. Based on parent DirStackEntry.
54           */
55          public double percentageOffset;
56  
57          /**
58           * Size of percentage space to work with.
59           */
60          public double percentageSize;
61  
62          /**
63           * Create a DirStackEntry.
64           *
65           * @param d the directory to track
66           * @param length the length of entries in the directory.
67           */
68          public DirStackEntry( File d, int length )
69          {
70              dir = d;
71              count = length;
72          }
73  
74          /**
75           * Calculate the next percentage offset. Used by the next DirStackEntry.
76           *
77           * @return the value for the next percentage offset.
78           */
79          public double getNextPercentageOffset()
80          {
81              return percentageOffset + ( index * ( percentageSize / count ) );
82          }
83  
84          /**
85           * Calculate the next percentage size. Used by the next DirStackEntry.
86           *
87           * @return the value for the next percentage size.
88           */
89          public double getNextPercentageSize()
90          {
91              return ( percentageSize / count );
92          }
93  
94          /**
95           * The percentage of the DirStackEntry right now. Based on count, index, percentageOffset, and percentageSize.
96           *
97           * @return the percentage right now.
98           */
99          public int getPercentage()
100         {
101             double percentageWithinDir = (double) index / (double) count;
102             return (int) Math.floor( percentageOffset + ( percentageWithinDir * percentageSize ) );
103         }
104 
105         @Override
106         public String toString()
107         {
108             return "DirStackEntry[" + "dir=" + dir.getAbsolutePath() + ",count=" + count + ",index=" + index
109                 + ",percentageOffset=" + percentageOffset + ",percentageSize=" + percentageSize + ",percentage()="
110                 + getPercentage() + ",getNextPercentageOffset()=" + getNextPercentageOffset()
111                 + ",getNextPercentageSize()=" + getNextPercentageSize() + "]";
112         }
113     }
114 
115     private File baseDir;
116 
117     private int baseDirOffset;
118 
119     private Stack<DirectoryWalker.DirStackEntry> dirStack;
120 
121     private List<String> excludes;
122 
123     private List<String> includes;
124 
125     private boolean isCaseSensitive = true;
126 
127     private List<DirectoryWalkListener> listeners;
128 
129     private boolean debugEnabled = false;
130 
131     public DirectoryWalker()
132     {
133         includes = new ArrayList<String>();
134         excludes = new ArrayList<String>();
135         listeners = new ArrayList<DirectoryWalkListener>();
136     }
137 
138     public void addDirectoryWalkListener( DirectoryWalkListener listener )
139     {
140         listeners.add( listener );
141     }
142 
143     public void addExclude( String exclude )
144     {
145         excludes.add( fixPattern( exclude ) );
146     }
147 
148     public void addInclude( String include )
149     {
150         includes.add( fixPattern( include ) );
151     }
152 
153     /**
154      * Add's to the Exclude List the default list of SCM excludes.
155      */
156     public void addSCMExcludes()
157     {
158         String scmexcludes[] = AbstractScanner.DEFAULTEXCLUDES;
159         for ( String scmexclude : scmexcludes )
160         {
161             addExclude( scmexclude );
162         }
163     }
164 
165     private void fireStep( File file )
166     {
167         DirStackEntry dsEntry = dirStack.peek();
168         int percentage = dsEntry.getPercentage();
169         for ( Object listener1 : listeners )
170         {
171             DirectoryWalkListener listener = (DirectoryWalkListener) listener1;
172             listener.directoryWalkStep( percentage, file );
173         }
174     }
175 
176     private void fireWalkFinished()
177     {
178         for ( DirectoryWalkListener listener1 : listeners )
179         {
180             listener1.directoryWalkFinished();
181         }
182     }
183 
184     private void fireWalkStarting()
185     {
186         for ( DirectoryWalkListener listener1 : listeners )
187         {
188             listener1.directoryWalkStarting( baseDir );
189         }
190     }
191 
192     private void fireDebugMessage( String message )
193     {
194         for ( DirectoryWalkListener listener1 : listeners )
195         {
196             listener1.debug( message );
197         }
198     }
199 
200     private String fixPattern( String pattern )
201     {
202         String cleanPattern = pattern;
203 
204         if ( File.separatorChar != '/' )
205         {
206             cleanPattern = cleanPattern.replace( '/', File.separatorChar );
207         }
208 
209         if ( File.separatorChar != '\\' )
210         {
211             cleanPattern = cleanPattern.replace( '\\', File.separatorChar );
212         }
213 
214         return cleanPattern;
215     }
216 
217     public void setDebugMode( boolean debugEnabled )
218     {
219         this.debugEnabled = debugEnabled;
220     }
221 
222     /**
223      * @return Returns the baseDir.
224      */
225     public File getBaseDir()
226     {
227         return baseDir;
228     }
229 
230     /**
231      * @return Returns the excludes.
232      */
233     public List<String> getExcludes()
234     {
235         return excludes;
236     }
237 
238     /**
239      * @return Returns the includes.
240      */
241     public List<String> getIncludes()
242     {
243         return includes;
244     }
245 
246     private boolean isExcluded( String name )
247     {
248         return isMatch( excludes, name );
249     }
250 
251     private boolean isIncluded( String name )
252     {
253         return isMatch( includes, name );
254     }
255 
256     private boolean isMatch( List<String> patterns, String name )
257     {
258         for ( String pattern1 : patterns )
259         {
260             if ( SelectorUtils.matchPath( pattern1, name, isCaseSensitive ) )
261             {
262                 return true;
263             }
264         }
265 
266         return false;
267     }
268 
269     private String relativeToBaseDir( File file )
270     {
271         return file.getAbsolutePath().substring( baseDirOffset + 1 );
272     }
273 
274     /**
275      * Removes a DirectoryWalkListener.
276      *
277      * @param listener the listener to remove.
278      */
279     public void removeDirectoryWalkListener( DirectoryWalkListener listener )
280     {
281         listeners.remove( listener );
282     }
283 
284     /**
285      * Performs a Scan against the provided {@link #setBaseDir(File)}
286      */
287     public void scan()
288     {
289         if ( baseDir == null )
290         {
291             throw new IllegalStateException( "Scan Failure.  BaseDir not specified." );
292         }
293 
294         if ( !baseDir.exists() )
295         {
296             throw new IllegalStateException( "Scan Failure.  BaseDir does not exist." );
297         }
298 
299         if ( !baseDir.isDirectory() )
300         {
301             throw new IllegalStateException( "Scan Failure.  BaseDir is not a directory." );
302         }
303 
304         if ( includes.isEmpty() )
305         {
306             // default to include all.
307             addInclude( "**" );
308         }
309 
310         if ( debugEnabled )
311         {
312             Iterator<String> it;
313             StringBuilder dbg = new StringBuilder();
314             dbg.append( "DirectoryWalker Scan" );
315             dbg.append( "\n  Base Dir: " ).append( baseDir.getAbsolutePath() );
316             dbg.append( "\n  Includes: " );
317             it = includes.iterator();
318             while ( it.hasNext() )
319             {
320                 String include = it.next();
321                 dbg.append( "\n    - \"" ).append( include ).append( "\"" );
322             }
323             dbg.append( "\n  Excludes: " );
324             it = excludes.iterator();
325             while ( it.hasNext() )
326             {
327                 String exclude = it.next();
328                 dbg.append( "\n    - \"" ).append( exclude ).append( "\"" );
329             }
330             fireDebugMessage( dbg.toString() );
331         }
332 
333         fireWalkStarting();
334         dirStack = new Stack<DirStackEntry>();
335         scanDir( baseDir );
336         fireWalkFinished();
337     }
338 
339     private void scanDir( File dir )
340     {
341         File[] files = dir.listFiles();
342 
343         if ( files == null )
344         {
345             return;
346         }
347 
348         DirectoryWalker.DirStackEntry curStackEntry = new DirectoryWalker.DirStackEntry( dir, files.length );
349         if ( dirStack.isEmpty() )
350         {
351             curStackEntry.percentageOffset = 0;
352             curStackEntry.percentageSize = 100;
353         }
354         else
355         {
356             DirectoryWalker.DirStackEntry previousStackEntry = dirStack.peek();
357             curStackEntry.percentageOffset = previousStackEntry.getNextPercentageOffset();
358             curStackEntry.percentageSize = previousStackEntry.getNextPercentageSize();
359         }
360 
361         dirStack.push( curStackEntry );
362 
363         for ( int idx = 0; idx < files.length; idx++ )
364         {
365             curStackEntry.index = idx;
366             String name = relativeToBaseDir( files[idx] );
367 
368             if ( isExcluded( name ) )
369             {
370                 fireDebugMessage( name + " is excluded." );
371                 continue;
372             }
373 
374             if ( files[idx].isDirectory() )
375             {
376                 scanDir( files[idx] );
377             }
378             else
379             {
380                 if ( isIncluded( name ) )
381                 {
382                     fireStep( files[idx] );
383                 }
384             }
385         }
386 
387         dirStack.pop();
388     }
389 
390     /**
391      * @param baseDir The baseDir to set.
392      */
393     public void setBaseDir( File baseDir )
394     {
395         this.baseDir = baseDir;
396         baseDirOffset = baseDir.getAbsolutePath().length();
397     }
398 
399     /**
400      * @param entries The excludes to set.
401      */
402     public void setExcludes( List<String> entries )
403     {
404         excludes.clear();
405         if ( entries != null )
406         {
407             for ( String entry : entries )
408             {
409                 excludes.add( fixPattern( entry ) );
410             }
411         }
412     }
413 
414     /**
415      * @param entries The includes to set.
416      */
417     public void setIncludes( List<String> entries )
418     {
419         includes.clear();
420         if ( entries != null )
421         {
422             for ( String entry : entries )
423             {
424                 includes.add( fixPattern( entry ) );
425             }
426         }
427     }
428 
429 }