Add
org.apache.commons.io.file.PathUtils.fileContentEquals(Path, Path,
OpenOption...)
MODIFY
src/changes/changes.xml
src/changes/changes.xml
@@ -165,18 +165,21 @@ The <action> type attribute can be add,update,fix,remove.
Add and reuse org.apache.commons.io.IOUtils.closeQuitely(Closeable, Consumer&lt;IOException&gt;).
Add and reuse org.apache.commons.io.IOUtils.close(Closeable, IOConsumer&lt;IOException&gt;).
</action>
- <action issue="IO-640" dev="ggregory" type="add" due-to="Gary Gregory">
+ <action issue="IO-640" dev="ggregory" type="fix" due-to="Gary Gregory">
NPE in org.apache.commons.io.IOUtils.contentEquals(InputStream, InputStream) when only one input is null.
</action>"src/changes/changes.xml"
- <action issue="IO-641" dev="ggregory" type="add" due-to="Gary Gregory">
+ <action issue="IO-641" dev="ggregory" type="fix" due-to="Gary Gregory">
NPE in org.apache.commons.io.IOUtils.contentEquals(Reader, Reader) when only one input is null.
</action>
- <action issue="IO-643" dev="ggregory" type="add" due-to="Gary Gregory">
+ <action issue="IO-643" dev="ggregory" type="fix" due-to="Gary Gregory">
NPE in org.apache.commons.io.IOUtils.contentEqualsIgnoreEOL(Reader, Reader) when only one input is null.
</action>
- <action issue="IO-644" dev="ggregory" type="add" due-to="Gary Gregory">
+ <action issue="IO-644" dev="ggregory" type="fix" due-to="Gary Gregory">
NPE in org.apache.commons.io.FileUtils.contentEqualsIgnoreEOL(File, File) when only one input is null.
</action>
+ <action issue="IO-645" dev="ggregory" type="add" due-to="Gary Gregory">
+ Add org.apache.commons.io.file.PathUtils.fileContentEquals(Path, Path, OpenOption...).
+ </action>
</release>
<release version="2.6" date="2017-10-15" description="Java 7 required, Java 9 supported.">

MODIFY
src/main/java/org/apache/commons/io/file/PathUtils.java
src/main/java/org/apache/commons/io/file/PathUtils.java
package org.apache.commons.io.file;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URI;
+import java.net.URL;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.NotDirectoryException;
+import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.file.Counters.PathCounters;
/**
@@ -47,6 +51,60 @@ public static PathCounters cleanDirectory(final Path directory) throws IOExcepti
return visitFileTree(CleaningPathVisitor.withLongCounters(), directory).getPathCounters();
}
+ /**
+ * Compares the contents of two Paths to determine if they are equal or not.
+ * <p>
+ * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
+ * </p>
+ *
+ * @param path1 the first stream.
+ * @param path2 the second stream.
+ * @param options options specifying how the files are opened.
+ * @return true if the content of the streams are equal or they both don't exist, false otherwise.
+ * @throws NullPointerException if either input is null.
+ * @throws IOException if an I/O error occurs.
+ * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
+ */
+ public static boolean fileContentEquals(final Path path1, final Path path2, final OpenOption... options) throws IOException {
+ if (path1 == null && path2 == null) {
+ return true;
+ }
+ if (path1 == null ^ path2 == null) {
+ return false;
+ }
+ final Path nPath1 = path1.normalize();
+ final Path nPath2 = path2.normalize();
+ final boolean path1Exists = Files.exists(nPath1);
+ if (path1Exists != Files.exists(nPath2)) {
+ return false;
+ }
+ if (!path1Exists) {
+ // Two not existing files are equal?
+ // Same as FileUtils
+ return true;
+ }
+ if (Files.isDirectory(nPath1)) {
+ // don't compare directory contents.
+ throw new IOException("Can't compare directories, only files: " + nPath1);
+ }
+ if (Files.isDirectory(nPath2)) {
+ // don't compare directory contents.
+ throw new IOException("Can't compare directories, only files: " + nPath2);
+ }
+ if (Files.size(nPath1) != Files.size(nPath2)) {
+ // lengths differ, cannot be equal
+ return false;
+ }
+ if (path1.equals(path2)) {
+ // same file
+ return true;
+ }
+ try (final InputStream inputStream1 = Files.newInputStream(nPath1, options);
+ final InputStream inputStream2 = Files.newInputStream(nPath2, options)) {
+ return IOUtils.contentEquals(inputStream1, inputStream2);
+ }
+ }
+
/**
* Copies a directory to another directory.
*
@@ -66,7 +124,7 @@ public static PathCounters copyDirectory(final Path sourceDirectory, final Path
/**
* Copies a file to a directory.
*
- * @param sourceFile The source file
+ * @param sourceFile The source file.
* @param targetDirectory The target directory.
* @param copyOptions Specifies how the copying should be done.
* @return The target file
@@ -76,7 +134,42 @@ public static PathCounters copyDirectory(final Path sourceDirectory, final Path
public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory,
final CopyOption... copyOptions) throws IOException {
return Files.copy(sourceFile, targetDirectory.resolve(sourceFile.getFileName()), copyOptions);
+ }
+ /**
+ * Copies a URL to a directory.
+ *
+ * @param sourceFile The source URL.
+ * @param targetDirectory The target directory.
+ * @param copyOptions Specifies how the copying should be done.
+ * @return The target file
+ * @throws IOException if an I/O error occurs
+ * @see Files#copy(InputStream, Path, CopyOption...)
+ */
+ public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory,
+ final CopyOption... copyOptions) throws IOException {
+ try (final InputStream inputStream = sourceFile.openStream()) {
+ Files.copy(inputStream, targetDirectory.resolve(sourceFile.getFile()), copyOptions);
+ return targetDirectory;
+ }
+ }
+
+ /**
+ * Copies a URL to a directory.
+ *
+ * @param sourceFile The source URL.
+ * @param targetFile The target file.
+ * @param copyOptions Specifies how the copying should be done.
+ * @return The target file
+ * @throws IOException if an I/O error occurs
+ * @see Files#copy(InputStream, Path, CopyOption...)
+ */
+ public static Path copyFile(final URL sourceFile, final Path targetFile,
+ final CopyOption... copyOptions) throws IOException {
+ try (final InputStream inputStream = sourceFile.openStream()) {
+ Files.copy(inputStream, targetFile, copyOptions);
+ return targetFile;
+ }
}
/**

ADD
src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io.file;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+public class PathUtilsContentEqualsTest {
+
+ @TempDir
+ public File temporaryFolder;
+
+ private String getName() {
+ return this.getClass().getSimpleName();
+ }
+
+ @Test
+ public void testFileContentEquals() throws Exception {
+ // Non-existent files
+ final Path path1 = new File(temporaryFolder, getName()).toPath();
+ final Path path2 = new File(temporaryFolder, getName() + "2").toPath();
+ assertTrue(PathUtils.fileContentEquals(null, null));
+ assertFalse(PathUtils.fileContentEquals(null, path1));
+ assertFalse(PathUtils.fileContentEquals(path1, null));
+ // both don't exist
+ assertTrue(PathUtils.fileContentEquals(path1, path1));
+ assertTrue(PathUtils.fileContentEquals(path1, path2));
+ assertTrue(PathUtils.fileContentEquals(path2, path2));
+ assertTrue(PathUtils.fileContentEquals(path2, path1));
+
+ // Directories
+ try {
+ PathUtils.fileContentEquals(temporaryFolder.toPath(), temporaryFolder.toPath());
+ fail("Comparing directories should fail with an IOException");
+ } catch (final IOException ioe) {
+ // expected
+ }
+
+ // Different files
+ final Path objFile1 = Paths.get(temporaryFolder.getAbsolutePath(), getName() + ".object");
+ objFile1.toFile().deleteOnExit();
+ PathUtils.copyFile(getClass().getResource("/java/lang/Object.class"), objFile1);
+
+ final Path objFile1b = Paths.get(temporaryFolder.getAbsolutePath(), getName() + ".object2");
+ objFile1b.toFile().deleteOnExit();
+ PathUtils.copyFile(getClass().getResource("/java/lang/Object.class"), objFile1b);
+
+ final Path objFile2 = Paths.get(temporaryFolder.getAbsolutePath(), getName() + ".collection");
+ objFile2.toFile().deleteOnExit();
+ PathUtils.copyFile(getClass().getResource("/java/util/Collection.class"), objFile2);
+
+ assertFalse(PathUtils.fileContentEquals(objFile1, objFile2));
+ assertFalse(PathUtils.fileContentEquals(objFile1b, objFile2));
+ assertTrue(PathUtils.fileContentEquals(objFile1, objFile1b));
+
+ assertTrue(PathUtils.fileContentEquals(objFile1, objFile1));
+ assertTrue(PathUtils.fileContentEquals(objFile1b, objFile1b));
+ assertTrue(PathUtils.fileContentEquals(objFile2, objFile2));
+
+ // Equal files
+ Files.createFile(path1);
+ Files.createFile(path2);
+ assertTrue(PathUtils.fileContentEquals(path1, path1));
+ assertTrue(PathUtils.fileContentEquals(path1, path2));
+ }
+
+}

[IO-645] Add
org.apache.commons.io.file.PathUtils.fileContentEquals(Path, Path,
OpenOption...)