Add and use FileUtils.lastModified[Unchecked](File) to workaround
MODIFY
src/changes/changes.xml
src/changes/changes.xml
@@ -148,6 +148,9 @@ The <action> type attribute can be add,update,fix,remove.
<action issue="IO-700" dev="ggregory" type="add" due-to="Gary Gregory">
Add FileUtils.isEmptyDirectory(File).
</action>
+ <action dev="ggregory" type="add" due-to="Gary Gregory">
+ Add FileUtils.lastModified[Unchecked](File) to workaround https://bugs.openjdk.java.net/browse/JDK-8177809.
+ </action>
<!-- UPDATES -->
<action dev="ggregory" type="update" due-to="Dependabot">
Update junit-jupiter from 5.6.2 to 5.7.0 #153.

MODIFY
src/main/java/org/apache/commons/io/FileUtils.java
src/main/java/org/apache/commons/io/FileUtils.java
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.CopyOption;
import java.nio.file.Files;
+import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
@@ -1629,7 +1630,7 @@ public static boolean isFileNewer(final File file, final Date date) {
*/
public static boolean isFileNewer(final File file, final File reference) {
requireExists(reference, "reference");
- return isFileNewer(file, reference.lastModified());
+ return isFileNewer(file, lastModifiedUnchecked(reference));
}
/**
@@ -1658,10 +1659,7 @@ public static boolean isFileNewer(final File file, final Instant instant) {
*/
public static boolean isFileNewer(final File file, final long timeMillis) {
Objects.requireNonNull(file, "file");
- if (!file.exists()) {
- return false;
- }
- return file.lastModified() > timeMillis;
+ return file.exists() ? lastModifiedUnchecked(file) > timeMillis : false;
}
/**
@@ -1794,7 +1792,7 @@ public static boolean isFileOlder(final File file, final Date date) {
*/
public static boolean isFileOlder(final File file, final File reference) {
requireExists(reference, "reference");
- return isFileOlder(file, reference.lastModified());
+ return isFileOlder(file, lastModifiedUnchecked(reference));
}
/**
@@ -1822,10 +1820,7 @@ public static boolean isFileOlder(final File file, final Instant instant) {
*/
public static boolean isFileOlder(final File file, final long timeMillis) {
Objects.requireNonNull(file, "file");
- if (!file.exists()) {
- return false;
- }
- return file.lastModified() < timeMillis;
+ return file.exists() ? lastModifiedUnchecked(file) < timeMillis : false;
}
/**
@@ -1920,6 +1915,52 @@ public static boolean isSymlink(final File file) {
return listFilesAndDirs(directory, fileFilter, dirFilter).iterator();
}
+ /**
+ * Returns the last modification time in milliseconds via
+ * {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
+ * <p>
+ * Use this method to avoid issues with {@link File#lastModified()} like
+ * <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()} is
+ * losing milliseconds (always ends in 000). This bug exists in OpenJDK 8 and 9, and is fixed in 10.
+ * </p>
+ *
+ * @param file The File to query.
+ * @return See {@link java.nio.file.attribute.FileTime#toMillis()}.
+ * @throws IOException if an I/O error occurs.
+ * @since 2.9.0
+ */
+ public static long lastModified(final File file) throws IOException {
+ // https://bugs.openjdk.java.net/browse/JDK-8177809
+ // File.lastModified() is losing milliseconds (always ends in 000)
+ // This bug is in OpenJDK 8 and 9, and fixed in 10.
+ return Files.getLastModifiedTime(Objects.requireNonNull(file.toPath(), "file")).toMillis();
+ }
+
+ /**
+ * Returns the last modification time in milliseconds via
+ * {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
+ * <p>
+ * Use this method to avoid issues with {@link File#lastModified()} like
+ * <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()} is
+ * losing milliseconds (always ends in 000). This bug exists in OpenJDK 8 and 9, and is fixed in 10.
+ * </p>
+ *
+ * @param file The File to query.
+ * @return See {@link java.nio.file.attribute.FileTime#toMillis()}.
+ * @throws IllegalArgumentException if an I/O error occurs.
+ * @since 2.9.0
+ */
+ public static long lastModifiedUnchecked(final File file) {
+ // https://bugs.openjdk.java.net/browse/JDK-8177809
+ // File.lastModified() is losing milliseconds (always ends in 000)
+ // This bug is in OpenJDK 8 and 9, and fixed in 10.
+ try {
+ return lastModified(file);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(file.toString(), e);
+ }
+ }
+
/**
* Returns an Iterator for the lines in a {@code File} using the default encoding for the VM.
*
@@ -2697,7 +2738,7 @@ private static File requireFileIfExists(final File file, final String name) {
*/
private static void setLastModified(final File sourceFile, final File targetFile) throws IOException {
Objects.requireNonNull(sourceFile, "sourceFile");
- setLastModified(targetFile, sourceFile.lastModified());
+ setLastModified(targetFile, lastModified(sourceFile));
}
/**

MODIFY
src/main/java/org/apache/commons/io/comparator/LastModifiedFileComparator.java
src/main/java/org/apache/commons/io/comparator/LastModifiedFileComparator.java
import java.io.Serializable;
import java.util.Comparator;
+import org.apache.commons.io.FileUtils;
+
/**
* Compare the <b>last modified date/time</b> of two files for order
- * (see {@link File#lastModified()}).
+ * (see {@link FileUtils#lastModifiedUnchecked(File)}).
* <p>
* This comparator can be used to sort lists or arrays of files
* by their last modified date/time.
*/
@Override
public int compare(final File file1, final File file2) {
- final long result = file1.lastModified() - file2.lastModified();
+ final long result = FileUtils.lastModifiedUnchecked(file1) - FileUtils.lastModifiedUnchecked(file2);
if (result < 0) {
return -1;
} else if (result > 0) {

MODIFY
src/main/java/org/apache/commons/io/filefilter/AgeFileFilter.java
src/main/java/org/apache/commons/io/filefilter/AgeFileFilter.java
@@ -121,7 +121,7 @@ public AgeFileFilter(final File cutoffReference) {
* cutoff).
*/
public AgeFileFilter(final File cutoffReference, final boolean acceptOlder) {
- this(cutoffReference.lastModified(), acceptOlder);
+ this(FileUtils.lastModifiedUnchecked(cutoffReference), acceptOlder);
}
/**

MODIFY
src/main/java/org/apache/commons/io/input/Tailer.java
src/main/java/org/apache/commons/io/input/Tailer.java
@@ -424,7 +424,7 @@ public void run() {
} else {
// The current position in the file
position = end ? file.length() : 0;
- last = file.lastModified();
+ last = FileUtils.lastModified(file);
reader.seek(position);
}
}
@@ -459,7 +459,7 @@ public void run() {
if (length > position) {
// The file has more content than it did last time
position = readLines(reader);
- last = file.lastModified();
+ last = FileUtils.lastModified(file);
} else if (newer) {
/*
* This can happen if the file is truncated or overwritten with the exact same length of
@@ -470,7 +470,7 @@ public void run() {
// Now we can read new lines
position = readLines(reader);
- last = file.lastModified();
+ last = FileUtils.lastModified(file);
}
if (reOpen && reader != null) {
reader.close();

MODIFY
src/main/java/org/apache/commons/io/monitor/FileEntry.java
src/main/java/org/apache/commons/io/monitor/FileEntry.java
package org.apache.commons.io.monitor;
import java.io.File;
+import java.io.IOException;
import java.io.Serializable;
+import java.nio.file.Files;
+
+import org.apache.commons.io.FileUtils;
/**
* The state of a file or directory, capturing the following {@link File} attributes at a point in time.
* <li>File Name (see {@link File#getName()})</li>
* <li>Exists - whether the file exists or not (see {@link File#exists()})</li>
* <li>Directory - whether the file is a directory or not (see {@link File#isDirectory()})</li>
- * <li>Last Modified Date/Time (see {@link File#lastModified()})</li>
+ * <li>Last Modified Date/Time (see {@link FileUtils#lastModifiedUnchecked(File)})</li>
* <li>Length (see {@link File#length()}) - directories treated as zero</li>
* <li>Children - contents of a directory (see {@link File#listFiles(java.io.FileFilter)})</li>
* </ul>
@@ -94,7 +98,6 @@ public FileEntry(final FileEntry parent, final File file) {
* @return {@code true} if the file has changed, otherwise {@code false}
*/
public boolean refresh(final File file) {
-
// cache original values
final boolean origExists = exists;
final long origLastModified = lastModified;
@@ -103,16 +106,18 @@ public boolean refresh(final File file) {
// refresh the values
name = file.getName();
- exists = file.exists();
+ exists = Files.exists(file.toPath());
directory = exists && file.isDirectory();
- lastModified = exists ? file.lastModified() : 0;
+ try {
+ lastModified = exists ? FileUtils.lastModified(file) : 0;
+ } catch (IOException e) {
+ lastModified = 0;
+ }
length = exists && !directory ? file.length() : 0;
// Return if there are changes
- return exists != origExists ||
- lastModified != origLastModified ||
- directory != origDirectory ||
- length != origLength;
+ return exists != origExists || lastModified != origLastModified || directory != origDirectory
+ || length != origLength;
}
/**

MODIFY
src/test/java/org/apache/commons/io/FileUtilsFileNewerTestCase.java
src/test/java/org/apache/commons/io/FileUtilsFileNewerTestCase.java
@@ -69,18 +69,19 @@ public void setUp() throws Exception {
/**
* Tests the {@code isFileNewer(File, *)} methods which a "normal" file.
+ * @throws IOException
*
* @see FileUtils#isFileNewer(File, long)
* @see FileUtils#isFileNewer(File, Date)
* @see FileUtils#isFileNewer(File, File)
*/
@Test
- public void testIsFileNewer() {
+ public void testIsFileNewer() throws IOException {
if (!testFile1.exists()) {
throw new IllegalStateException("The testFile1 should exist");
}
- final long fileLastModified = testFile1.lastModified();
+ final long fileLastModified = FileUtils.lastModified(testFile1);
final long TWO_SECOND = 2000;
testIsFileNewer("two second earlier is not newer" , testFile1, fileLastModified + TWO_SECOND, false);
@@ -90,19 +91,20 @@ public void testIsFileNewer() {
/**
* Tests the {@code isFileNewer(File, *)} methods which a not existing file.
+ * @throws IOException if an I/O error occurs.
*
* @see FileUtils#isFileNewer(File, long)
* @see FileUtils#isFileNewer(File, Date)
* @see FileUtils#isFileNewer(File, File)
*/
@Test
- public void testIsFileNewerImaginaryFile() {
+ public void testIsFileNewerImaginaryFile() throws IOException {
final File imaginaryFile = new File(temporaryFolder, "imaginaryFile");
if (imaginaryFile.exists()) {
throw new IllegalStateException("The imaginary File exists");
}
- testIsFileNewer("imaginary file can be newer" , imaginaryFile, testFile2.lastModified(), false);
+ testIsFileNewer("imaginary file can be newer" , imaginaryFile, FileUtils.lastModified(testFile2), false);
}
/**
@@ -123,19 +125,20 @@ public void testIsFileNewerImaginaryFile() {
* @param file the file of which the last modification date is compared
* @param time the time reference measured in milliseconds since the epoch
* @param wantedResult the expected result
+ * @throws IOException if an I/O error occurs.
*
* @see FileUtils#isFileNewer(File, long)
* @see FileUtils#isFileNewer(File, Date)
* @see FileUtils#isFileNewer(File, File)
*/
- protected void testIsFileNewer(final String description, final File file, final long time, final boolean wantedResult) {
+ protected void testIsFileNewer(final String description, final File file, final long time, final boolean wantedResult) throws IOException {
assertEquals(wantedResult, FileUtils.isFileNewer(file, time), description + " - time");
assertEquals(wantedResult, FileUtils.isFileNewer(file, new Date(time)), description + " - date");
final File temporaryFile = testFile2;
temporaryFile.setLastModified(time);
- assertEquals(time, temporaryFile.lastModified(), "The temporary file hasn't the right last modification date");
+ assertEquals(time, FileUtils.lastModified(temporaryFile), "The temporary file hasn't the right last modification date");
assertEquals(wantedResult, FileUtils.isFileNewer(file, temporaryFile), description + " - file");
}

MODIFY
src/test/java/org/apache/commons/io/FileUtilsTestCase.java
src/test/java/org/apache/commons/io/FileUtilsTestCase.java
@@ -167,7 +167,7 @@ public long length() {
private long testFile2Size;
- private void backDateFile10Minutes(final File testFile) {
+ private void backDateFile10Minutes(final File testFile) throws IOException {
final long mins10 = 1000 * 60 * 10;
final long lastModified1 = getLastModifiedMillis(testFile);
assertTrue(setLastModifiedMillis(testFile, lastModified1 - mins10));
@@ -216,10 +216,8 @@ private void createFilesForTestCopyDirectory(final File grandParentDir, final Fi
FileUtils.writeStringToFile(file6, "File 6 in grandChild2", "UTF8");
}
- private long getLastModifiedMillis(final File file) {
- return file.lastModified();
- //https://bugs.openjdk.java.net/browse/JDK-8177809
- //return Files.getLastModifiedTime(file.toPath()).toMillis();
+ private long getLastModifiedMillis(final File file) throws IOException {
+ return FileUtils.lastModified(file);
}
private String getName() {
@@ -1024,7 +1022,7 @@ public void testCopyFile1ToDir() throws Exception {
FileUtils.copyFileToDirectory(testFile1, directory);
assertTrue(destination.exists(), "Check Exist");
assertEquals(testFile1Size, destination.length(), "Check Full copy");
- assertEquals(testFile1.lastModified(), destination.lastModified(), "Check last modified date preserved");
+ assertEquals(FileUtils.lastModified(testFile1), FileUtils.lastModified(destination), "Check last modified date preserved");
assertThrows(IllegalArgumentException.class, () -> FileUtils.copyFileToDirectory(destination, directory),
"Should not be able to copy a file into the same directory as itself");
@@ -1057,7 +1055,7 @@ public void testCopyFile2ToDir() throws Exception {
FileUtils.copyFileToDirectory(testFile1, directory);
assertTrue(destination.exists(), "Check Exist");
assertEquals(testFile2Size, destination.length(), "Check Full copy");
- assertEquals(testFile1.lastModified(), destination.lastModified(), "Check last modified date preserved");
+ assertEquals(FileUtils.lastModified(testFile1), FileUtils.lastModified(destination), "Check last modified date preserved");
}
@Test

MODIFY
src/test/java/org/apache/commons/io/comparator/LastModifiedFileComparatorTest.java
src/test/java/org/apache/commons/io/comparator/LastModifiedFileComparatorTest.java
import java.io.FileOutputStream;
import java.io.IOException;
+import org.apache.commons.io.FileUtils;
import org.apache.commons.io.test.TestUtils;
import org.junit.jupiter.api.BeforeEach;
* Test case for {@link LastModifiedFileComparator}.
*/
public class LastModifiedFileComparatorTest extends ComparatorAbstractTestCase {
+
@BeforeEach
public void setUp() throws Exception {
comparator = (AbstractFileComparator) LastModifiedFileComparator.LASTMODIFIED_COMPARATOR;
reverse = LastModifiedFileComparator.LASTMODIFIED_REVERSE;
final File olderFile = new File(dir, "older.txt");
if (!olderFile.getParentFile().exists()) {
- throw new IOException("Cannot create file " + olderFile
- + " as the parent directory does not exist");
+ throw new IOException("Cannot create file " + olderFile + " as the parent directory does not exist");
}
- try (final BufferedOutputStream output2 =
- new BufferedOutputStream(new FileOutputStream(olderFile))) {
+ try (final BufferedOutputStream output2 = new BufferedOutputStream(new FileOutputStream(olderFile))) {
TestUtils.generateTestData(output2, 0);
}
final File equalFile = new File(dir, "equal.txt");
if (!equalFile.getParentFile().exists()) {
- throw new IOException("Cannot create file " + equalFile
- + " as the parent directory does not exist");
+ throw new IOException("Cannot create file " + equalFile + " as the parent directory does not exist");
}
- try (final BufferedOutputStream output1 =
- new BufferedOutputStream(new FileOutputStream(equalFile))) {
+ try (final BufferedOutputStream output1 = new BufferedOutputStream(new FileOutputStream(equalFile))) {
TestUtils.generateTestData(output1, 0);
}
do {
TestUtils.sleepQuietly(300);
equalFile.setLastModified(System.currentTimeMillis());
- } while( olderFile.lastModified() == equalFile.lastModified() );
+ } while (FileUtils.lastModified(olderFile) == FileUtils.lastModified(equalFile));
final File newerFile = new File(dir, "newer.txt");
if (!newerFile.getParentFile().exists()) {
- throw new IOException("Cannot create file " + newerFile
- + " as the parent directory does not exist");
+ throw new IOException("Cannot create file " + newerFile + " as the parent directory does not exist");
}
- try (final BufferedOutputStream output =
- new BufferedOutputStream(new FileOutputStream(newerFile))){
+ try (final BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(newerFile))) {
TestUtils.generateTestData(output, 0);
}
do {
TestUtils.sleepQuietly(300);
newerFile.setLastModified(System.currentTimeMillis());
- } while( equalFile.lastModified() == newerFile.lastModified() );
+ } while (FileUtils.lastModified(equalFile) == FileUtils.lastModified(newerFile));
equalFile1 = equalFile;
equalFile2 = equalFile;
lessFile = olderFile;

MODIFY
src/test/java/org/apache/commons/io/monitor/AbstractMonitorTestCase.java
src/test/java/org/apache/commons/io/monitor/AbstractMonitorTestCase.java
import static org.apache.commons.io.test.TestUtils.sleepQuietly;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.File;
import java.io.FileFilter;
+import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
@@ -127,13 +129,15 @@ protected void checkCollectionSizes(String label,
*
* @param file The file to touch
* @return The file
+ * @throws IOException if an I/O error occurs.
*/
- protected File touch(File file) {
- final long lastModified = file.exists() ? file.lastModified() : 0;
+ protected File touch(File file) throws IOException {
+ final long lastModified = file.exists() ? FileUtils.lastModified(file) : 0;
try {
FileUtils.touch(file);
+ assertTrue(file.exists());
file = new File(file.getParent(), file.getName());
- while (lastModified == file.lastModified()) {
+ while (lastModified == FileUtils.lastModified(file)) {
sleepQuietly(pauseTime);
FileUtils.touch(file);
file = new File(file.getParent(), file.getName());

MODIFY
src/test/java/org/apache/commons/io/monitor/FileAlterationObserverTestCase.java
src/test/java/org/apache/commons/io/monitor/FileAlterationObserverTestCase.java
import java.io.File;
import java.io.FileFilter;
+import java.io.IOException;
import java.util.Iterator;
import org.apache.commons.io.FileUtils;
@@ -146,9 +147,10 @@ public void testDirectory() throws Exception {
/**
* Test checkAndNotify() creating
+ * @throws IOException if an I/O error occurs.
*/
@Test
- public void testFileCreate() {
+ public void testFileCreate() throws IOException {
checkAndNotify();
checkCollectionsEmpty("A");
File testDirA = new File(testDir, "test-dir-A");
@@ -205,9 +207,10 @@ public void testFileCreate() {
/**
* Test checkAndNotify() creating
+ * @throws IOException if an I/O error occurs.
*/
@Test
- public void testFileUpdate() {
+ public void testFileUpdate() throws IOException {
checkAndNotify();
checkCollectionsEmpty("A");
File testDirA = new File(testDir, "test-dir-A");
@@ -261,9 +264,10 @@ public void testFileUpdate() {
/**
* Test checkAndNotify() deleting
+ * @throws IOException if an I/O error occurs.
*/
@Test
- public void testFileDelete() {
+ public void testFileDelete() throws IOException {
checkAndNotify();
checkCollectionsEmpty("A");
File testDirA = new File(testDir, "test-dir-A");
@@ -320,9 +324,10 @@ public void testFileDelete() {
/**
* Test checkAndNotify() method
+ * @throws IOException if an I/O error occurs.
*/
@Test
- public void testObserveSingleFile() {
+ public void testObserveSingleFile() throws IOException {
final File testDirA = new File(testDir, "test-dir-A");
File testDirAFile1 = new File(testDirA, "A-file1.java");
testDirA.mkdir();

Add and use FileUtils.lastModified[Unchecked](File) to workaround
https://bugs.openjdk.java.net/browse/JDK-8177809.