This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-io.git
The following commit(s) were added to refs/heads/master by this push: new b0a9ab1 [IO-645] Add org.apache.commons.io.file.PathUtils.fileContentEquals(Path, Path, OpenOption...) b0a9ab1 is described below commit b0a9ab1b3cd4d295e082b59c2a2450b5b245fc3d Author: Gary Gregory <gardgreg...@gmail.com> AuthorDate: Mon Nov 25 15:01:44 2019 -0500 [IO-645] Add org.apache.commons.io.file.PathUtils.fileContentEquals(Path, Path, OpenOption...) --- src/changes/changes.xml | 11 ++- .../java/org/apache/commons/io/file/PathUtils.java | 95 +++++++++++++++++++++- .../io/file/PathUtilsContentEqualsTest.java | 92 +++++++++++++++++++++ 3 files changed, 193 insertions(+), 5 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index c709b31..d612be5 100644 --- a/src/changes/changes.xml +++ b/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<IOException>). Add and reuse org.apache.commons.io.IOUtils.close(Closeable, IOConsumer<IOException>). </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."> diff --git a/src/main/java/org/apache/commons/io/file/PathUtils.java b/src/main/java/org/apache/commons/io/file/PathUtils.java index a6af570..5f3d0e0 100644 --- a/src/main/java/org/apache/commons/io/file/PathUtils.java +++ b/src/main/java/org/apache/commons/io/file/PathUtils.java @@ -18,15 +18,19 @@ 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; /** @@ -48,6 +52,60 @@ public final class PathUtils { } /** + * 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. * * @param sourceDirectory The source directory. @@ -66,7 +124,7 @@ public final class PathUtils { /** * 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 final class PathUtils { 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; + } } /** diff --git a/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java b/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java new file mode 100644 index 0000000..7bcba2e --- /dev/null +++ b/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java @@ -0,0 +1,92 @@ +/* + * 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)); + } + +}