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