4343import java .nio .file .NotDirectoryException ;
4444import java .nio .file .Path ;
4545import java .nio .file .StandardCopyOption ;
46+ import java .nio .file .attribute .BasicFileAttributeView ;
47+ import java .nio .file .attribute .BasicFileAttributes ;
4648import java .nio .file .attribute .FileTime ;
4749import java .time .Duration ;
4850import java .time .Instant ;
5456import java .time .chrono .ChronoLocalDateTime ;
5557import java .time .chrono .ChronoZonedDateTime ;
5658import java .util .ArrayList ;
57- import java .util .Arrays ;
5859import java .util .Collection ;
5960import java .util .Collections ;
6061import java .util .Date ;
@@ -199,23 +200,6 @@ public class FileUtils {
199200 */
200201 public static final File [] EMPTY_FILE_ARRAY = {};
201202
202- /**
203- * Copies the given array and adds StandardCopyOption.COPY_ATTRIBUTES.
204- *
205- * @param copyOptions sorted copy options.
206- * @return a new array.
207- */
208- private static CopyOption [] addCopyAttributes (final CopyOption ... copyOptions ) {
209- // Make a copy first since we don't want to sort the call site's version.
210- final CopyOption [] actual = Arrays .copyOf (copyOptions , copyOptions .length + 1 );
211- Arrays .sort (actual , 0 , copyOptions .length );
212- if (Arrays .binarySearch (copyOptions , 0 , copyOptions .length , StandardCopyOption .COPY_ATTRIBUTES ) >= 0 ) {
213- return copyOptions ;
214- }
215- actual [actual .length - 1 ] = StandardCopyOption .COPY_ATTRIBUTES ;
216- return actual ;
217- }
218-
219203 /**
220204 * Returns a human-readable version of the file size, where the input represents a specific number of bytes.
221205 * <p>
@@ -489,9 +473,10 @@ public static File[] convertFileCollectionToFileArray(final Collection<File> fil
489473 * method merges the source with the destination, with the source taking precedence.
490474 * </p>
491475 * <p>
492- * <strong>Note:</strong> This method tries to preserve the files' last modified date/times using
493- * {@link File#setLastModified(long)}, however it is not guaranteed that those operations will succeed. If the
494- * modification operation fails, the methods throws IOException.
476+ * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
477+ * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
478+ * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
479+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
495480 * </p>
496481 *
497482 * @param srcDir an existing directory to copy, must not be {@code null}.
@@ -594,9 +579,10 @@ public static void copyDirectory(final File srcDir, final File destDir, final Fi
594579 * method merges the source with the destination, with the source taking precedence.
595580 * </p>
596581 * <p>
597- * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the files' last
598- * modified date/times using {@link File#setLastModified(long)}, however it is not guaranteed that those operations
599- * will succeed. If the modification operation fails, the methods throws IOException.
582+ * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
583+ * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
584+ * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
585+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
600586 * </p>
601587 * <b>Example: Copy directories only</b>
602588 *
@@ -643,9 +629,10 @@ public static void copyDirectory(final File srcDir, final File destDir, final Fi
643629 * method merges the source with the destination, with the source taking precedence.
644630 * </p>
645631 * <p>
646- * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the files' last
647- * modified date/times using {@link File#setLastModified(long)}, however it is not guaranteed that those operations
648- * will succeed. If the modification operation fails, the methods throws IOException.
632+ * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
633+ * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
634+ * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
635+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
649636 * </p>
650637 * <b>Example: Copy directories only</b>
651638 *
@@ -698,7 +685,7 @@ public static void copyDirectory(final File srcDir, final File destDir, final Fi
698685 }
699686 }
700687 }
701- doCopyDirectory (srcDir , destDir , fileFilter , exclusionList , preserveFileDate , preserveFileDate ? addCopyAttributes ( copyOptions ) : copyOptions );
688+ doCopyDirectory (srcDir , destDir , fileFilter , exclusionList , preserveFileDate , copyOptions );
702689 }
703690
704691 /**
@@ -712,9 +699,10 @@ public static void copyDirectory(final File srcDir, final File destDir, final Fi
712699 * method merges the source with the destination, with the source taking precedence.
713700 * </p>
714701 * <p>
715- * <strong>Note:</strong> This method tries to preserve the files' last modified date/times using
716- * {@link File#setLastModified(long)}, however it is not guaranteed that those operations will succeed. If the
717- * modification operation fails, the methods throws IOException.
702+ * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
703+ * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
704+ * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
705+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
718706 * </p>
719707 *
720708 * @param sourceDir an existing directory to copy, must not be {@code null}.
@@ -740,8 +728,9 @@ public static void copyDirectoryToDirectory(final File sourceDir, final File des
740728 * </p>
741729 * <p>
742730 * <strong>Note:</strong> This method tries to preserve the file's last modified date/times using
743- * {@link StandardCopyOption#COPY_ATTRIBUTES}, however it is not guaranteed that the operation will succeed. If the
744- * modification operation fails, the methods throws IOException.
731+ * {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is not guaranteed that the
732+ * operation will succeed. If the modification operation fails it will fallback to
733+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
745734 * </p>
746735 *
747736 * @param srcFile an existing file to copy, must not be {@code null}.
@@ -754,7 +743,7 @@ public static void copyDirectoryToDirectory(final File sourceDir, final File des
754743 * @see #copyFile(File, File, boolean)
755744 */
756745 public static void copyFile (final File srcFile , final File destFile ) throws IOException {
757- copyFile (srcFile , destFile , StandardCopyOption .COPY_ATTRIBUTES , StandardCopyOption . REPLACE_EXISTING );
746+ copyFile (srcFile , destFile , StandardCopyOption .REPLACE_EXISTING );
758747 }
759748
760749 /**
@@ -766,8 +755,9 @@ public static void copyFile(final File srcFile, final File destFile) throws IOEx
766755 * </p>
767756 * <p>
768757 * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
769- * modified date/times using {@link StandardCopyOption#COPY_ATTRIBUTES}, however it is not guaranteed that the operation
770- * will succeed. If the modification operation fails, the methods throws IOException.
758+ * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
759+ * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
760+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
771761 * </p>
772762 *
773763 * @param srcFile an existing file to copy, must not be {@code null}.
@@ -781,9 +771,7 @@ public static void copyFile(final File srcFile, final File destFile) throws IOEx
781771 */
782772 public static void copyFile (final File srcFile , final File destFile , final boolean preserveFileDate ) throws IOException {
783773 // @formatter:off
784- copyFile (srcFile , destFile , preserveFileDate
785- ? new CopyOption [] {StandardCopyOption .COPY_ATTRIBUTES , StandardCopyOption .REPLACE_EXISTING }
786- : new CopyOption [] {StandardCopyOption .REPLACE_EXISTING });
774+ copyFile (srcFile , destFile , preserveFileDate , new CopyOption [] {StandardCopyOption .REPLACE_EXISTING });
787775 // @formatter:on
788776 }
789777
@@ -796,8 +784,9 @@ public static void copyFile(final File srcFile, final File destFile, final boole
796784 * </p>
797785 * <p>
798786 * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
799- * modified date/times using {@link StandardCopyOption#COPY_ATTRIBUTES}, however it is not guaranteed that the operation
800- * will succeed. If the modification operation fails, the methods throws IOException.
787+ * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
788+ * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
789+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
801790 * </p>
802791 *
803792 * @param srcFile an existing file to copy, must not be {@code null}.
@@ -813,7 +802,20 @@ public static void copyFile(final File srcFile, final File destFile, final boole
813802 * @since 2.8.0
814803 */
815804 public static void copyFile (final File srcFile , final File destFile , final boolean preserveFileDate , final CopyOption ... copyOptions ) throws IOException {
816- copyFile (srcFile , destFile , preserveFileDate ? addCopyAttributes (copyOptions ) : copyOptions );
805+ requireFileCopy (srcFile , destFile );
806+ requireFile (srcFile , "srcFile" );
807+ requireCanonicalPathsNotEquals (srcFile , destFile );
808+ createParentDirectories (destFile );
809+ requireFileIfExists (destFile , "destFile" );
810+ if (destFile .exists ()) {
811+ requireCanWrite (destFile , "destFile" );
812+ }
813+ Files .copy (srcFile .toPath (), destFile .toPath (), copyOptions );
814+
815+ // On Windows, the last modified time is copied by default.
816+ if (preserveFileDate ) {
817+ setTimes (srcFile , destFile );
818+ }
817819 }
818820
819821 /**
@@ -835,16 +837,7 @@ public static void copyFile(final File srcFile, final File destFile, final boole
835837 * @since 2.9.0
836838 */
837839 public static void copyFile (final File srcFile , final File destFile , final CopyOption ... copyOptions ) throws IOException {
838- requireFileCopy (srcFile , destFile );
839- requireFile (srcFile , "srcFile" );
840- requireCanonicalPathsNotEquals (srcFile , destFile );
841- createParentDirectories (destFile );
842- requireFileIfExists (destFile , "destFile" );
843- if (destFile .exists ()) {
844- requireCanWrite (destFile , "destFile" );
845- }
846- // On Windows, the last modified time is copied by default.
847- Files .copy (srcFile .toPath (), destFile .toPath (), copyOptions );
840+ copyFile (srcFile , destFile , true , copyOptions );
848841 }
849842
850843 /**
@@ -876,8 +869,9 @@ public static long copyFile(final File input, final OutputStream output) throws
876869 * </p>
877870 * <p>
878871 * <strong>Note:</strong> This method tries to preserve the file's last modified date/times using
879- * {@link StandardCopyOption#COPY_ATTRIBUTES}, however it is not guaranteed that the operation will succeed. If the
880- * modification operation fails, the methods throws IOException.
872+ * {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is not guaranteed that the
873+ * operation will succeed. If the modification operation fails it will fallback to
874+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
881875 * </p>
882876 *
883877 * @param srcFile an existing file to copy, must not be {@code null}.
@@ -900,8 +894,9 @@ public static void copyFileToDirectory(final File srcFile, final File destDir) t
900894 * </p>
901895 * <p>
902896 * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
903- * modified date/times using {@link StandardCopyOption#COPY_ATTRIBUTES}, however it is not guaranteed that the operation
904- * will succeed. If the modification operation fails, the methods throws IOException.
897+ * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
898+ * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
899+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
905900 * </p>
906901 *
907902 * @param sourceFile an existing file to copy, must not be {@code null}.
@@ -957,10 +952,10 @@ public static void copyInputStreamToFile(final InputStream source, final File de
957952 * merges the source with the destination, with the source taking precedence.
958953 * </p>
959954 * <p>
960- * <strong>Note:</strong> This method tries to preserve the files' last modified date/times using
961- * {@link StandardCopyOption#COPY_ATTRIBUTES} or {@link File#setLastModified(long)} depending on the input , however it
962- * is not guaranteed that those operations will succeed. If the modification operation fails, the methods throws
963- * IOException.
955+ * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
956+ * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)} , however it is
957+ * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
958+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
964959 * </p>
965960 *
966961 * @param sourceFile an existing file or directory to copy, must not be {@code null}.
@@ -993,10 +988,10 @@ public static void copyToDirectory(final File sourceFile, final File destination
993988 * If the destination file exists, then this method will overwrite it.
994989 * </p>
995990 * <p>
996- * <strong>Note:</strong> This method tries to preserve the file's last
997- * modified date/times using {@link File#setLastModified(long )}, however
998- * it is not guaranteed that the operation will succeed.
999- * If the modification operation fails, the methods throws IOException.
991+ * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
992+ * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime )}, however it is
993+ * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
994+ * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
1000995 * </p>
1001996 *
1002997 * @param sourceIterable existing files to copy, must not be {@code null}.
@@ -1310,13 +1305,13 @@ private static void doCopyDirectory(final File srcDir, final File destDir, final
13101305 if (srcFile .isDirectory ()) {
13111306 doCopyDirectory (srcFile , dstFile , fileFilter , exclusionList , preserveDirDate , copyOptions );
13121307 } else {
1313- copyFile (srcFile , dstFile , copyOptions );
1308+ copyFile (srcFile , dstFile , preserveDirDate , copyOptions );
13141309 }
13151310 }
13161311 }
13171312 // Do this last, as the above has probably affected directory metadata
13181313 if (preserveDirDate ) {
1319- setLastModified (srcDir , destDir );
1314+ setTimes (srcDir , destDir );
13201315 }
13211316 }
13221317
@@ -2386,7 +2381,8 @@ public static void moveFile(final File srcFile, final File destFile, final CopyO
23862381 requireAbsent (destFile , "destFile" );
23872382 final boolean rename = srcFile .renameTo (destFile );
23882383 if (!rename ) {
2389- copyFile (srcFile , destFile , copyOptions );
2384+ // Don't interfere with file date on move, handled by StandardCopyOption.COPY_ATTRIBUTES
2385+ copyFile (srcFile , destFile , false , copyOptions );
23902386 if (!srcFile .delete ()) {
23912387 FileUtils .deleteQuietly (destFile );
23922388 throw new IOException ("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'" );
@@ -2836,37 +2832,30 @@ private static File requireFileIfExists(final File file, final String name) {
28362832 }
28372833
28382834 /**
2839- * Sets the given {@code targetFile}'s last modified date to the value from {@code sourceFile}.
2835+ * Set file lastModifiedTime, lastAccessTime and creationTime to match source file
28402836 *
28412837 * @param sourceFile The source file to query.
28422838 * @param targetFile The target file or directory to set.
28432839 * @throws NullPointerException if sourceFile is {@code null}.
28442840 * @throws NullPointerException if targetFile is {@code null}.
28452841 * @throws IOException if setting the last-modified time failed.
28462842 */
2847- private static void setLastModified (final File sourceFile , final File targetFile ) throws IOException {
2843+ private static void setTimes (final File sourceFile , final File targetFile ) throws IOException {
28482844 Objects .requireNonNull (sourceFile , "sourceFile" );
28492845 Objects .requireNonNull (targetFile , "targetFile" );
2850- if (targetFile .isFile ()) {
2851- PathUtils .setLastModifiedTime (targetFile .toPath (), sourceFile .toPath ());
2852- } else {
2853- setLastModified (targetFile , lastModified (sourceFile ));
2854- }
2855- }
2856-
2857- /**
2858- * Sets the given {@code targetFile}'s last modified date to the given value.
2859- *
2860- * @param file The source file to query.
2861- * @param timeMillis The new last-modified time, measured in milliseconds since the epoch 01-01-1970 GMT.
2862- * @throws NullPointerException if file is {@code null}.
2863- * @throws IOException if setting the last-modified time failed.
2864- */
2865- private static void setLastModified (final File file , final long timeMillis ) throws IOException {
2866- Objects .requireNonNull (file , "file" );
2867- if (!file .setLastModified (timeMillis )) {
2868- throw new IOException (String .format ("Failed setLastModified(%s) on '%s'" , timeMillis , file ));
2869- }
2846+ try {
2847+ // Set creation, modified, last accessed to match source file
2848+ final BasicFileAttributes srcAttr = Files .readAttributes (sourceFile .toPath (), BasicFileAttributes .class );
2849+ final BasicFileAttributeView destAttrView = Files .getFileAttributeView (targetFile .toPath (), BasicFileAttributeView .class );
2850+ // null guards are not needed; BasicFileAttributes.setTimes(...) is null safe
2851+ destAttrView .setTimes (srcAttr .lastModifiedTime (), srcAttr .lastAccessTime (), srcAttr .creationTime ());
2852+ } catch (IOException unused ) {
2853+ // Fallback: Only set modified time to match source file
2854+ targetFile .setLastModified (sourceFile .lastModified ());
2855+ }
2856+
2857+ // TODO: (Help!) Determine historically why setLastModified(File, File) needed PathUtils.setLastModifiedTime() if
2858+ // sourceFile.isFile() was true, but needed setLastModifiedTime(File, long) if sourceFile.isFile() was false
28702859 }
28712860
28722861 /**
0 commit comments