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 isCaseSensitiveParameter )
287 {
288 this.isCaseSensitive = isCaseSensitiveParameter;
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
364
365
366 public void setScanConductor( final ScanConductor scanConductor )
367 {
368 this.scanConductor = scanConductor;
369 }
370
371
372
373
374
375
376
377
378 public void scan()
379 throws IllegalStateException
380 {
381 if ( basedir == null )
382 {
383 throw new IllegalStateException( "No basedir set" );
384 }
385 if ( !basedir.exists() )
386 {
387 throw new IllegalStateException( "basedir " + basedir + " does not exist" );
388 }
389 if ( !basedir.isDirectory() )
390 {
391 throw new IllegalStateException( "basedir " + basedir + " is not a directory" );
392 }
393
394 setupDefaultFilters();
395 setupMatchPatterns();
396
397 filesIncluded = new ArrayList<String>();
398 filesNotIncluded = new ArrayList<String>();
399 filesExcluded = new ArrayList<String>();
400 dirsIncluded = new ArrayList<String>();
401 dirsNotIncluded = new ArrayList<String>();
402 dirsExcluded = new ArrayList<String>();
403 scanAction = ScanConductor.ScanAction.CONTINUE;
404
405 if ( isIncluded( "" ) )
406 {
407 if ( !isExcluded( "" ) )
408 {
409 if ( scanConductor != null )
410 {
411 scanAction = scanConductor.visitDirectory( "", basedir );
412
413 if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
414 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction )
415 || ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
416 {
417 return;
418 }
419 }
420
421 dirsIncluded.add( "" );
422 }
423 else
424 {
425 dirsExcluded.add( "" );
426 }
427 }
428 else
429 {
430 dirsNotIncluded.add( "" );
431 }
432 scandir( basedir, "", true );
433 }
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451 public DirectoryScanResult diffIncludedFiles( String... oldFiles )
452 {
453 if ( filesIncluded == null )
454 {
455
456 scan();
457 }
458
459 return diffFiles( oldFiles, filesIncluded.toArray( new String[filesIncluded.size()] ) );
460 }
461
462
463
464
465
466
467 public static DirectoryScanResult diffFiles( @Nullable String[] oldFiles, @Nullable String[] newFiles )
468 {
469 Set<String> oldFileSet = arrayAsHashSet( oldFiles );
470 Set<String> newFileSet = arrayAsHashSet( newFiles );
471
472 List<String> added = new ArrayList<String>();
473 List<String> removed = new ArrayList<String>();
474
475 for ( String oldFile : oldFileSet )
476 {
477 if ( !newFileSet.contains( oldFile ) )
478 {
479 removed.add( oldFile );
480 }
481 }
482
483 for ( String newFile : newFileSet )
484 {
485 if ( !oldFileSet.contains( newFile ) )
486 {
487 added.add( newFile );
488 }
489 }
490
491 String[] filesAdded = added.toArray( new String[added.size()] );
492 String[] filesRemoved = removed.toArray( new String[removed.size()] );
493
494 return new DirectoryScanResult( filesAdded, filesRemoved );
495 }
496
497
498
499
500
501
502
503
504
505 private static <T> Set<T> arrayAsHashSet( @Nullable T[] array )
506 {
507 if ( array == null || array.length == 0 )
508 {
509 return Collections.emptySet();
510 }
511
512 Set<T> set = new HashSet<T>( array.length );
513 Collections.addAll( set, array );
514
515 return set;
516 }
517
518
519
520
521
522
523
524
525 void slowScan()
526 {
527 if ( haveSlowResults )
528 {
529 return;
530 }
531
532 final String[] excl = dirsExcluded.toArray( new String[dirsExcluded.size()] );
533
534 final String[] notIncl = dirsNotIncluded.toArray( new String[dirsNotIncluded.size()] );
535
536 for ( String anExcl : excl )
537 {
538 if ( !couldHoldIncluded( anExcl ) )
539 {
540 scandir( new File( basedir, anExcl ), anExcl + File.separator, false );
541 }
542 }
543
544 for ( String aNotIncl : notIncl )
545 {
546 if ( !couldHoldIncluded( aNotIncl ) )
547 {
548 scandir( new File( basedir, aNotIncl ), aNotIncl + File.separator, false );
549 }
550 }
551
552 haveSlowResults = true;
553 }
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572 void scandir( @Nonnull final File dir, @Nonnull final String vpath, final boolean fast )
573 {
574 String[] newfiles = dir.list();
575
576 if ( newfiles == null )
577 {
578
579
580
581
582
583
584
585
586
587
588 newfiles = new String[0];
589
590
591 }
592
593 if ( !followSymlinks )
594 {
595 newfiles = doNotFollowSymbolicLinks( dir, vpath, newfiles );
596 }
597
598 for ( final String newfile : newfiles )
599 {
600 final String name = vpath + newfile;
601 final File file = new File( dir, newfile );
602 if ( file.isDirectory() )
603 {
604 if ( isIncluded( name ) )
605 {
606 if ( !isExcluded( name ) )
607 {
608 if ( scanConductor != null )
609 {
610 scanAction = scanConductor.visitDirectory( name, file );
611
612 if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
613 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
614 {
615 return;
616 }
617 }
618
619 if ( !ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
620 {
621 dirsIncluded.add( name );
622 if ( fast )
623 {
624 scandir( file, name + File.separator, fast );
625
626 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
627 {
628 return;
629 }
630 }
631 }
632 scanAction = null;
633
634 }
635 else
636 {
637 dirsExcluded.add( name );
638 if ( fast && couldHoldIncluded( name ) )
639 {
640 scandir( file, name + File.separator, fast );
641 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
642 {
643 return;
644 }
645 scanAction = null;
646 }
647 }
648 }
649 else
650 {
651 if ( fast && couldHoldIncluded( name ) )
652 {
653 if ( scanConductor != null )
654 {
655 scanAction = scanConductor.visitDirectory( name, file );
656
657 if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
658 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
659 {
660 return;
661 }
662 }
663 if ( !ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
664 {
665 dirsNotIncluded.add( name );
666
667 scandir( file, name + File.separator, fast );
668 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
669 {
670 return;
671 }
672 }
673 scanAction = null;
674 }
675 }
676 if ( !fast )
677 {
678 scandir( file, name + File.separator, fast );
679 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
680 {
681 return;
682 }
683 scanAction = null;
684 }
685 }
686 else if ( file.isFile() )
687 {
688 if ( isIncluded( name ) )
689 {
690 if ( !isExcluded( name ) )
691 {
692 if ( scanConductor != null )
693 {
694 scanAction = scanConductor.visitFile( name, file );
695 }
696
697 if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
698 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
699 {
700 return;
701 }
702
703 filesIncluded.add( name );
704 }
705 else
706 {
707 filesExcluded.add( name );
708 }
709 }
710 else
711 {
712 filesNotIncluded.add( name );
713 }
714 }
715 }
716 }
717
718 private String[] doNotFollowSymbolicLinks( final File dir, final String vpath, String[] newfiles )
719 {
720 final List<String> noLinks = new ArrayList<String>();
721 for ( final String newfile : newfiles )
722 {
723 try
724 {
725 if ( isSymbolicLink( dir, newfile ) )
726 {
727 final String name = vpath + newfile;
728 final File file = new File( dir, newfile );
729 if ( file.isDirectory() )
730 {
731 dirsExcluded.add( name );
732 }
733 else
734 {
735 filesExcluded.add( name );
736 }
737 }
738 else
739 {
740 noLinks.add( newfile );
741 }
742 }
743 catch ( final IOException ioe )
744 {
745 final String msg =
746 "IOException caught while checking " + "for links, couldn't get cannonical path!";
747
748 System.err.println( msg );
749 noLinks.add( newfile );
750 }
751 }
752 newfiles = noLinks.toArray( new String[noLinks.size()] );
753 return newfiles;
754 }
755
756
757
758
759
760
761
762
763 boolean isIncluded( final String name )
764 {
765 return includesPatterns.matches( name, isCaseSensitive );
766 }
767
768
769
770
771
772
773
774
775 boolean couldHoldIncluded( @Nonnull final String name )
776 {
777 return includesPatterns.matchesPatternStart( name, isCaseSensitive );
778 }
779
780
781
782
783
784
785
786
787 boolean isExcluded( @Nonnull final String name )
788 {
789 return excludesPatterns.matches( name, isCaseSensitive );
790 }
791
792
793
794
795
796
797
798
799 public String[] getIncludedFiles()
800 {
801 if ( filesIncluded == null )
802 {
803 return new String[0];
804 }
805 return filesIncluded.toArray( new String[filesIncluded.size()] );
806 }
807
808
809
810
811
812
813
814
815 public String[] getNotIncludedFiles()
816 {
817 slowScan();
818 return filesNotIncluded.toArray( new String[filesNotIncluded.size()] );
819 }
820
821
822
823
824
825
826
827
828
829
830 public String[] getExcludedFiles()
831 {
832 slowScan();
833 return filesExcluded.toArray( new String[filesExcluded.size()] );
834 }
835
836
837
838
839
840
841
842
843 public String[] getIncludedDirectories()
844 {
845 return dirsIncluded.toArray( new String[dirsIncluded.size()] );
846 }
847
848
849
850
851
852
853
854
855 public String[] getNotIncludedDirectories()
856 {
857 slowScan();
858 return dirsNotIncluded.toArray( new String[dirsNotIncluded.size()] );
859 }
860
861
862
863
864
865
866
867
868
869
870 public String[] getExcludedDirectories()
871 {
872 slowScan();
873 return dirsExcluded.toArray( new String[dirsExcluded.size()] );
874 }
875
876
877
878
879 public void addDefaultExcludes()
880 {
881 final int excludesLength = excludes == null ? 0 : excludes.length;
882 String[] newExcludes;
883 newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length];
884 if ( excludesLength > 0 )
885 {
886 System.arraycopy( excludes, 0, newExcludes, 0, excludesLength );
887 }
888 for ( int i = 0; i < DEFAULTEXCLUDES.length; i++ )
889 {
890 newExcludes[i + excludesLength] =
891 DEFAULTEXCLUDES[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
892 }
893 excludes = newExcludes;
894 }
895
896
897
898
899
900
901
902
903
904
905
906
907 boolean isSymbolicLink( final File parent, final String name )
908 throws IOException
909 {
910 if ( Java7Support.isAtLeastJava7() )
911 {
912 return Java7Support.isSymLink( parent );
913 }
914 final File resolvedParent = new File( parent.getCanonicalPath() );
915 final File toTest = new File( resolvedParent, name );
916 return !toTest.getAbsolutePath().equals( toTest.getCanonicalPath() );
917 }
918
919 private void setupDefaultFilters()
920 {
921 if ( includes == null )
922 {
923
924 includes = new String[1];
925 includes[0] = "**";
926 }
927 if ( excludes == null )
928 {
929 excludes = new String[0];
930 }
931 }
932
933
934 private void setupMatchPatterns()
935 {
936 includesPatterns = MatchPatterns.from( includes );
937 excludesPatterns = MatchPatterns.from( excludes );
938 }
939
940 }