1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.io.file;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.io.RandomAccessFile;
25 import java.math.BigInteger;
26 import java.net.URI;
27 import java.net.URISyntaxException;
28 import java.net.URL;
29 import java.nio.charset.Charset;
30 import java.nio.file.AccessDeniedException;
31 import java.nio.file.CopyOption;
32 import java.nio.file.DirectoryStream;
33 import java.nio.file.FileSystem;
34 import java.nio.file.FileVisitOption;
35 import java.nio.file.FileVisitResult;
36 import java.nio.file.FileVisitor;
37 import java.nio.file.Files;
38 import java.nio.file.LinkOption;
39 import java.nio.file.NoSuchFileException;
40 import java.nio.file.NotDirectoryException;
41 import java.nio.file.OpenOption;
42 import java.nio.file.Path;
43 import java.nio.file.Paths;
44 import java.nio.file.StandardOpenOption;
45 import java.nio.file.attribute.AclEntry;
46 import java.nio.file.attribute.AclFileAttributeView;
47 import java.nio.file.attribute.BasicFileAttributes;
48 import java.nio.file.attribute.DosFileAttributeView;
49 import java.nio.file.attribute.DosFileAttributes;
50 import java.nio.file.attribute.FileAttribute;
51 import java.nio.file.attribute.FileTime;
52 import java.nio.file.attribute.PosixFileAttributeView;
53 import java.nio.file.attribute.PosixFileAttributes;
54 import java.nio.file.attribute.PosixFilePermission;
55 import java.time.Duration;
56 import java.time.Instant;
57 import java.time.chrono.ChronoZonedDateTime;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collection;
61 import java.util.Collections;
62 import java.util.Comparator;
63 import java.util.EnumSet;
64 import java.util.HashSet;
65 import java.util.Iterator;
66 import java.util.List;
67 import java.util.Objects;
68 import java.util.Set;
69 import java.util.function.Function;
70 import java.util.stream.Collector;
71 import java.util.stream.Collectors;
72 import java.util.stream.Stream;
73 import java.util.stream.StreamSupport;
74
75 import org.apache.commons.io.Charsets;
76 import org.apache.commons.io.FileUtils;
77 import org.apache.commons.io.FilenameUtils;
78 import org.apache.commons.io.IOUtils;
79 import org.apache.commons.io.RandomAccessFileMode;
80 import org.apache.commons.io.RandomAccessFiles;
81 import org.apache.commons.io.ThreadUtils;
82 import org.apache.commons.io.file.Counters.PathCounters;
83 import org.apache.commons.io.file.attribute.FileTimes;
84 import org.apache.commons.io.filefilter.IOFileFilter;
85 import org.apache.commons.io.function.IOFunction;
86 import org.apache.commons.io.function.IOSupplier;
87
88
89
90
91
92
93 public final class PathUtils {
94
95
96
97
98 private static final class RelativeSortedPaths {
99
100
101
102
103
104
105
106
107 private static boolean equalsIgnoreFileSystem(final List<Path> list1, final List<Path> list2) {
108 if (list1.size() != list2.size()) {
109 return false;
110 }
111
112 final Iterator<Path> iterator1 = list1.iterator();
113 final Iterator<Path> iterator2 = list2.iterator();
114 while (iterator1.hasNext() && iterator2.hasNext()) {
115 if (!equalsIgnoreFileSystem(iterator1.next(), iterator2.next())) {
116 return false;
117 }
118 }
119 return true;
120 }
121
122 private static boolean equalsIgnoreFileSystem(final Path path1, final Path path2) {
123 final FileSystem fileSystem1 = path1.getFileSystem();
124 final FileSystem fileSystem2 = path2.getFileSystem();
125 if (fileSystem1 == fileSystem2) {
126 return path1.equals(path2);
127 }
128 final String separator1 = fileSystem1.getSeparator();
129 final String separator2 = fileSystem2.getSeparator();
130 final String string1 = path1.toString();
131 final String string2 = path2.toString();
132 if (Objects.equals(separator1, separator2)) {
133
134 return Objects.equals(string1, string2);
135 }
136
137 return extractKey(separator1, string1).equals(extractKey(separator2, string2));
138 }
139
140 static String extractKey(final String separator, final String string) {
141
142 return string.replaceAll("\\" + separator, ">");
143 }
144
145 final boolean equals;
146
147
148 final List<Path> relativeFileList1;
149 final List<Path> relativeFileList2;
150
151
152
153
154
155
156
157
158
159
160
161 private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth, final LinkOption[] linkOptions,
162 final FileVisitOption[] fileVisitOptions) throws IOException {
163 final List<Path> tmpRelativeDirList1;
164 final List<Path> tmpRelativeDirList2;
165 List<Path> tmpRelativeFileList1 = null;
166 List<Path> tmpRelativeFileList2 = null;
167 if (dir1 == null && dir2 == null) {
168 equals = true;
169 } else if (dir1 == null ^ dir2 == null) {
170 equals = false;
171 } else {
172 final boolean parentDirNotExists1 = Files.notExists(dir1, linkOptions);
173 final boolean parentDirNotExists2 = Files.notExists(dir2, linkOptions);
174 if (parentDirNotExists1 || parentDirNotExists2) {
175 equals = parentDirNotExists1 && parentDirNotExists2;
176 } else {
177 final AccumulatorPathVisitor visitor1 = accumulate(dir1, maxDepth, fileVisitOptions);
178 final AccumulatorPathVisitor visitor2 = accumulate(dir2, maxDepth, fileVisitOptions);
179 if (visitor1.getDirList().size() != visitor2.getDirList().size() || visitor1.getFileList().size() != visitor2.getFileList().size()) {
180 equals = false;
181 } else {
182 tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null);
183 tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null);
184 if (!equalsIgnoreFileSystem(tmpRelativeDirList1, tmpRelativeDirList2)) {
185 equals = false;
186 } else {
187 tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null);
188 tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null);
189 equals = equalsIgnoreFileSystem(tmpRelativeFileList1, tmpRelativeFileList2);
190 }
191 }
192 }
193 }
194
195
196 relativeFileList1 = tmpRelativeFileList1;
197 relativeFileList2 = tmpRelativeFileList2;
198 }
199 }
200
201 private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING };
202 private static final OpenOption[] OPEN_OPTIONS_APPEND = { StandardOpenOption.CREATE, StandardOpenOption.APPEND };
203
204
205
206
207
208 public static final CopyOption[] EMPTY_COPY_OPTIONS = {};
209
210
211
212
213
214 public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {};
215
216
217
218
219
220 public static final FileAttribute<?>[] EMPTY_FILE_ATTRIBUTE_ARRAY = {};
221
222
223
224 public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = {};
225
226
227
228 public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {};
229
230
231
232
233
234
235 @Deprecated
236 public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = { LinkOption.NOFOLLOW_LINKS };
237
238
239
240
241
242 static final LinkOption NULL_LINK_OPTION = null;
243
244
245
246 public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {};
247
248
249
250
251
252 public static final Path[] EMPTY_PATH_ARRAY = {};
253
254
255
256
257
258
259
260
261
262
263 private static AccumulatorPathVisitor accumulate(final Path directory, final int maxDepth, final FileVisitOption[] fileVisitOptions) throws IOException {
264 return visitFileTree(AccumulatorPathVisitor.builder().setDirectoryPostTransformer(PathUtils::stripTrailingSeparator).get(), directory,
265 toFileVisitOptionSet(fileVisitOptions), maxDepth);
266 }
267
268
269
270
271
272
273
274
275 public static PathCounters cleanDirectory(final Path directory) throws IOException {
276 return cleanDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
277 }
278
279
280
281
282
283
284
285
286
287
288 public static PathCounters cleanDirectory(final Path directory, final DeleteOption... deleteOptions) throws IOException {
289 return visitFileTree(new CleaningPathVisitor(Counters.longPathCounters(), deleteOptions), directory).getPathCounters();
290 }
291
292
293
294
295
296
297
298
299
300
301
302 private static int compareLastModifiedTimeTo(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
303 return getLastModifiedTime(file, options).compareTo(fileTime);
304 }
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327 public static boolean contentEquals(final FileSystem fileSystem1, final FileSystem fileSystem2) throws IOException {
328 if (Objects.equals(fileSystem1, fileSystem2)) {
329 return true;
330 }
331 final List<Path> sortedList1 = toSortedList(fileSystem1.getRootDirectories());
332 final List<Path> sortedList2 = toSortedList(fileSystem2.getRootDirectories());
333 if (sortedList1.size() != sortedList2.size()) {
334 return false;
335 }
336 for (int i = 0; i < sortedList1.size(); i++) {
337 if (!directoryAndFileContentEquals(sortedList1.get(i), sortedList2.get(i))) {
338 return false;
339 }
340 }
341 return true;
342 }
343
344
345
346
347
348
349
350
351
352
353
354 public static long copy(final IOSupplier<InputStream> in, final Path target, final CopyOption... copyOptions) throws IOException {
355 try (InputStream inputStream = in.get()) {
356 return Files.copy(inputStream, target, copyOptions);
357 }
358 }
359
360
361
362
363
364
365
366
367
368
369 public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
370 final Path absoluteSource = sourceDirectory.toAbsolutePath();
371 return visitFileTree(new CopyDirectoryVisitor(Counters.longPathCounters(), absoluteSource, targetDirectory, copyOptions), absoluteSource)
372 .getPathCounters();
373 }
374
375
376
377
378
379
380
381
382
383
384
385 public static Path copyFile(final URL sourceFile, final Path targetFile, final CopyOption... copyOptions) throws IOException {
386 copy(sourceFile::openStream, targetFile, copyOptions);
387 return targetFile;
388 }
389
390
391
392
393
394
395
396
397
398
399
400 public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
401
402 final Path sourceFileName = Objects.requireNonNull(sourceFile.getFileName(), "source file name");
403 final Path targetFile = resolve(targetDirectory, sourceFileName);
404 return Files.copy(sourceFile, targetFile, copyOptions);
405 }
406
407
408
409
410
411
412
413
414
415
416
417 public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
418 final Path resolve = targetDirectory.resolve(FilenameUtils.getName(sourceFile.getFile()));
419 copy(sourceFile::openStream, resolve, copyOptions);
420 return resolve;
421 }
422
423
424
425
426
427
428
429
430 public static PathCounters countDirectory(final Path directory) throws IOException {
431 return visitFileTree(CountingPathVisitor.withLongCounters(), directory).getPathCounters();
432 }
433
434
435
436
437
438
439
440
441
442 public static PathCounters countDirectoryAsBigInteger(final Path directory) throws IOException {
443 return visitFileTree(CountingPathVisitor.withBigIntegerCounters(), directory).getPathCounters();
444 }
445
446
447
448
449
450
451
452
453
454
455
456
457
458 public static Path createParentDirectories(final Path path, final FileAttribute<?>... attrs) throws IOException {
459 return createParentDirectories(path, LinkOption.NOFOLLOW_LINKS, attrs);
460 }
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475 public static Path createParentDirectories(final Path path, final LinkOption linkOption, final FileAttribute<?>... attrs) throws IOException {
476 Path parent = getParent(path);
477 parent = linkOption == LinkOption.NOFOLLOW_LINKS ? parent : readIfSymbolicLink(parent);
478 if (parent == null) {
479 return null;
480 }
481 final boolean exists = linkOption == null ? Files.exists(parent) : Files.exists(parent, linkOption);
482 return exists ? parent : Files.createDirectories(parent, attrs);
483 }
484
485
486
487
488
489
490
491 public static Path current() {
492 return Paths.get(".");
493 }
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510 public static PathCounters delete(final Path path) throws IOException {
511 return delete(path, EMPTY_DELETE_OPTION_ARRAY);
512 }
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531 public static PathCounters delete(final Path path, final DeleteOption... deleteOptions) throws IOException {
532
533 return Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) ? deleteDirectory(path, deleteOptions) : deleteFile(path, deleteOptions);
534 }
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554 public static PathCounters delete(final Path path, final LinkOption[] linkOptions, final DeleteOption... deleteOptions) throws IOException {
555
556 return Files.isDirectory(path, linkOptions) ? deleteDirectory(path, linkOptions, deleteOptions) : deleteFile(path, linkOptions, deleteOptions);
557 }
558
559
560
561
562
563
564
565
566 public static PathCounters deleteDirectory(final Path directory) throws IOException {
567 return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
568 }
569
570
571
572
573
574
575
576
577
578
579 public static PathCounters deleteDirectory(final Path directory, final DeleteOption... deleteOptions) throws IOException {
580 final LinkOption[] linkOptions = noFollowLinkOptionArray();
581
582 return withPosixFileAttributes(getParent(directory), linkOptions, overrideReadOnly(deleteOptions),
583 pfa -> visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters());
584 }
585
586
587
588
589
590
591
592
593
594
595
596 public static PathCounters deleteDirectory(final Path directory, final LinkOption[] linkOptions, final DeleteOption... deleteOptions) throws IOException {
597 return visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters();
598 }
599
600
601
602
603
604
605
606
607
608 public static PathCounters deleteFile(final Path file) throws IOException {
609 return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY);
610 }
611
612
613
614
615
616
617
618
619
620
621
622 public static PathCounters deleteFile(final Path file, final DeleteOption... deleteOptions) throws IOException {
623
624 return deleteFile(file, noFollowLinkOptionArray(), deleteOptions);
625 }
626
627
628
629
630
631
632
633
634
635
636
637
638 public static PathCounters deleteFile(final Path file, final LinkOption[] linkOptions, final DeleteOption... deleteOptions)
639 throws NoSuchFileException, IOException {
640
641
642
643 if (Files.isDirectory(file, linkOptions)) {
644 throw new NoSuchFileException(file.toString());
645 }
646 final PathCounters pathCounts = Counters.longPathCounters();
647 boolean exists = exists(file, linkOptions);
648 long size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
649 try {
650 if (Files.deleteIfExists(file)) {
651 pathCounts.getFileCounter().increment();
652 pathCounts.getByteCounter().add(size);
653 return pathCounts;
654 }
655 } catch (final AccessDeniedException ignored) {
656
657 }
658 final Path parent = getParent(file);
659 PosixFileAttributes posixFileAttributes = null;
660 try {
661 if (overrideReadOnly(deleteOptions)) {
662 posixFileAttributes = readPosixFileAttributes(parent, linkOptions);
663 setReadOnly(file, false, linkOptions);
664 }
665
666 exists = exists(file, linkOptions);
667 size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
668 if (Files.deleteIfExists(file)) {
669 pathCounts.getFileCounter().increment();
670 pathCounts.getByteCounter().add(size);
671 }
672 } finally {
673 if (posixFileAttributes != null) {
674 Files.setPosixFilePermissions(parent, posixFileAttributes.permissions());
675 }
676 }
677 return pathCounts;
678 }
679
680
681
682
683
684
685
686 public static void deleteOnExit(final Path path) {
687 Objects.requireNonNull(path).toFile().deleteOnExit();
688 }
689
690
691
692
693
694
695
696
697
698
699 public static boolean directoryAndFileContentEquals(final Path path1, final Path path2) throws IOException {
700 return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
701 }
702
703
704
705
706
707
708
709
710
711
712
713
714
715 public static boolean directoryAndFileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions,
716 final FileVisitOption[] fileVisitOption) throws IOException {
717
718 if (path1 == null && path2 == null) {
719 return true;
720 }
721 if (path1 == null || path2 == null) {
722 return false;
723 }
724 if (notExists(path1) && notExists(path2)) {
725 return true;
726 }
727 final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE, linkOptions, fileVisitOption);
728
729 if (!relativeSortedPaths.equals) {
730 return false;
731 }
732
733 final List<Path> fileList1 = relativeSortedPaths.relativeFileList1;
734 final List<Path> fileList2 = relativeSortedPaths.relativeFileList2;
735 final boolean sameFileSystem = isSameFileSystem(path1, path2);
736 for (final Path path : fileList1) {
737 final int binarySearch = sameFileSystem ? Collections.binarySearch(fileList2, path)
738 : Collections.binarySearch(fileList2, path,
739 Comparator.comparing(p -> RelativeSortedPaths.extractKey(p.getFileSystem().getSeparator(), p.toString())));
740 if (binarySearch < 0) {
741 throw new IllegalStateException("Unexpected mismatch.");
742 }
743 if (sameFileSystem && !fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) {
744 return false;
745 }
746 if (!fileContentEquals(path1.resolve(path.toString()), path2.resolve(path.toString()), linkOptions, openOptions)) {
747 return false;
748 }
749 }
750 return true;
751 }
752
753
754
755
756
757
758
759
760
761
762 public static boolean directoryContentEquals(final Path path1, final Path path2) throws IOException {
763 return directoryContentEquals(path1, path2, Integer.MAX_VALUE, EMPTY_LINK_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
764 }
765
766
767
768
769
770
771
772
773
774
775
776
777
778 public static boolean directoryContentEquals(final Path path1, final Path path2, final int maxDepth, final LinkOption[] linkOptions,
779 final FileVisitOption[] fileVisitOptions) throws IOException {
780 return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals;
781 }
782
783 private static boolean exists(final Path path, final LinkOption... options) {
784 return path != null && (options != null ? Files.exists(path, options) : Files.exists(path));
785 }
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800 public static boolean fileContentEquals(final Path path1, final Path path2) throws IOException {
801 return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY);
802 }
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819 public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions)
820 throws IOException {
821 if (path1 == null && path2 == null) {
822 return true;
823 }
824 if (path1 == null || path2 == null) {
825 return false;
826 }
827 final Path nPath1 = path1.normalize();
828 final Path nPath2 = path2.normalize();
829 final boolean path1Exists = exists(nPath1, linkOptions);
830 if (path1Exists != exists(nPath2, linkOptions)) {
831 return false;
832 }
833 if (!path1Exists) {
834
835
836 return true;
837 }
838 if (Files.isDirectory(nPath1, linkOptions)) {
839
840 throw new IOException("Can't compare directories, only files: " + nPath1);
841 }
842 if (Files.isDirectory(nPath2, linkOptions)) {
843
844 throw new IOException("Can't compare directories, only files: " + nPath2);
845 }
846 if (Files.size(nPath1) != Files.size(nPath2)) {
847
848 return false;
849 }
850 if (isSameFileSystem(path1, path2) && path1.equals(path2)) {
851
852 return true;
853 }
854
855 try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(path1.toRealPath(linkOptions));
856 RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(path2.toRealPath(linkOptions))) {
857 return RandomAccessFiles.contentEquals(raf1, raf2);
858 } catch (final UnsupportedOperationException e) {
859
860
861
862
863 try (InputStream inputStream1 = Files.newInputStream(nPath1, openOptions);
864 InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) {
865 return IOUtils.contentEquals(inputStream1, inputStream2);
866 }
867 }
868 }
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893 public static Path[] filter(final PathFilter filter, final Path... paths) {
894 Objects.requireNonNull(filter, "filter");
895 if (paths == null) {
896 return EMPTY_PATH_ARRAY;
897 }
898 return filterPaths(filter, Stream.of(paths), Collectors.toList()).toArray(EMPTY_PATH_ARRAY);
899 }
900
901 private static <R, A> R filterPaths(final PathFilter filter, final Stream<Path> stream, final Collector<? super Path, A, R> collector) {
902 Objects.requireNonNull(filter, "filter");
903 Objects.requireNonNull(collector, "collector");
904 if (stream == null) {
905 return Stream.<Path>empty().collect(collector);
906 }
907 return stream.filter(p -> {
908 try {
909 return p != null && filter.accept(p, readBasicFileAttributes(p)) == FileVisitResult.CONTINUE;
910 } catch (final IOException e) {
911 return false;
912 }
913 }).collect(collector);
914 }
915
916
917
918
919
920
921
922
923
924 public static List<AclEntry> getAclEntryList(final Path sourcePath) throws IOException {
925 final AclFileAttributeView fileAttributeView = getAclFileAttributeView(sourcePath);
926 return fileAttributeView == null ? null : fileAttributeView.getAcl();
927 }
928
929
930
931
932
933
934
935
936
937 public static AclFileAttributeView getAclFileAttributeView(final Path path, final LinkOption... options) {
938 return Files.getFileAttributeView(path, AclFileAttributeView.class, options);
939 }
940
941
942
943
944
945
946
947
948
949
950
951 public static String getBaseName(final Path path) {
952 if (path == null) {
953 return null;
954 }
955 final Path fileName = path.getFileName();
956 return fileName != null ? FilenameUtils.removeExtension(fileName.toString()) : null;
957 }
958
959
960
961
962
963
964
965
966
967 public static DosFileAttributeView getDosFileAttributeView(final Path path, final LinkOption... options) {
968 return Files.getFileAttributeView(path, DosFileAttributeView.class, options);
969 }
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991 public static String getExtension(final Path path) {
992 final String fileName = getFileNameString(path);
993 return fileName != null ? FilenameUtils.getExtension(fileName) : null;
994 }
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006 public static <R> R getFileName(final Path path, final Function<Path, R> function) {
1007 final Path fileName = path != null ? path.getFileName() : null;
1008 return fileName != null ? function.apply(fileName) : null;
1009 }
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019 public static String getFileNameString(final Path path) {
1020 return getFileName(path, Path::toString);
1021 }
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035 public static FileTime getLastModifiedFileTime(final File file) throws IOException {
1036 return getLastModifiedFileTime(file.toPath(), null, EMPTY_LINK_OPTION_ARRAY);
1037 }
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049 public static FileTime getLastModifiedFileTime(final Path path, final FileTime defaultIfAbsent, final LinkOption... options) throws IOException {
1050 return Files.exists(path) ? getLastModifiedTime(path, options) : defaultIfAbsent;
1051 }
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062 public static FileTime getLastModifiedFileTime(final Path path, final LinkOption... options) throws IOException {
1063 return getLastModifiedFileTime(path, null, options);
1064 }
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074 public static FileTime getLastModifiedFileTime(final URI uri) throws IOException {
1075 return getLastModifiedFileTime(Paths.get(uri), null, EMPTY_LINK_OPTION_ARRAY);
1076 }
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087 public static FileTime getLastModifiedFileTime(final URL url) throws IOException, URISyntaxException {
1088 return getLastModifiedFileTime(url.toURI());
1089 }
1090
1091 private static FileTime getLastModifiedTime(final Path path, final LinkOption... options) throws IOException {
1092 return Files.getLastModifiedTime(Objects.requireNonNull(path, "path"), options);
1093 }
1094
1095 private static Path getParent(final Path path) {
1096 return path == null ? null : path.getParent();
1097 }
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107 public static PosixFileAttributeView getPosixFileAttributeView(final Path path, final LinkOption... options) {
1108 return Files.getFileAttributeView(path, PosixFileAttributeView.class, options);
1109 }
1110
1111
1112
1113
1114
1115
1116
1117 public static Path getTempDirectory() {
1118 return Paths.get(FileUtils.getTempDirectoryPath());
1119 }
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133 public static boolean isDirectory(final Path path, final LinkOption... options) {
1134 return path != null && Files.isDirectory(path, options);
1135 }
1136
1137
1138
1139
1140
1141
1142
1143
1144 public static boolean isEmpty(final Path path) throws IOException {
1145 return Files.isDirectory(path) ? isEmptyDirectory(path) : isEmptyFile(path);
1146 }
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158 public static boolean isEmptyDirectory(final Path directory) throws IOException {
1159 try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
1160 return !directoryStream.iterator().hasNext();
1161 }
1162 }
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173 public static boolean isEmptyFile(final Path file) throws IOException {
1174 return Files.size(file) <= 0;
1175 }
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188 public static boolean isNewer(final Path file, final ChronoZonedDateTime<?> czdt, final LinkOption... options) throws IOException {
1189 Objects.requireNonNull(czdt, "czdt");
1190 return isNewer(file, czdt.toInstant(), options);
1191 }
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204 public static boolean isNewer(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
1205 if (notExists(file)) {
1206 return false;
1207 }
1208 return compareLastModifiedTimeTo(file, fileTime, options) > 0;
1209 }
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222 public static boolean isNewer(final Path file, final Instant instant, final LinkOption... options) throws IOException {
1223 return isNewer(file, FileTime.from(instant), options);
1224 }
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237 public static boolean isNewer(final Path file, final long timeMillis, final LinkOption... options) throws IOException {
1238 return isNewer(file, FileTime.fromMillis(timeMillis), options);
1239 }
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250 public static boolean isNewer(final Path file, final Path reference) throws IOException {
1251 return isNewer(file, getLastModifiedTime(reference));
1252 }
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265 public static boolean isOlder(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
1266 if (notExists(file)) {
1267 return false;
1268 }
1269 return compareLastModifiedTimeTo(file, fileTime, options) < 0;
1270 }
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283 public static boolean isOlder(final Path file, final Instant instant, final LinkOption... options) throws IOException {
1284 return isOlder(file, FileTime.from(instant), options);
1285 }
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298 public static boolean isOlder(final Path file, final long timeMillis, final LinkOption... options) throws IOException {
1299 return isOlder(file, FileTime.fromMillis(timeMillis), options);
1300 }
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311 public static boolean isOlder(final Path file, final Path reference) throws IOException {
1312 return isOlder(file, getLastModifiedTime(reference));
1313 }
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323 public static boolean isPosix(final Path test, final LinkOption... options) {
1324 return exists(test, options) && readPosixFileAttributes(test, options) != null;
1325 }
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339 public static boolean isRegularFile(final Path path, final LinkOption... options) {
1340 return path != null && Files.isRegularFile(path, options);
1341 }
1342
1343 static boolean isSameFileSystem(final Path path1, final Path path2) {
1344 return path1.getFileSystem() == path2.getFileSystem();
1345 }
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359 public static DirectoryStream<Path> newDirectoryStream(final Path dir, final PathFilter pathFilter) throws IOException {
1360 return Files.newDirectoryStream(dir, new DirectoryStreamFilter(pathFilter));
1361 }
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373 public static OutputStream newOutputStream(final Path path, final boolean append) throws IOException {
1374 return newOutputStream(path, EMPTY_LINK_OPTION_ARRAY, append ? OPEN_OPTIONS_APPEND : OPEN_OPTIONS_TRUNCATE);
1375 }
1376
1377 static OutputStream newOutputStream(final Path path, final LinkOption[] linkOptions, final OpenOption... openOptions) throws IOException {
1378 if (!exists(path, linkOptions)) {
1379 createParentDirectories(path, linkOptions != null && linkOptions.length > 0 ? linkOptions[0] : NULL_LINK_OPTION);
1380 }
1381 final List<OpenOption> list = new ArrayList<>(Arrays.asList(openOptions != null ? openOptions : EMPTY_OPEN_OPTION_ARRAY));
1382 list.addAll(Arrays.asList(linkOptions != null ? linkOptions : EMPTY_LINK_OPTION_ARRAY));
1383 return Files.newOutputStream(path, list.toArray(EMPTY_OPEN_OPTION_ARRAY));
1384 }
1385
1386
1387
1388
1389
1390
1391 public static LinkOption[] noFollowLinkOptionArray() {
1392 return NOFOLLOW_LINK_OPTION_ARRAY.clone();
1393 }
1394
1395 private static boolean notExists(final Path path, final LinkOption... options) {
1396 return Files.notExists(Objects.requireNonNull(path, "path"), options);
1397 }
1398
1399
1400
1401
1402
1403
1404
1405 private static boolean overrideReadOnly(final DeleteOption... deleteOptions) {
1406 if (deleteOptions == null) {
1407 return false;
1408 }
1409 return Stream.of(deleteOptions).anyMatch(e -> e == StandardDeleteOption.OVERRIDE_READ_ONLY);
1410 }
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423 public static <A extends BasicFileAttributes> A readAttributes(final Path path, final Class<A> type, final LinkOption... options) {
1424 try {
1425 return path == null ? null : Files.readAttributes(path, type, options);
1426 } catch (final UnsupportedOperationException | IOException e) {
1427
1428 return null;
1429 }
1430 }
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440 public static BasicFileAttributes readBasicFileAttributes(final Path path) throws IOException {
1441 return Files.readAttributes(path, BasicFileAttributes.class);
1442 }
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452 public static BasicFileAttributes readBasicFileAttributes(final Path path, final LinkOption... options) {
1453 return readAttributes(path, BasicFileAttributes.class, options);
1454 }
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464 @Deprecated
1465 public static BasicFileAttributes readBasicFileAttributesUnchecked(final Path path) {
1466 return readBasicFileAttributes(path, EMPTY_LINK_OPTION_ARRAY);
1467 }
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477 public static DosFileAttributes readDosFileAttributes(final Path path, final LinkOption... options) {
1478 return readAttributes(path, DosFileAttributes.class, options);
1479 }
1480
1481 private static Path readIfSymbolicLink(final Path path) throws IOException {
1482 return path != null ? Files.isSymbolicLink(path) ? Files.readSymbolicLink(path) : path : null;
1483 }
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493 public static BasicFileAttributes readOsFileAttributes(final Path path, final LinkOption... options) {
1494 final PosixFileAttributes fileAttributes = readPosixFileAttributes(path, options);
1495 return fileAttributes != null ? fileAttributes : readDosFileAttributes(path, options);
1496 }
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506 public static PosixFileAttributes readPosixFileAttributes(final Path path, final LinkOption... options) {
1507 return readAttributes(path, PosixFileAttributes.class, options);
1508 }
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521 public static String readString(final Path path, final Charset charset) throws IOException {
1522 return new String(Files.readAllBytes(path), Charsets.toCharset(charset));
1523 }
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534 static List<Path> relativize(final Collection<Path> collection, final Path parent, final boolean sort, final Comparator<? super Path> comparator) {
1535 Stream<Path> stream = collection.stream().map(parent::relativize);
1536 if (sort) {
1537 stream = comparator == null ? stream.sorted() : stream.sorted(comparator);
1538 }
1539 return stream.collect(Collectors.toList());
1540 }
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552 private static Path requireExists(final Path file, final String fileParamName, final LinkOption... options) {
1553 Objects.requireNonNull(file, fileParamName);
1554 if (!exists(file, options)) {
1555 throw new IllegalArgumentException("File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'");
1556 }
1557 return file;
1558 }
1559
1560 static Path resolve(final Path targetDirectory, final Path otherPath) {
1561 final FileSystem fileSystemTarget = targetDirectory.getFileSystem();
1562 final FileSystem fileSystemSource = otherPath.getFileSystem();
1563 if (fileSystemTarget == fileSystemSource) {
1564 return targetDirectory.resolve(otherPath);
1565 }
1566 final String separatorSource = fileSystemSource.getSeparator();
1567 final String separatorTarget = fileSystemTarget.getSeparator();
1568 final String otherString = otherPath.toString();
1569 return targetDirectory.resolve(Objects.equals(separatorSource, separatorTarget) ? otherString : otherString.replace(separatorSource, separatorTarget));
1570 }
1571
1572 private static boolean setDosReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
1573 final DosFileAttributeView dosFileAttributeView = getDosFileAttributeView(path, linkOptions);
1574 if (dosFileAttributeView != null) {
1575 dosFileAttributeView.setReadOnly(readOnly);
1576 return true;
1577 }
1578 return false;
1579 }
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591 public static void setLastModifiedTime(final Path sourceFile, final Path targetFile) throws IOException {
1592 Objects.requireNonNull(sourceFile, "sourceFile");
1593 Files.setLastModifiedTime(targetFile, getLastModifiedTime(sourceFile));
1594 }
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605 private static boolean setPosixDeletePermissions(final Path parent, final boolean enableDeleteChildren, final LinkOption... linkOptions)
1606 throws IOException {
1607
1608
1609 return setPosixPermissions(parent, enableDeleteChildren, Arrays.asList(
1610 PosixFilePermission.OWNER_WRITE,
1611
1612
1613 PosixFilePermission.OWNER_EXECUTE
1614
1615
1616 ), linkOptions);
1617
1618 }
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633 private static boolean setPosixPermissions(final Path path, final boolean addPermissions, final List<PosixFilePermission> updatePermissions,
1634 final LinkOption... linkOptions) throws IOException {
1635 if (path != null) {
1636 final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
1637 final Set<PosixFilePermission> newPermissions = new HashSet<>(permissions);
1638 if (addPermissions) {
1639 newPermissions.addAll(updatePermissions);
1640 } else {
1641 newPermissions.removeAll(updatePermissions);
1642 }
1643 if (!newPermissions.equals(permissions)) {
1644 Files.setPosixFilePermissions(path, newPermissions);
1645 }
1646 return true;
1647 }
1648 return false;
1649 }
1650
1651 private static void setPosixReadOnlyFile(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
1652
1653 final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
1654
1655 final List<PosixFilePermission> readPermissions = Arrays.asList(
1656 PosixFilePermission.OWNER_READ
1657
1658
1659 );
1660 final List<PosixFilePermission> writePermissions = Arrays.asList(
1661 PosixFilePermission.OWNER_WRITE
1662
1663
1664 );
1665
1666 if (readOnly) {
1667
1668 permissions.addAll(readPermissions);
1669 permissions.removeAll(writePermissions);
1670 } else {
1671
1672 permissions.addAll(readPermissions);
1673 permissions.addAll(writePermissions);
1674 }
1675 Files.setPosixFilePermissions(path, permissions);
1676 }
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691 public static Path setReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
1692 try {
1693
1694 if (setDosReadOnly(path, readOnly, linkOptions)) {
1695 return path;
1696 }
1697 } catch (final IOException ignored) {
1698
1699 }
1700 final Path parent = getParent(path);
1701 if (!isPosix(parent, linkOptions)) {
1702 throw new IOException(String.format("DOS or POSIX file operations not available for '%s', linkOptions %s", path, Arrays.toString(linkOptions)));
1703 }
1704
1705 if (readOnly) {
1706
1707
1708 setPosixReadOnlyFile(path, readOnly, linkOptions);
1709 setPosixDeletePermissions(parent, false, linkOptions);
1710 } else {
1711
1712
1713 setPosixDeletePermissions(parent, true, linkOptions);
1714 }
1715 return path;
1716 }
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733 public static long sizeOf(final Path path) throws IOException {
1734 requireExists(path, "path");
1735 return Files.isDirectory(path) ? sizeOfDirectory(path) : Files.size(path);
1736 }
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749 public static BigInteger sizeOfAsBigInteger(final Path path) throws IOException {
1750 requireExists(path, "path");
1751 return Files.isDirectory(path) ? sizeOfDirectoryAsBigInteger(path) : BigInteger.valueOf(Files.size(path));
1752 }
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767 public static long sizeOfDirectory(final Path directory) throws IOException {
1768 return countDirectory(directory).getByteCounter().getLong();
1769 }
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780 public static BigInteger sizeOfDirectoryAsBigInteger(final Path directory) throws IOException {
1781 return countDirectoryAsBigInteger(directory).getByteCounter().getBigInteger();
1782 }
1783
1784 private static Path stripTrailingSeparator(final Path dir) {
1785 final String separator = dir.getFileSystem().getSeparator();
1786 final String fileName = getFileNameString(dir);
1787 return fileName != null && fileName.endsWith(separator) ? dir.resolveSibling(fileName.substring(0, fileName.length() - 1)) : dir;
1788 }
1789
1790
1791
1792
1793
1794
1795
1796 static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVisitOptions) {
1797 return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class) : Stream.of(fileVisitOptions).collect(Collectors.toSet());
1798 }
1799
1800 private static <T> List<T> toList(final Iterable<T> iterable) {
1801 return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
1802 }
1803
1804 private static List<Path> toSortedList(final Iterable<Path> rootDirectories) {
1805 final List<Path> list = toList(rootDirectories);
1806 Collections.sort(list);
1807 return list;
1808 }
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820 public static Path touch(final Path file) throws IOException {
1821 Objects.requireNonNull(file, "file");
1822 if (!Files.exists(file)) {
1823 createParentDirectories(file);
1824 Files.createFile(file);
1825 } else {
1826 FileTimes.setLastModifiedTime(file);
1827 }
1828 return file;
1829 }
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path directory) throws IOException {
1845 Files.walkFileTree(directory, visitor);
1846 return visitor;
1847 }
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path start, final Set<FileVisitOption> options,
1863 final int maxDepth) throws IOException {
1864 Files.walkFileTree(start, options, maxDepth, visitor);
1865 return visitor;
1866 }
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final String first, final String... more) throws IOException {
1881 return visitFileTree(visitor, Paths.get(first, more));
1882 }
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final URI uri) throws IOException {
1896 return visitFileTree(visitor, Paths.get(uri));
1897 }
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912 public static boolean waitFor(final Path file, final Duration timeout, final LinkOption... options) {
1913 Objects.requireNonNull(file, "file");
1914 final Instant finishInstant = Instant.now().plus(timeout);
1915 boolean interrupted = false;
1916 final long minSleepMillis = 100;
1917 try {
1918 while (!exists(file, options)) {
1919 final Instant now = Instant.now();
1920 if (now.isAfter(finishInstant)) {
1921 return false;
1922 }
1923 try {
1924 ThreadUtils.sleep(Duration.ofMillis(Math.min(minSleepMillis, finishInstant.minusMillis(now.toEpochMilli()).toEpochMilli())));
1925 } catch (final InterruptedException ignore) {
1926 interrupted = true;
1927 } catch (final Exception ex) {
1928 break;
1929 }
1930 }
1931 } finally {
1932 if (interrupted) {
1933 Thread.currentThread().interrupt();
1934 }
1935 }
1936 return exists(file, options);
1937 }
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956 @SuppressWarnings("resource")
1957 public static Stream<Path> walk(final Path start, final PathFilter pathFilter, final int maxDepth, final boolean readAttributes,
1958 final FileVisitOption... options) throws IOException {
1959 return Files.walk(start, maxDepth, options)
1960 .filter(path -> pathFilter.accept(path, readAttributes ? readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE);
1961 }
1962
1963 private static <R> R withPosixFileAttributes(final Path path, final LinkOption[] linkOptions, final boolean overrideReadOnly,
1964 final IOFunction<PosixFileAttributes, R> function) throws IOException {
1965 final PosixFileAttributes posixFileAttributes = overrideReadOnly ? readPosixFileAttributes(path, linkOptions) : null;
1966 try {
1967 return function.apply(posixFileAttributes);
1968 } finally {
1969 if (posixFileAttributes != null && path != null && Files.exists(path, linkOptions)) {
1970 Files.setPosixFilePermissions(path, posixFileAttributes.permissions());
1971 }
1972 }
1973 }
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987 public static Path writeString(final Path path, final CharSequence charSequence, final Charset charset, final OpenOption... openOptions)
1988 throws IOException {
1989
1990 Objects.requireNonNull(path, "path");
1991 Objects.requireNonNull(charSequence, "charSequence");
1992 Files.write(path, String.valueOf(charSequence).getBytes(Charsets.toCharset(charset)), openOptions);
1993 return path;
1994 }
1995
1996
1997
1998
1999 private PathUtils() {
2000
2001 }
2002 }