Add PathUtils.getLastModifiedFileTime(*).
MODIFY
src/changes/changes.xml
src/changes/changes.xml
@@ -403,6 +403,9 @@ The <action> type attribute can be add,update,fix,remove.
<action dev="ggregory" type="add" due-to="Gary Gregory">
Add XmlStreamWriter(OutputStream, Charset).
</action>
+ <action dev="ggregory" type="add" due-to="Gary Gregory">
+ Add PathUtils.getLastModifiedFileTime(*).
+ </action>
<!-- UPDATE -->
<action dev="kinow" type="update" due-to="Dependabot, Gary Gregory">
Bump actions/cache from 2.1.6 to 3.0.5 #307, #337.

MODIFY
src/main/java/org/apache/commons/io/file/PathUtils.java
src/main/java/org/apache/commons/io/file/PathUtils.java
import java.io.UncheckedIOException;
import java.math.BigInteger;
import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.AccessDeniedException;
@@ -812,6 +813,76 @@ public static DosFileAttributeView getDosFileAttributeView(final Path path, fina
return Files.getFileAttributeView(path, DosFileAttributeView.class, options);
}
+ /**
+ * Gets the file's last modified time or null if the file does not exist.
+ * <p>
+ * The method provides a workaround for bug <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a>
+ * where {@link File#lastModified()} looses milliseconds and always ends in 000. This bug is in OpenJDK 8 and 9, and
+ * fixed in 11.
+ * </p>
+ *
+ * @param file the file to query.
+ * @return the file's last modified time.
+ * @throws IOException Thrown if an I/O error occurs.
+ * @since 2.12.0
+ */
+ public static FileTime getLastModifiedFileTime(final File file) throws IOException {
+ return getLastModifiedFileTime(file.toPath(), null, EMPTY_LINK_OPTION_ARRAY);
+ }
+
+ /**
+ * Gets the file's last modified time or null if the file does not exist.
+ *
+ * @param path the file to query.
+ * @param options options indicating how symbolic links are handled.
+ * @return the file's last modified time.
+ * @throws IOException Thrown if an I/O error occurs.
+ * @since 2.12.0
+ */
+ public static FileTime getLastModifiedFileTime(final Path path, final LinkOption... options) throws IOException {
+ return getLastModifiedFileTime(path, null, options);
+ }
+
+ /**
+ * Gets the file's last modified time or null if the file does not exist.
+ *
+ * @param path the file to query.
+ * @param defaultIfAbsent Returns this file time of the file does not exist, may be null.
+ * @param options options indicating how symbolic links are handled.
+ * @return the file's last modified time.
+ * @throws IOException Thrown if an I/O error occurs.
+ * @since 2.12.0
+ */
+ public static FileTime getLastModifiedFileTime(final Path path, final FileTime defaultIfAbsent, final LinkOption... options) throws IOException {
+ return Files.exists(path) ? getLastModifiedTime(path, options) : defaultIfAbsent;
+ }
+
+ /**
+ * Gets the file's last modified time or null if the file does not exist.
+ *
+ * @param uri the file to query.
+ * @return the file's last modified time.
+ * @throws IOException Thrown if an I/O error occurs.
+ * @since 2.12.0
+ */
+ public static FileTime getLastModifiedFileTime(final URI uri) throws IOException {
+ return getLastModifiedFileTime(Paths.get(uri), null, EMPTY_LINK_OPTION_ARRAY);
+ }
+
+ /**
+ * Gets the file's last modified time or null if the file does not exist.
+ *
+ * @param url the file to query.
+ * @return the file's last modified time.
+ * @throws IOException Thrown if an I/O error occurs.
+ * @throws URISyntaxException if the URL is not formatted strictly according to to RFC2396 and cannot be converted to a
+ * URI.
+ * @since 2.12.0
+ */
+ public static FileTime getLastModifiedFileTime(final URL url) throws IOException, URISyntaxException {
+ return getLastModifiedFileTime(url.toURI());
+ }
+
private static FileTime getLastModifiedTime(final Path path, final LinkOption... options) throws IOException {
return Files.getLastModifiedTime(Objects.requireNonNull(path, "path"), options);
}

MODIFY
src/test/java/org/apache/commons/io/file/PathUtilsTest.java
src/test/java/org/apache/commons/io/file/PathUtilsTest.java
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
@@ -89,10 +91,18 @@ private Path createTempSymlinkedRelativeDir() throws IOException {
return symlinkDir;
}
+ private Path current() {
+ return PathUtils.current();
+ }
+
private Long getLastModifiedMillis(final Path file) throws IOException {
return Files.getLastModifiedTime(file).toMillis();
}
+ private Path getNonExistantPath() {
+ return Paths.get("/does not exist/for/certain");
+ }
+
private FileSystem openArchive(final Path p, final boolean createNew) throws IOException {
if (createNew) {
final Map<String, String> env = new HashMap<>();
@@ -227,6 +237,37 @@ public void testCreateDirectoriesSymlinkClashing() throws IOException {
assertThrowsExactly(FileAlreadyExistsException.class, () -> PathUtils.createParentDirectories(symlinkedDir.resolve("child")));
}
+ @Test
+ public void testGetLastModifiedFileTime_File_Present() throws IOException {
+ assertNotNull(PathUtils.getLastModifiedFileTime(current().toFile()));
+ }
+
+ @Test
+ public void testGetLastModifiedFileTime_Path_Absent() throws IOException {
+ assertNull(PathUtils.getLastModifiedFileTime(getNonExistantPath()));
+ }
+
+ @Test
+ public void testGetLastModifiedFileTime_Path_FileTime_Absent() throws IOException {
+ final FileTime fromMillis = FileTime.fromMillis(0);
+ assertEquals(fromMillis, PathUtils.getLastModifiedFileTime(getNonExistantPath(), fromMillis));
+ }
+
+ @Test
+ public void testGetLastModifiedFileTime_Path_Present() throws IOException {
+ assertNotNull(PathUtils.getLastModifiedFileTime(current()));
+ }
+
+ @Test
+ public void testGetLastModifiedFileTime_URI_Present() throws IOException {
+ assertNotNull(PathUtils.getLastModifiedFileTime(current().toUri()));
+ }
+
+ @Test
+ public void testGetLastModifiedFileTime_URL_Present() throws IOException, URISyntaxException {
+ assertNotNull(PathUtils.getLastModifiedFileTime(current().toUri().toURL()));
+ }
+
@Test
public void testGetTempDirectory() {
final Path tempDirectory = Paths.get(System.getProperty("java.io.tmpdir"));
@@ -254,12 +295,12 @@ public void testIsDirectory() throws IOException {
public void testIsPosix() throws IOException {
boolean isPosix;
try {
- Files.getPosixFilePermissions(PathUtils.current());
+ Files.getPosixFilePermissions(current());
isPosix = true;
} catch (final UnsupportedOperationException e) {
isPosix = false;
}
- assertEquals(isPosix, PathUtils.isPosix(PathUtils.current()));
+ assertEquals(isPosix, PathUtils.isPosix(current()));
}
@Test
@@ -278,7 +319,7 @@ public void testIsRegularFile() throws IOException {
@Test
public void testNewDirectoryStream() throws Exception {
final PathFilter pathFilter = new NameFileFilter(PATH_FIXTURE);
- try (DirectoryStream<Path> stream = PathUtils.newDirectoryStream(PathUtils.current(), pathFilter)) {
+ try (DirectoryStream<Path> stream = PathUtils.newDirectoryStream(current(), pathFilter)) {
final Iterator<Path> iterator = stream.iterator();
final Path path = iterator.next();
assertEquals(PATH_FIXTURE, path.getFileName().toString());
@@ -336,12 +377,12 @@ public void testNewOutputStreamNewFileInsideExistingSymlinkedDir() throws IOExce
public void testReadAttributesPosix() throws IOException {
boolean isPosix;
try {
- Files.getPosixFilePermissions(PathUtils.current());
+ Files.getPosixFilePermissions(current());
isPosix = true;
} catch (final UnsupportedOperationException e) {
isPosix = false;
}
- assertEquals(isPosix, PathUtils.readAttributes(PathUtils.current(), PosixFileAttributes.class) != null);
+ assertEquals(isPosix, PathUtils.readAttributes(current(), PosixFileAttributes.class) != null);
}
@Test

Add PathUtils.getLastModifiedFileTime(*).