Skip to content

Commit 12114a9

Browse files
committed
Consistent use of NIO.2 for file read/write interactions
Issue: SPR-15748
1 parent d56fedc commit 12114a9

File tree

10 files changed

+118
-125
lines changed

10 files changed

+118
-125
lines changed

spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
1717
package org.springframework.core.io;
1818

1919
import java.io.File;
20-
import java.io.FileInputStream;
2120
import java.io.FileNotFoundException;
2221
import java.io.IOException;
2322
import java.io.InputStream;
2423
import java.net.HttpURLConnection;
2524
import java.net.URI;
2625
import java.net.URL;
2726
import java.net.URLConnection;
27+
import java.nio.channels.FileChannel;
2828
import java.nio.channels.ReadableByteChannel;
29+
import java.nio.file.StandardOpenOption;
2930

3031
import org.springframework.util.ResourceUtils;
3132

@@ -127,7 +128,7 @@ protected File getFile(URI uri) throws IOException {
127128
@Override
128129
public ReadableByteChannel readableChannel() throws IOException {
129130
if (isFile()) {
130-
return new FileInputStream(getFile()).getChannel();
131+
return FileChannel.open(getFile().toPath(), StandardOpenOption.READ);
131132
}
132133
else {
133134
return super.readableChannel();

spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
1717
package org.springframework.core.io;
1818

1919
import java.io.File;
20-
import java.io.FileInputStream;
21-
import java.io.FileOutputStream;
2220
import java.io.IOException;
2321
import java.io.InputStream;
2422
import java.io.OutputStream;
2523
import java.net.URI;
2624
import java.net.URL;
25+
import java.nio.channels.FileChannel;
2726
import java.nio.channels.ReadableByteChannel;
2827
import java.nio.channels.WritableByteChannel;
28+
import java.nio.file.Files;
29+
import java.nio.file.StandardOpenOption;
2930

3031
import org.springframework.util.Assert;
3132
import org.springframework.util.StringUtils;
@@ -35,9 +36,15 @@
3536
* Supports resolution as a {@code File} and also as a {@code URL}.
3637
* Implements the extended {@link WritableResource} interface.
3738
*
39+
* <p>Note: As of Spring Framework 5.0, this {@link Resource} implementation
40+
* uses NIO.2 API for read/write interactions. Nevertheless, in contrast to
41+
* {@link PathResource}, it primarily manages a {@code java.io.File} handle.
42+
*
3843
* @author Juergen Hoeller
3944
* @since 28.12.2003
45+
* @see PathResource
4046
* @see java.io.File
47+
* @see java.nio.file.Files
4148
*/
4249
public class FileSystemResource extends AbstractResource implements WritableResource {
4350

@@ -113,7 +120,7 @@ public boolean isReadable() {
113120
*/
114121
@Override
115122
public InputStream getInputStream() throws IOException {
116-
return new FileInputStream(this.file);
123+
return Files.newInputStream(this.file.toPath());
117124
}
118125

119126
/**
@@ -133,7 +140,7 @@ public boolean isWritable() {
133140
*/
134141
@Override
135142
public OutputStream getOutputStream() throws IOException {
136-
return new FileOutputStream(this.file);
143+
return Files.newOutputStream(this.file.toPath());
137144
}
138145

139146
/**
@@ -176,7 +183,7 @@ public File getFile() {
176183
*/
177184
@Override
178185
public ReadableByteChannel readableChannel() throws IOException {
179-
return new FileInputStream(this.file).getChannel();
186+
return FileChannel.open(getFile().toPath(), StandardOpenOption.READ);
180187
}
181188

182189
/**
@@ -185,7 +192,7 @@ public ReadableByteChannel readableChannel() throws IOException {
185192
*/
186193
@Override
187194
public WritableByteChannel writableChannel() throws IOException {
188-
return new FileOutputStream(this.file).getChannel();
195+
return FileChannel.open(getFile().toPath(), StandardOpenOption.WRITE);
189196
}
190197

191198
/**

spring-core/src/main/java/org/springframework/core/io/PathResource.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
* @author Philippe Marschall
4242
* @author Juergen Hoeller
4343
* @since 4.0
44+
* @see FileSystemResource
4445
* @see java.nio.file.Path
46+
* @see java.nio.file.Files
4547
*/
4648
public class PathResource extends AbstractResource implements WritableResource {
4749

@@ -52,8 +54,7 @@ public class PathResource extends AbstractResource implements WritableResource {
5254
* Create a new PathResource from a Path handle.
5355
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
5456
* via {@link #createRelative}, the relative path will be built <i>underneath</i>
55-
* the given root:
56-
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
57+
* the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
5758
* @param path a Path handle
5859
*/
5960
public PathResource(Path path) {
@@ -65,8 +66,7 @@ public PathResource(Path path) {
6566
* Create a new PathResource from a Path handle.
6667
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
6768
* via {@link #createRelative}, the relative path will be built <i>underneath</i>
68-
* the given root:
69-
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
69+
* the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
7070
* @param path a path
7171
* @see java.nio.file.Paths#get(String, String...)
7272
*/
@@ -79,8 +79,7 @@ public PathResource(String path) {
7979
* Create a new PathResource from a Path handle.
8080
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
8181
* via {@link #createRelative}, the relative path will be built <i>underneath</i>
82-
* the given root:
83-
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
82+
* the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
8483
* @see java.nio.file.Paths#get(URI)
8584
* @param uri a path URI
8685
*/
@@ -193,7 +192,7 @@ public File getFile() throws IOException {
193192
catch (UnsupportedOperationException ex) {
194193
// Only paths on the default file system can be converted to a File:
195194
// Do exception translation for cases where conversion is not possible.
196-
throw new FileNotFoundException(this.path + " cannot be resolved to " + "absolute file path");
195+
throw new FileNotFoundException(this.path + " cannot be resolved to absolute file path");
197196
}
198197
}
199198

@@ -231,11 +230,11 @@ public long contentLength() throws IOException {
231230
public long lastModified() throws IOException {
232231
// We can not use the superclass method since it uses conversion to a File and
233232
// only a Path on the default file system can be converted to a File...
234-
return Files.getLastModifiedTime(path).toMillis();
233+
return Files.getLastModifiedTime(this.path).toMillis();
235234
}
236235

237236
/**
238-
* This implementation creates a FileResource, applying the given path
237+
* This implementation creates a PathResource, applying the given path
239238
* relative to the path of the underlying file of this resource descriptor.
240239
* @see java.nio.file.Path#resolve(String)
241240
*/

spring-core/src/main/java/org/springframework/util/FileCopyUtils.java

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,16 @@
1616

1717
package org.springframework.util;
1818

19-
import java.io.BufferedInputStream;
20-
import java.io.BufferedOutputStream;
2119
import java.io.ByteArrayInputStream;
2220
import java.io.ByteArrayOutputStream;
2321
import java.io.File;
24-
import java.io.FileInputStream;
25-
import java.io.FileOutputStream;
2622
import java.io.IOException;
2723
import java.io.InputStream;
2824
import java.io.OutputStream;
2925
import java.io.Reader;
3026
import java.io.StringWriter;
3127
import java.io.Writer;
28+
import java.nio.file.Files;
3229

3330
import org.springframework.lang.Nullable;
3431

@@ -42,6 +39,7 @@
4239
* @author Juergen Hoeller
4340
* @since 06.10.2003
4441
* @see StreamUtils
42+
* @see FileSystemUtils
4543
*/
4644
public abstract class FileCopyUtils {
4745

@@ -62,9 +60,7 @@ public abstract class FileCopyUtils {
6260
public static int copy(File in, File out) throws IOException {
6361
Assert.notNull(in, "No input File specified");
6462
Assert.notNull(out, "No output File specified");
65-
66-
return copy(new BufferedInputStream(new FileInputStream(in)),
67-
new BufferedOutputStream(new FileOutputStream(out)));
63+
return copy(Files.newInputStream(in.toPath()), Files.newOutputStream(out.toPath()));
6864
}
6965

7066
/**
@@ -76,10 +72,7 @@ public static int copy(File in, File out) throws IOException {
7672
public static void copy(byte[] in, File out) throws IOException {
7773
Assert.notNull(in, "No input byte array specified");
7874
Assert.notNull(out, "No output File specified");
79-
80-
ByteArrayInputStream inStream = new ByteArrayInputStream(in);
81-
OutputStream outStream = new BufferedOutputStream(new FileOutputStream(out));
82-
copy(inStream, outStream);
75+
copy(new ByteArrayInputStream(in), Files.newOutputStream(out.toPath()));
8376
}
8477

8578
/**
@@ -90,8 +83,7 @@ public static void copy(byte[] in, File out) throws IOException {
9083
*/
9184
public static byte[] copyToByteArray(File in) throws IOException {
9285
Assert.notNull(in, "No input File specified");
93-
94-
return copyToByteArray(new BufferedInputStream(new FileInputStream(in)));
86+
return copyToByteArray(Files.newInputStream(in.toPath()));
9587
}
9688

9789

spring-core/src/main/java/org/springframework/util/FileSystemUtils.java

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.nio.file.FileVisitResult;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.nio.file.SimpleFileVisitor;
25+
import java.nio.file.attribute.BasicFileAttributes;
2126

2227
import org.springframework.lang.Nullable;
2328

@@ -27,29 +32,60 @@
2732
* @author Rob Harrop
2833
* @author Juergen Hoeller
2934
* @since 2.5.3
30-
* @deprecated as of Spring Framework 5.0, in favor of native NIO API usage
35+
* @see java.io.File
36+
* @see java.nio.file.Path
37+
* @see java.nio.file.Files
3138
*/
32-
@Deprecated
3339
public abstract class FileSystemUtils {
3440

3541
/**
3642
* Delete the supplied {@link File} - for directories,
3743
* recursively delete any nested directories or files as well.
44+
* <p>Note: Like {@link File#delete()}, this method does not throw any
45+
* exception but rather silently returns {@code false} in case of I/O
46+
* errors. Consider using {@link #deleteRecursively(Path)} for NIO-style
47+
* handling of I/O errors, clearly differentiating between non-existence
48+
* and failure to delete an existing file.
3849
* @param root the root {@code File} to delete
39-
* @return {@code true} if the {@code File} was deleted,
50+
* @return {@code true} if the {@code File} was successfully deleted,
4051
* otherwise {@code false}
4152
*/
4253
public static boolean deleteRecursively(@Nullable File root) {
43-
if (root != null && root.exists()) {
44-
if (root.isDirectory()) {
45-
File[] children = root.listFiles();
46-
if (children != null) {
47-
for (File child : children) {
48-
deleteRecursively(child);
49-
}
50-
}
54+
if (root != null) {
55+
try {
56+
return deleteRecursively(root.toPath());
57+
}
58+
catch (IOException ex) {
59+
return false;
5160
}
52-
return root.delete();
61+
}
62+
return false;
63+
}
64+
65+
/**
66+
* Delete the supplied {@link File} - for directories,
67+
* recursively delete any nested directories or files as well.
68+
* @param root the root {@code File} to delete
69+
* @return {@code true} if the {@code File} existed and was deleted,
70+
* or {@code false} it it did not exist
71+
* @throws IOException in the case of I/O errors
72+
* @since 5.0
73+
*/
74+
public static boolean deleteRecursively(@Nullable Path root) throws IOException {
75+
if (root != null) {
76+
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
77+
@Override
78+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
79+
Files.delete(file);
80+
return FileVisitResult.CONTINUE;
81+
}
82+
@Override
83+
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
84+
Files.delete(dir);
85+
return FileVisitResult.CONTINUE;
86+
}
87+
});
88+
return Files.deleteIfExists(root);
5389
}
5490
return false;
5591
}
@@ -61,43 +97,44 @@ public static boolean deleteRecursively(@Nullable File root) {
6197
* @param dest the destination directory
6298
* @throws IOException in the case of I/O errors
6399
*/
64-
public static void copyRecursively(@Nullable File src, File dest) throws IOException {
65-
Assert.isTrue(src != null && (src.isDirectory() || src.isFile()),
66-
"Source File must denote a directory or file");
100+
public static void copyRecursively(File src, File dest) throws IOException {
101+
Assert.notNull(src, "Source File must not be null");
67102
Assert.notNull(dest, "Destination File must not be null");
68-
doCopyRecursively(src, dest);
103+
copyRecursively(src.toPath(), dest.toPath());
69104
}
70105

71106
/**
72-
* Actually copy the contents of the {@code src} file/directory
107+
* Recursively copy the contents of the {@code src} file/directory
73108
* to the {@code dest} file/directory.
74109
* @param src the source directory
75110
* @param dest the destination directory
76111
* @throws IOException in the case of I/O errors
112+
* @since 5.0
77113
*/
78-
private static void doCopyRecursively(File src, File dest) throws IOException {
79-
if (src.isDirectory()) {
80-
dest.mkdir();
81-
File[] entries = src.listFiles();
82-
if (entries == null) {
83-
throw new IOException("Could not list files in directory: " + src);
84-
}
85-
for (File entry : entries) {
86-
doCopyRecursively(entry, new File(dest, entry.getName()));
87-
}
114+
public static void copyRecursively(Path src, Path dest) throws IOException {
115+
Assert.notNull(src, "Source Path must not be null");
116+
Assert.notNull(dest, "Destination Path must not be null");
117+
BasicFileAttributes srcAttr = Files.readAttributes(src, BasicFileAttributes.class);
118+
119+
if (srcAttr.isDirectory()) {
120+
Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
121+
@Override
122+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
123+
Files.createDirectories(dest.resolve(src.relativize(dir)));
124+
return FileVisitResult.CONTINUE;
125+
}
126+
@Override
127+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
128+
Files.copy(file, dest.resolve(src.relativize(file)));
129+
return FileVisitResult.CONTINUE;
130+
}
131+
});
88132
}
89-
else if (src.isFile()) {
90-
try {
91-
dest.createNewFile();
92-
}
93-
catch (IOException ex) {
94-
throw new IOException("Failed to create file: " + dest, ex);
95-
}
96-
FileCopyUtils.copy(src, dest);
133+
else if (srcAttr.isRegularFile()) {
134+
Files.copy(src, dest);
97135
}
98136
else {
99-
// Special File handle: neither a file not a directory.
100-
// Simply skip it when contained in nested directory...
137+
throw new IllegalArgumentException("Source File must denote a directory or file");
101138
}
102139
}
103140

spring-core/src/test/java/org/springframework/util/FileSystemUtilsTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
/**
2727
* @author Rob Harrop
2828
*/
29-
@Deprecated
3029
public class FileSystemUtilsTests {
3130

3231
@Test

0 commit comments

Comments
 (0)