1 package org.apache.maven.shared.utils.io;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Set;
29
30 import javax.annotation.Nonnull;
31 import javax.annotation.Nullable;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 public class DirectoryScanner
112 {
113
114
115
116
117
118 public static final String[] DEFAULTEXCLUDES = {
119
120 "**/*~", "**/#*#", "**/.#*", "**/%*%", "**/._*",
121
122
123 "**/CVS", "**/CVS/**", "**/.cvsignore",
124
125
126 "**/RCS", "**/RCS/**",
127
128
129 "**/SCCS", "**/SCCS/**",
130
131
132 "**/vssver.scc",
133
134
135 "**/.svn", "**/.svn/**",
136
137
138 "**/.arch-ids", "**/.arch-ids/**",
139
140
141 "**/.bzr", "**/.bzr/**",
142
143
144 "**/.MySCMServerInfo",
145
146
147 "**/.DS_Store",
148
149
150 "**/.metadata", "**/.metadata/**",
151
152
153 "**/.hg", "**/.hg/**",
154
155
156 "**/.git", "**/.git/**",
157
158
159 "**/BitKeeper", "**/BitKeeper/**", "**/ChangeSet", "**/ChangeSet/**",
160
161
162 "**/_darcs", "**/_darcs/**", "**/.darcsrepo", "**/.darcsrepo/**", "**/-darcs-backup*", "**/.darcs-temp-mail" };
163
164
165
166
167 private File basedir;
168
169
170
171
172 private String[] includes;
173
174
175
176
177 private String[] excludes;
178
179 private MatchPatterns excludesPatterns;
180
181 private MatchPatterns includesPatterns;
182
183
184
185
186
187 private List<String> filesIncluded;
188
189
190
191
192 private List<String> filesNotIncluded;
193
194
195
196
197 private List<String> filesExcluded;
198
199
200
201
202 private List<String> dirsIncluded;
203
204
205
206
207 private List<String> dirsNotIncluded;
208
209
210
211
212 private List<String> dirsExcluded;
213
214
215
216
217 private boolean haveSlowResults = false;
218
219
220
221
222 private boolean isCaseSensitive = true;
223
224
225
226
227
228
229 private boolean followSymlinks = true;
230
231
232
233
234
235 private ScanConductor scanConductor = null;
236
237
238
239
240 private ScanConductor.ScanAction scanAction = null;
241
242
243
244
245 public DirectoryScanner()
246 {
247 }
248
249
250
251
252
253
254
255
256 public void setBasedir( final String basedir )
257 {
258 setBasedir( new File( basedir.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ) ) );
259 }
260
261
262
263
264
265
266 public void setBasedir( @Nonnull final File basedir )
267 {
268 this.basedir = basedir;
269 }
270
271
272
273
274
275
276 public File getBasedir()
277 {
278 return basedir;
279 }
280
281
282
283
284
285
286 public void setCaseSensitive( final boolean isCaseSensitive )
287 {
288 this.isCaseSensitive = isCaseSensitive;
289 }
290
291
292
293
294
295
296 public void setFollowSymlinks( final boolean followSymlinks )
297 {
298 this.followSymlinks = followSymlinks;
299 }
300
301
302
303
304
305
306
307
308
309
310 public void setIncludes( final String... includes )
311 {
312 if ( includes == null )
313 {
314 this.includes = null;
315 }
316 else
317 {
318 this.includes = new String[includes.length];
319 for ( int i = 0; i < includes.length; i++ )
320 {
321 String pattern;
322 pattern = includes[i].trim().replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
323 if ( pattern.endsWith( File.separator ) )
324 {
325 pattern += "**";
326 }
327 this.includes[i] = pattern;
328 }
329 }
330 }
331
332
333
334
335
336
337
338
339
340
341 public void setExcludes( final String... excludes )
342 {
343 if ( excludes == null )
344 {
345 this.excludes = null;
346 }
347 else
348 {
349 this.excludes = new String[excludes.length];
350 for ( int i = 0; i < excludes.length; i++ )
351 {
352 String pattern;
353 pattern = excludes[i].trim().replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
354 if ( pattern.endsWith( File.separator ) )
355 {
356 pattern += "**";
357 }
358 this.excludes[i] = pattern;
359 }
360 }
361 }
362
363 public void setScanConductor( final ScanConductor scanConductor )
364 {
365 this.scanConductor = scanConductor;
366 }
367
368
369
370
371
372
373
374
375 public void scan()
376 throws IllegalStateException
377 {
378 if ( basedir == null )
379 {
380 throw new IllegalStateException( "No basedir set" );
381 }
382 if ( !basedir.exists() )
383 {
384 throw new IllegalStateException( "basedir " + basedir + " does not exist" );
385 }
386 if ( !basedir.isDirectory() )
387 {
388 throw new IllegalStateException( "basedir " + basedir + " is not a directory" );
389 }
390
391 setupDefaultFilters();
392 setupMatchPatterns();
393
394 filesIncluded = new ArrayList<String>();
395 filesNotIncluded = new ArrayList<String>();
396 filesExcluded = new ArrayList<String>();
397 dirsIncluded = new ArrayList<String>();
398 dirsNotIncluded = new ArrayList<String>();
399 dirsExcluded = new ArrayList<String>();
400 scanAction = ScanConductor.ScanAction.CONTINUE;
401
402 if ( isIncluded( "" ) )
403 {
404 if ( !isExcluded( "" ) )
405 {
406 if ( scanConductor != null )
407 {
408 scanAction = scanConductor.visitDirectory( "", basedir );
409
410 if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
411 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction )
412 || ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
413 {
414 return;
415 }
416 }
417
418 dirsIncluded.add( "" );
419 }
420 else
421 {
422 dirsExcluded.add( "" );
423 }
424 }
425 else
426 {
427 dirsNotIncluded.add( "" );
428 }
429 scandir( basedir, "", true );
430 }
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447 public DirectoryScanResult diffIncludedFiles( String... oldFiles )
448 {
449 if ( filesIncluded == null )
450 {
451
452 scan();
453 }
454
455 return diffFiles( oldFiles, filesIncluded.toArray( new String[filesIncluded.size()] ) );
456 }
457
458 public static DirectoryScanResult diffFiles( @Nullable String[] oldFiles, @Nullable String[] newFiles )
459 {
460 Set<String> oldFileSet = arrayAsHashSet( oldFiles );
461 Set<String> newFileSet = arrayAsHashSet( newFiles );
462
463 List<String> added = new ArrayList<String>();
464 List<String> removed = new ArrayList<String>();
465
466 for ( String oldFile : oldFileSet )
467 {
468 if ( !newFileSet.contains( oldFile ) )
469 {
470 removed.add( oldFile );
471 }
472 }
473
474 for ( String newFile : newFileSet )
475 {
476 if ( !oldFileSet.contains( newFile ) )
477 {
478 added.add( newFile );
479 }
480 }
481
482 String[] filesAdded = added.toArray( new String[added.size()] );
483 String[] filesRemoved = removed.toArray( new String[removed.size()] );
484
485 return new DirectoryScanResult( filesAdded, filesRemoved );
486 }
487
488
489
490
491
492
493
494
495
496 private static <T> Set<T> arrayAsHashSet( @Nullable T[] array )
497 {
498 if ( array == null || array.length == 0 )
499 {
500 return Collections.emptySet();
501 }
502
503 Set<T> set = new HashSet<T>( array.length );
504 Collections.addAll( set, array );
505
506 return set;
507 }
508
509
510
511
512
513
514
515
516 void slowScan()
517 {
518 if ( haveSlowResults )
519 {
520 return;
521 }
522
523 final String[] excl = dirsExcluded.toArray( new String[dirsExcluded.size()] );
524
525 final String[] notIncl = dirsNotIncluded.toArray( new String[dirsNotIncluded.size()] );
526
527 for ( String anExcl : excl )
528 {
529 if ( !couldHoldIncluded( anExcl ) )
530 {
531 scandir( new File( basedir, anExcl ), anExcl + File.separator, false );
532 }
533 }
534
535 for ( String aNotIncl : notIncl )
536 {
537 if ( !couldHoldIncluded( aNotIncl ) )
538 {
539 scandir( new File( basedir, aNotIncl ), aNotIncl + File.separator, false );
540 }
541 }
542
543 haveSlowResults = true;
544 }
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563 void scandir( @Nonnull final File dir, @Nonnull final String vpath, final boolean fast )
564 {
565 String[] newfiles = dir.list();
566
567 if ( newfiles == null )
568 {
569
570
571
572
573
574
575
576
577
578
579 newfiles = new String[0];
580
581
582 }
583
584 if ( !followSymlinks )
585 {
586 final List<String> noLinks = new ArrayList<String>();
587 for ( final String newfile : newfiles )
588 {
589 try
590 {
591 if ( isSymbolicLink( dir, newfile ) )
592 {
593 final String name = vpath + newfile;
594 final File file = new File( dir, newfile );
595 if ( file.isDirectory() )
596 {
597 dirsExcluded.add( name );
598 }
599 else
600 {
601 filesExcluded.add( name );
602 }
603 }
604 else
605 {
606 noLinks.add( newfile );
607 }
608 }
609 catch ( final IOException ioe )
610 {
611 final String msg =
612 "IOException caught while checking " + "for links, couldn't get cannonical path!";
613
614 System.err.println( msg );
615 noLinks.add( newfile );
616 }
617 }
618 newfiles = noLinks.toArray( new String[noLinks.size()] );
619 }
620
621 for ( final String newfile : newfiles )
622 {
623 final String name = vpath + newfile;
624 final File file = new File( dir, newfile );
625 if ( file.isDirectory() )
626 {
627 if ( isIncluded( name ) )
628 {
629 if ( !isExcluded( name ) )
630 {
631 if ( scanConductor != null )
632 {
633 scanAction = scanConductor.visitDirectory( name, file );
634
635 if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
636 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
637 {
638 return;
639 }
640 }
641
642 if ( !ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
643 {
644 dirsIncluded.add( name );
645 if ( fast )
646 {
647 scandir( file, name + File.separator, fast );
648
649 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
650 {
651 return;
652 }
653 }
654 }
655 scanAction = null;
656
657 }
658 else
659 {
660 dirsExcluded.add( name );
661 if ( fast && couldHoldIncluded( name ) )
662 {
663 scandir( file, name + File.separator, fast );
664 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
665 {
666 return;
667 }
668 scanAction = null;
669 }
670 }
671 }
672 else
673 {
674 if ( fast && couldHoldIncluded( name ) )
675 {
676 if ( scanConductor != null )
677 {
678 scanAction = scanConductor.visitDirectory( name, file );
679
680 if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
681 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
682 {
683 return;
684 }
685 }
686 if ( !ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
687 {
688 dirsNotIncluded.add( name );
689
690 scandir( file, name + File.separator, fast );
691 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
692 {
693 return;
694 }
695 }
696 scanAction = null;
697 }
698 }
699 if ( !fast )
700 {
701 scandir( file, name + File.separator, fast );
702 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
703 {
704 return;
705 }
706 scanAction = null;
707 }
708 }
709 else if ( file.isFile() )
710 {
711 if ( isIncluded( name ) )
712 {
713 if ( !isExcluded( name ) )
714 {
715 if ( scanConductor != null )
716 {
717 scanAction = scanConductor.visitFile( name, file );
718 }
719
720 if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
721 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
722 {
723 return;
724 }
725
726 filesIncluded.add( name );
727 }
728 else
729 {
730 filesExcluded.add( name );
731 }
732 }
733 else
734 {
735 filesNotIncluded.add( name );
736 }
737 }
738 }
739 }
740
741
742
743
744
745
746
747
748 boolean isIncluded( final String name )
749 {
750 return includesPatterns.matches( name, isCaseSensitive );
751 }
752
753
754
755
756
757
758
759
760 boolean couldHoldIncluded( @Nonnull final String name )
761 {
762 return includesPatterns.matchesPatternStart( name, isCaseSensitive );
763 }
764
765
766
767
768
769
770
771
772 boolean isExcluded( @Nonnull final String name )
773 {
774 return excludesPatterns.matches( name, isCaseSensitive );
775 }
776
777
778
779
780
781
782
783
784 public String[] getIncludedFiles()
785 {
786 if ( filesIncluded == null )
787 {
788 return new String[0];
789 }
790 return filesIncluded.toArray( new String[filesIncluded.size()] );
791 }
792
793
794
795
796
797
798
799
800 public String[] getNotIncludedFiles()
801 {
802 slowScan();
803 return filesNotIncluded.toArray( new String[filesNotIncluded.size()] );
804 }
805
806
807
808
809
810
811
812
813
814
815 public String[] getExcludedFiles()
816 {
817 slowScan();
818 return filesExcluded.toArray( new String[filesExcluded.size()] );
819 }
820
821
822
823
824
825
826
827
828 public String[] getIncludedDirectories()
829 {
830 return dirsIncluded.toArray( new String[dirsIncluded.size()] );
831 }
832
833
834
835
836
837
838
839
840 public String[] getNotIncludedDirectories()
841 {
842 slowScan();
843 return dirsNotIncluded.toArray( new String[dirsNotIncluded.size()] );
844 }
845
846
847
848
849
850
851
852
853
854
855 public String[] getExcludedDirectories()
856 {
857 slowScan();
858 return dirsExcluded.toArray( new String[dirsExcluded.size()] );
859 }
860
861
862
863
864 public void addDefaultExcludes()
865 {
866 final int excludesLength = excludes == null ? 0 : excludes.length;
867 String[] newExcludes;
868 newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length];
869 if ( excludesLength > 0 )
870 {
871 System.arraycopy( excludes, 0, newExcludes, 0, excludesLength );
872 }
873 for ( int i = 0; i < DEFAULTEXCLUDES.length; i++ )
874 {
875 newExcludes[i + excludesLength] =
876 DEFAULTEXCLUDES[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
877 }
878 excludes = newExcludes;
879 }
880
881
882
883
884
885
886
887
888
889
890
891
892 boolean isSymbolicLink( final File parent, final String name )
893 throws IOException
894 {
895 if ( Java7Support.isAtLeastJava7() )
896 {
897 return Java7Support.isSymLink( parent );
898 }
899 final File resolvedParent = new File( parent.getCanonicalPath() );
900 final File toTest = new File( resolvedParent, name );
901 return !toTest.getAbsolutePath().equals( toTest.getCanonicalPath() );
902 }
903
904 private void setupDefaultFilters()
905 {
906 if ( includes == null )
907 {
908
909 includes = new String[1];
910 includes[0] = "**";
911 }
912 if ( excludes == null )
913 {
914 excludes = new String[0];
915 }
916 }
917
918
919 private void setupMatchPatterns()
920 {
921 includesPatterns = MatchPatterns.from( includes );
922 excludesPatterns = MatchPatterns.from( excludes );
923 }
924
925 }