Skip to content

Commit b0a9ab1

Browse files
author
Gary Gregory
committed
[IO-645] Add
org.apache.commons.io.file.PathUtils.fileContentEquals(Path, Path, OpenOption...)
1 parent 7fed4a3 commit b0a9ab1

3 files changed

Lines changed: 193 additions & 5 deletions

File tree

src/changes/changes.xml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,18 +165,21 @@ The <action> type attribute can be add,update,fix,remove.
165165
Add and reuse org.apache.commons.io.IOUtils.closeQuitely(Closeable, Consumer&lt;IOException&gt;).
166166
Add and reuse org.apache.commons.io.IOUtils.close(Closeable, IOConsumer&lt;IOException&gt;).
167167
</action>
168-
<action issue="IO-640" dev="ggregory" type="add" due-to="Gary Gregory">
168+
<action issue="IO-640" dev="ggregory" type="fix" due-to="Gary Gregory">
169169
NPE in org.apache.commons.io.IOUtils.contentEquals(InputStream, InputStream) when only one input is null.
170170
</action>"src/changes/changes.xml"
171-
<action issue="IO-641" dev="ggregory" type="add" due-to="Gary Gregory">
171+
<action issue="IO-641" dev="ggregory" type="fix" due-to="Gary Gregory">
172172
NPE in org.apache.commons.io.IOUtils.contentEquals(Reader, Reader) when only one input is null.
173173
</action>
174-
<action issue="IO-643" dev="ggregory" type="add" due-to="Gary Gregory">
174+
<action issue="IO-643" dev="ggregory" type="fix" due-to="Gary Gregory">
175175
NPE in org.apache.commons.io.IOUtils.contentEqualsIgnoreEOL(Reader, Reader) when only one input is null.
176176
</action>
177-
<action issue="IO-644" dev="ggregory" type="add" due-to="Gary Gregory">
177+
<action issue="IO-644" dev="ggregory" type="fix" due-to="Gary Gregory">
178178
NPE in org.apache.commons.io.FileUtils.contentEqualsIgnoreEOL(File, File) when only one input is null.
179179
</action>
180+
<action issue="IO-645" dev="ggregory" type="add" due-to="Gary Gregory">
181+
Add org.apache.commons.io.file.PathUtils.fileContentEquals(Path, Path, OpenOption...).
182+
</action>
180183
</release>
181184

182185
<release version="2.6" date="2017-10-15" description="Java 7 required, Java 9 supported.">

src/main/java/org/apache/commons/io/file/PathUtils.java

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@
1818
package org.apache.commons.io.file;
1919

2020
import java.io.IOException;
21+
import java.io.InputStream;
2122
import java.net.URI;
23+
import java.net.URL;
2224
import java.nio.file.CopyOption;
2325
import java.nio.file.DirectoryStream;
2426
import java.nio.file.FileVisitor;
2527
import java.nio.file.Files;
2628
import java.nio.file.NotDirectoryException;
29+
import java.nio.file.OpenOption;
2730
import java.nio.file.Path;
2831
import java.nio.file.Paths;
2932

33+
import org.apache.commons.io.IOUtils;
3034
import org.apache.commons.io.file.Counters.PathCounters;
3135

3236
/**
@@ -47,6 +51,60 @@ public static PathCounters cleanDirectory(final Path directory) throws IOExcepti
4751
return visitFileTree(CleaningPathVisitor.withLongCounters(), directory).getPathCounters();
4852
}
4953

54+
/**
55+
* Compares the contents of two Paths to determine if they are equal or not.
56+
* <p>
57+
* File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
58+
* </p>
59+
*
60+
* @param path1 the first stream.
61+
* @param path2 the second stream.
62+
* @param options options specifying how the files are opened.
63+
* @return true if the content of the streams are equal or they both don't exist, false otherwise.
64+
* @throws NullPointerException if either input is null.
65+
* @throws IOException if an I/O error occurs.
66+
* @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
67+
*/
68+
public static boolean fileContentEquals(final Path path1, final Path path2, final OpenOption... options) throws IOException {
69+
if (path1 == null && path2 == null) {
70+
return true;
71+
}
72+
if (path1 == null ^ path2 == null) {
73+
return false;
74+
}
75+
final Path nPath1 = path1.normalize();
76+
final Path nPath2 = path2.normalize();
77+
final boolean path1Exists = Files.exists(nPath1);
78+
if (path1Exists != Files.exists(nPath2)) {
79+
return false;
80+
}
81+
if (!path1Exists) {
82+
// Two not existing files are equal?
83+
// Same as FileUtils
84+
return true;
85+
}
86+
if (Files.isDirectory(nPath1)) {
87+
// don't compare directory contents.
88+
throw new IOException("Can't compare directories, only files: " + nPath1);
89+
}
90+
if (Files.isDirectory(nPath2)) {
91+
// don't compare directory contents.
92+
throw new IOException("Can't compare directories, only files: " + nPath2);
93+
}
94+
if (Files.size(nPath1) != Files.size(nPath2)) {
95+
// lengths differ, cannot be equal
96+
return false;
97+
}
98+
if (path1.equals(path2)) {
99+
// same file
100+
return true;
101+
}
102+
try (final InputStream inputStream1 = Files.newInputStream(nPath1, options);
103+
final InputStream inputStream2 = Files.newInputStream(nPath2, options)) {
104+
return IOUtils.contentEquals(inputStream1, inputStream2);
105+
}
106+
}
107+
50108
/**
51109
* Copies a directory to another directory.
52110
*
@@ -66,7 +124,7 @@ public static PathCounters copyDirectory(final Path sourceDirectory, final Path
66124
/**
67125
* Copies a file to a directory.
68126
*
69-
* @param sourceFile The source file
127+
* @param sourceFile The source file.
70128
* @param targetDirectory The target directory.
71129
* @param copyOptions Specifies how the copying should be done.
72130
* @return The target file
@@ -76,7 +134,42 @@ public static PathCounters copyDirectory(final Path sourceDirectory, final Path
76134
public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory,
77135
final CopyOption... copyOptions) throws IOException {
78136
return Files.copy(sourceFile, targetDirectory.resolve(sourceFile.getFileName()), copyOptions);
137+
}
79138

139+
/**
140+
* Copies a URL to a directory.
141+
*
142+
* @param sourceFile The source URL.
143+
* @param targetDirectory The target directory.
144+
* @param copyOptions Specifies how the copying should be done.
145+
* @return The target file
146+
* @throws IOException if an I/O error occurs
147+
* @see Files#copy(InputStream, Path, CopyOption...)
148+
*/
149+
public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory,
150+
final CopyOption... copyOptions) throws IOException {
151+
try (final InputStream inputStream = sourceFile.openStream()) {
152+
Files.copy(inputStream, targetDirectory.resolve(sourceFile.getFile()), copyOptions);
153+
return targetDirectory;
154+
}
155+
}
156+
157+
/**
158+
* Copies a URL to a directory.
159+
*
160+
* @param sourceFile The source URL.
161+
* @param targetFile The target file.
162+
* @param copyOptions Specifies how the copying should be done.
163+
* @return The target file
164+
* @throws IOException if an I/O error occurs
165+
* @see Files#copy(InputStream, Path, CopyOption...)
166+
*/
167+
public static Path copyFile(final URL sourceFile, final Path targetFile,
168+
final CopyOption... copyOptions) throws IOException {
169+
try (final InputStream inputStream = sourceFile.openStream()) {
170+
Files.copy(inputStream, targetFile, copyOptions);
171+
return targetFile;
172+
}
80173
}
81174

82175
/**
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.io.file;
19+
20+
import static org.junit.jupiter.api.Assertions.assertFalse;
21+
import static org.junit.jupiter.api.Assertions.assertTrue;
22+
import static org.junit.jupiter.api.Assertions.fail;
23+
24+
import java.io.File;
25+
import java.io.IOException;
26+
import java.nio.file.Files;
27+
import java.nio.file.Path;
28+
import java.nio.file.Paths;
29+
30+
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.api.io.TempDir;
32+
33+
public class PathUtilsContentEqualsTest {
34+
35+
@TempDir
36+
public File temporaryFolder;
37+
38+
private String getName() {
39+
return this.getClass().getSimpleName();
40+
}
41+
42+
@Test
43+
public void testFileContentEquals() throws Exception {
44+
// Non-existent files
45+
final Path path1 = new File(temporaryFolder, getName()).toPath();
46+
final Path path2 = new File(temporaryFolder, getName() + "2").toPath();
47+
assertTrue(PathUtils.fileContentEquals(null, null));
48+
assertFalse(PathUtils.fileContentEquals(null, path1));
49+
assertFalse(PathUtils.fileContentEquals(path1, null));
50+
// both don't exist
51+
assertTrue(PathUtils.fileContentEquals(path1, path1));
52+
assertTrue(PathUtils.fileContentEquals(path1, path2));
53+
assertTrue(PathUtils.fileContentEquals(path2, path2));
54+
assertTrue(PathUtils.fileContentEquals(path2, path1));
55+
56+
// Directories
57+
try {
58+
PathUtils.fileContentEquals(temporaryFolder.toPath(), temporaryFolder.toPath());
59+
fail("Comparing directories should fail with an IOException");
60+
} catch (final IOException ioe) {
61+
// expected
62+
}
63+
64+
// Different files
65+
final Path objFile1 = Paths.get(temporaryFolder.getAbsolutePath(), getName() + ".object");
66+
objFile1.toFile().deleteOnExit();
67+
PathUtils.copyFile(getClass().getResource("/java/lang/Object.class"), objFile1);
68+
69+
final Path objFile1b = Paths.get(temporaryFolder.getAbsolutePath(), getName() + ".object2");
70+
objFile1b.toFile().deleteOnExit();
71+
PathUtils.copyFile(getClass().getResource("/java/lang/Object.class"), objFile1b);
72+
73+
final Path objFile2 = Paths.get(temporaryFolder.getAbsolutePath(), getName() + ".collection");
74+
objFile2.toFile().deleteOnExit();
75+
PathUtils.copyFile(getClass().getResource("/java/util/Collection.class"), objFile2);
76+
77+
assertFalse(PathUtils.fileContentEquals(objFile1, objFile2));
78+
assertFalse(PathUtils.fileContentEquals(objFile1b, objFile2));
79+
assertTrue(PathUtils.fileContentEquals(objFile1, objFile1b));
80+
81+
assertTrue(PathUtils.fileContentEquals(objFile1, objFile1));
82+
assertTrue(PathUtils.fileContentEquals(objFile1b, objFile1b));
83+
assertTrue(PathUtils.fileContentEquals(objFile2, objFile2));
84+
85+
// Equal files
86+
Files.createFile(path1);
87+
Files.createFile(path2);
88+
assertTrue(PathUtils.fileContentEquals(path1, path1));
89+
assertTrue(PathUtils.fileContentEquals(path1, path2));
90+
}
91+
92+
}

0 commit comments

Comments
 (0)