Skip to content

Commit 154cb76

Browse files
author
Gary Gregory
committed
[IO-632] Add PathUtils for operations on NIO Path.
Refactor to add PathUtils.copyDirectory(Path, Path, CopyOption...)
1 parent b2ea4fa commit 154cb76

12 files changed

Lines changed: 233 additions & 50 deletions

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,17 @@ private boolean accept(final Path path) {
7777
}
7878

7979
@Override
80-
public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
81-
super.preVisitDirectory(dir, attrs);
80+
public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attributes) throws IOException {
81+
super.preVisitDirectory(dir, attributes);
8282
return accept(dir) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
8383
}
8484

8585
@Override
86-
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
86+
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attributes) throws IOException {
8787
if (accept(file) && Files.exists(file)) {
8888
Files.deleteIfExists(file);
8989
}
90-
updateFileCounters(file, attrs);
90+
updateFileCounters(file, attributes);
9191
return FileVisitResult.CONTINUE;
9292
}
9393
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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 java.io.IOException;
21+
import java.nio.file.CopyOption;
22+
import java.nio.file.FileVisitResult;
23+
import java.nio.file.Files;
24+
import java.nio.file.Path;
25+
import java.nio.file.attribute.BasicFileAttributes;
26+
27+
import org.apache.commons.io.file.Counters.PathCounters;
28+
29+
/**
30+
* Copies a source directory to a target directory.
31+
*
32+
* @since 2.7
33+
*/
34+
public class CopyDirectoryVisitor extends CountingPathVisitor {
35+
36+
private static final CopyOption[] EMPTY_COPY_OPTIONS = new CopyOption[0];
37+
38+
private final CopyOption[] copyOptions;
39+
private final Path sourceDirectory;
40+
private final Path targetDirectory;
41+
42+
/**
43+
* Constructs a new visitor that deletes files except for the files and directories explicitly given.
44+
*
45+
* @param pathCounter How to count visits.
46+
* @param sourceDirectory The source directory
47+
* @param targetDirectory The target directory
48+
* @param copyOptions Specifies how the copying should be done.
49+
*/
50+
public CopyDirectoryVisitor(final PathCounters pathCounter, final Path sourceDirectory, final Path targetDirectory,
51+
final CopyOption... copyOptions) {
52+
super(pathCounter);
53+
this.sourceDirectory = sourceDirectory;
54+
this.targetDirectory = targetDirectory;
55+
this.copyOptions = copyOptions == null ? EMPTY_COPY_OPTIONS : copyOptions.clone();
56+
}
57+
58+
@Override
59+
public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAttributes attributes)
60+
throws IOException {
61+
final Path newTargetDir = targetDirectory.resolve(sourceDirectory.relativize(directory));
62+
if (Files.notExists(newTargetDir)) {
63+
Files.createDirectory(newTargetDir);
64+
}
65+
return super.preVisitDirectory(directory, attributes);
66+
}
67+
68+
@Override
69+
public FileVisitResult visitFile(final Path sourceFile, final BasicFileAttributes attributes) throws IOException {
70+
final Path targetFile = targetDirectory.resolve(sourceDirectory.relativize(sourceFile));
71+
Files.copy(sourceFile, targetFile, copyOptions);
72+
return super.visitFile(targetFile, attributes);
73+
}
74+
75+
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,17 @@ public String toString() {
8989
* Updates the counters for visiting the given file.
9090
*
9191
* @param file the visited file.
92-
* @param attrs the visited file attributes.
92+
* @param attributes the visited file attributes.
9393
*/
94-
protected void updateFileCounters(final Path file, final BasicFileAttributes attrs) {
94+
protected void updateFileCounters(final Path file, final BasicFileAttributes attributes) {
9595
pathCounters.getFileCounter().increment();
96-
pathCounters.getByteCounter().add(attrs.size());
96+
pathCounters.getByteCounter().add(attributes.size());
9797
}
9898

9999
@Override
100-
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
100+
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attributes) throws IOException {
101101
if (Files.exists(file)) {
102-
updateFileCounters(file, attrs);
102+
updateFileCounters(file, attributes);
103103
}
104104
return FileVisitResult.CONTINUE;
105105
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)
8383
if (PathUtils.isEmptyDirectory(dir)) {
8484
Files.deleteIfExists(dir);
8585
}
86-
super.postVisitDirectory(dir, exc);
87-
return FileVisitResult.CONTINUE;
86+
return super.postVisitDirectory(dir, exc);
8887
}
8988

9089
@Override

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.IOException;
2121
import java.net.URI;
22+
import java.nio.file.CopyOption;
2223
import java.nio.file.DirectoryStream;
2324
import java.nio.file.FileVisitor;
2425
import java.nio.file.Files;
@@ -39,13 +40,29 @@ public final class PathUtils {
3940
* Cleans a directory including sub-directories without deleting directories.
4041
*
4142
* @param directory directory to clean.
42-
* @return The visitor used to clean the given directory.
43+
* @return The visitation path counters.
4344
* @throws IOException if an I/O error is thrown by a visitor method.
4445
*/
4546
public static PathCounters cleanDirectory(final Path directory) throws IOException {
4647
return visitFileTree(CleaningPathVisitor.withLongCounters(), directory).getPathCounters();
4748
}
4849

50+
/**
51+
* Copies a source directory to a target directory.
52+
*
53+
* @param sourceDirectory The source directory
54+
* @param targetDirectory The target directory
55+
* @param copyOptions Specifies how the copying should be done.
56+
* @return The visitation path counters.
57+
* @throws IOException if an I/O error is thrown by a visitor method.
58+
*/
59+
public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory,
60+
final CopyOption... copyOptions) throws IOException {
61+
return visitFileTree(
62+
new CopyDirectoryVisitor(Counters.longPathCounters(), sourceDirectory, targetDirectory, copyOptions),
63+
sourceDirectory).getPathCounters();
64+
}
65+
4966
/**
5067
* Counts aspects of a directory including sub-directories.
5168
*

src/test/java/org/apache/commons/io/file/CleaningPathVisitorTest.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ public void testCleanEmptyDirectoryNullCtorArg(final PathCounters pathCounters)
8484
@ParameterizedTest
8585
@MethodSource("cleaningPathVisitors")
8686
public void testCleanFolders1FileSize0(final CleaningPathVisitor visitor) throws IOException {
87-
FileUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0").toFile(),
88-
tempDir.toFile());
87+
PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0"), tempDir);
8988
assertCounts(1, 1, 0, PathUtils.visitFileTree(visitor, tempDir));
9089
}
9190

@@ -95,8 +94,7 @@ public void testCleanFolders1FileSize0(final CleaningPathVisitor visitor) throws
9594
@ParameterizedTest
9695
@MethodSource("cleaningPathVisitors")
9796
public void testCleanFolders1FileSize1(final CleaningPathVisitor visitor) throws IOException {
98-
FileUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1").toFile(),
99-
tempDir.toFile());
97+
PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1"), tempDir);
10098
assertCounts(1, 1, 1, PathUtils.visitFileTree(visitor, tempDir));
10199
}
102100

@@ -106,8 +104,7 @@ public void testCleanFolders1FileSize1(final CleaningPathVisitor visitor) throws
106104
@ParameterizedTest
107105
@MethodSource("pathCounters")
108106
public void testCleanFolders1FileSize1Skip(final PathCounters pathCounters) throws IOException {
109-
FileUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1").toFile(),
110-
tempDir.toFile());
107+
PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1"), tempDir);
111108
final String skipFileName = "file-size-1.bin";
112109
final CountingPathVisitor visitor = new CleaningPathVisitor(pathCounters, skipFileName);
113110
assertCounts(1, 1, 1, PathUtils.visitFileTree(visitor, tempDir));
@@ -122,8 +119,7 @@ public void testCleanFolders1FileSize1Skip(final PathCounters pathCounters) thro
122119
@ParameterizedTest
123120
@MethodSource("cleaningPathVisitors")
124121
public void testCleanFolders2FileSize2(final CleaningPathVisitor visitor) throws IOException {
125-
FileUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2").toFile(),
126-
tempDir.toFile());
122+
PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2"), tempDir);
127123
assertCounts(3, 2, 2, PathUtils.visitFileTree(visitor, tempDir));
128124
}
129125
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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.apache.commons.io.file.CounterAssertions.assertCounts;
21+
22+
import java.io.File;
23+
import java.io.IOException;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.nio.file.Paths;
27+
import java.nio.file.StandardCopyOption;
28+
29+
import org.apache.commons.io.FileUtils;
30+
import org.apache.commons.io.file.Counters.PathCounters;
31+
import org.junit.jupiter.api.AfterEach;
32+
import org.junit.jupiter.api.BeforeEach;
33+
import org.junit.jupiter.params.ParameterizedTest;
34+
import org.junit.jupiter.params.provider.MethodSource;
35+
36+
/**
37+
* Tests {@link CountingPathVisitor}.
38+
*/
39+
public class CopyDirectoryVisitorTest extends TestArguments {
40+
41+
private Path targetDir;
42+
43+
@AfterEach
44+
public void afterEach() throws IOException {
45+
PathUtils.deleteDirectory(targetDir);
46+
}
47+
48+
@BeforeEach
49+
public void beforeEach() throws IOException {
50+
targetDir = Files.createTempDirectory(getClass().getCanonicalName() + "-target");
51+
}
52+
53+
/**
54+
* Tests an empty folder.
55+
*/
56+
@ParameterizedTest
57+
@MethodSource("pathCounters")
58+
public void testCopyDirectoryEmptyFolder(final PathCounters pathCounters) throws IOException {
59+
final Path sourceDir = Files.createTempDirectory(getClass().getSimpleName());
60+
try {
61+
assertCounts(1, 0, 0,
62+
PathUtils.visitFileTree(new CopyDirectoryVisitor(pathCounters, sourceDir, targetDir, StandardCopyOption.REPLACE_EXISTING), sourceDir));
63+
} finally {
64+
Files.deleteIfExists(sourceDir);
65+
}
66+
}
67+
68+
/**
69+
* Tests a directory with one file of size 0.
70+
*/
71+
@ParameterizedTest
72+
@MethodSource("pathCounters")
73+
public void testCopyDirectoryFolders1FileSize0(final PathCounters pathCounters) throws IOException {
74+
final Path sourceDir = Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0");
75+
assertCounts(1, 1, 0, PathUtils.visitFileTree(
76+
new CopyDirectoryVisitor(pathCounters, sourceDir, targetDir, StandardCopyOption.REPLACE_EXISTING),
77+
sourceDir));
78+
}
79+
80+
/**
81+
* Tests a directory with one file of size 1.
82+
*/
83+
@ParameterizedTest
84+
@MethodSource("pathCounters")
85+
public void testCopyDirectoryFolders1FileSize1(final PathCounters pathCounters) throws IOException {
86+
final Path sourceDir = Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1");
87+
assertCounts(1, 1, 1, PathUtils.visitFileTree(
88+
new CopyDirectoryVisitor(pathCounters, sourceDir, targetDir, StandardCopyOption.REPLACE_EXISTING),
89+
sourceDir));
90+
}
91+
92+
/**
93+
* Tests a directory with two subdirectorys, each containing one file of size 1.
94+
*/
95+
@ParameterizedTest
96+
@MethodSource("pathCounters")
97+
public void testCopyDirectoryFolders2FileSize2(final PathCounters pathCounters) throws IOException {
98+
final Path sourceDir = Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2");
99+
assertCounts(3, 2, 2, PathUtils.visitFileTree(
100+
new CopyDirectoryVisitor(pathCounters, sourceDir, targetDir, StandardCopyOption.REPLACE_EXISTING),
101+
sourceDir));
102+
}
103+
104+
}

src/test/java/org/apache/commons/io/file/CounterAssertions.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,22 @@
2626

2727
class CounterAssertions {
2828

29-
static void assertCounter(final long expected, final Counter actual) {
30-
assertEquals(expected, actual.get());
31-
assertEquals(Long.valueOf(expected), actual.getLong());
32-
assertEquals(BigInteger.valueOf(expected), actual.getBigInteger());
29+
static void assertCounter(final long expected, final Counter actual, String message) {
30+
assertEquals(expected, actual.get(), message);
31+
assertEquals(Long.valueOf(expected), actual.getLong(), message);
32+
assertEquals(BigInteger.valueOf(expected), actual.getBigInteger(), message);
3333
}
3434

35-
static void assertCounts(final long expectedDirCount, final long expectedFileCount, final long expectedByteCount, final CountingPathVisitor actualVisitor) {
35+
static void assertCounts(final long expectedDirCount, final long expectedFileCount, final long expectedByteCount,
36+
final CountingPathVisitor actualVisitor) {
3637
assertCounts(expectedDirCount, expectedFileCount, expectedByteCount, actualVisitor.getPathCounters());
3738
}
3839

39-
static void assertCounts(final long expectedDirCount, final long expectedFileCount, final long expectedByteCount, final PathCounters actualPathCounters) {
40-
assertCounter(expectedDirCount, actualPathCounters.getDirectoryCounter());
41-
assertCounter(expectedFileCount, actualPathCounters.getFileCounter());
42-
assertCounter(expectedByteCount, actualPathCounters.getByteCounter());
40+
static void assertCounts(final long expectedDirCount, final long expectedFileCount, final long expectedByteCount,
41+
final PathCounters actualPathCounters) {
42+
assertCounter(expectedDirCount, actualPathCounters.getDirectoryCounter(), "getDirectoryCounter");
43+
assertCounter(expectedFileCount, actualPathCounters.getFileCounter(), "getFileCounter");
44+
assertCounter(expectedByteCount, actualPathCounters.getByteCounter(), "getByteCounter");
4345
}
4446

4547
}

src/test/java/org/apache/commons/io/file/CountersTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class CountersTest extends TestArguments {
3030
@ParameterizedTest
3131
@MethodSource("numberCounters")
3232
public void testInitialValue(final Counter counter) {
33-
assertCounter(0, counter);
33+
assertCounter(0, counter, "");
3434
}
3535

3636
@ParameterizedTest

src/test/java/org/apache/commons/io/file/DeletingPathVisitorTest.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ public void testDeleteEmptyDirectoryNullCtorArg(final PathCounters pathCounters)
8585
@ParameterizedTest
8686
@MethodSource("deletingPathVisitors")
8787
public void testDeleteFolders1FileSize0(final DeletingPathVisitor visitor) throws IOException {
88-
FileUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0").toFile(),
89-
tempDir.toFile());
88+
PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0"), tempDir);
9089
assertCounts(1, 1, 0, PathUtils.visitFileTree(visitor, tempDir));
9190
// This will throw if not empty.
9291
Files.deleteIfExists(tempDir);
@@ -98,8 +97,7 @@ public void testDeleteFolders1FileSize0(final DeletingPathVisitor visitor) throw
9897
@ParameterizedTest
9998
@MethodSource("deletingPathVisitors")
10099
public void testDeleteFolders1FileSize1(final DeletingPathVisitor visitor) throws IOException {
101-
FileUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1").toFile(),
102-
tempDir.toFile());
100+
PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1"), tempDir);
103101
assertCounts(1, 1, 1, PathUtils.visitFileTree(visitor, tempDir));
104102
// This will throw if not empty.
105103
Files.deleteIfExists(tempDir);
@@ -111,8 +109,7 @@ public void testDeleteFolders1FileSize1(final DeletingPathVisitor visitor) throw
111109
@ParameterizedTest
112110
@MethodSource("pathCounters")
113111
public void testDeleteFolders1FileSize1Skip(final PathCounters pathCounters) throws IOException {
114-
FileUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1").toFile(),
115-
tempDir.toFile());
112+
PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1"), tempDir);
116113
final String skipFileName = "file-size-1.bin";
117114
final CountingPathVisitor visitor = new DeletingPathVisitor(pathCounters, skipFileName);
118115
assertCounts(1, 1, 1, PathUtils.visitFileTree(visitor, tempDir));
@@ -127,8 +124,7 @@ public void testDeleteFolders1FileSize1Skip(final PathCounters pathCounters) thr
127124
@ParameterizedTest
128125
@MethodSource("deletingPathVisitors")
129126
public void testDeleteFolders2FileSize2(final DeletingPathVisitor visitor) throws IOException {
130-
FileUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2").toFile(),
131-
tempDir.toFile());
127+
PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2"), tempDir);
132128
assertCounts(3, 2, 2, PathUtils.visitFileTree(visitor, tempDir));
133129
// This will throw if not empty.
134130
Files.deleteIfExists(tempDir);

0 commit comments

Comments
 (0)