Consistent use of NIO.2 for file read/write interactions

Issue: SPR-15748
This commit is contained in:
Juergen Hoeller
2017-07-18 00:54:41 +02:00
parent d56fedc226
commit 12114a9d4c
10 changed files with 118 additions and 125 deletions

View File

@@ -17,7 +17,6 @@
package org.springframework.core.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -25,7 +24,9 @@ import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.StandardOpenOption;
import org.springframework.util.ResourceUtils;
@@ -127,7 +128,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
@Override
public ReadableByteChannel readableChannel() throws IOException {
if (isFile()) {
return new FileInputStream(getFile()).getChannel();
return FileChannel.open(getFile().toPath(), StandardOpenOption.READ);
}
else {
return super.readableChannel();

View File

@@ -17,15 +17,16 @@
package org.springframework.core.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -35,9 +36,15 @@ import org.springframework.util.StringUtils;
* Supports resolution as a {@code File} and also as a {@code URL}.
* Implements the extended {@link WritableResource} interface.
*
* <p>Note: As of Spring Framework 5.0, this {@link Resource} implementation
* uses NIO.2 API for read/write interactions. Nevertheless, in contrast to
* {@link PathResource}, it primarily manages a {@code java.io.File} handle.
*
* @author Juergen Hoeller
* @since 28.12.2003
* @see PathResource
* @see java.io.File
* @see java.nio.file.Files
*/
public class FileSystemResource extends AbstractResource implements WritableResource {
@@ -113,7 +120,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
*/
@Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
return Files.newInputStream(this.file.toPath());
}
/**
@@ -133,7 +140,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
*/
@Override
public OutputStream getOutputStream() throws IOException {
return new FileOutputStream(this.file);
return Files.newOutputStream(this.file.toPath());
}
/**
@@ -176,7 +183,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
*/
@Override
public ReadableByteChannel readableChannel() throws IOException {
return new FileInputStream(this.file).getChannel();
return FileChannel.open(getFile().toPath(), StandardOpenOption.READ);
}
/**
@@ -185,7 +192,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
*/
@Override
public WritableByteChannel writableChannel() throws IOException {
return new FileOutputStream(this.file).getChannel();
return FileChannel.open(getFile().toPath(), StandardOpenOption.WRITE);
}
/**

View File

@@ -41,7 +41,9 @@ import org.springframework.util.Assert;
* @author Philippe Marschall
* @author Juergen Hoeller
* @since 4.0
* @see FileSystemResource
* @see java.nio.file.Path
* @see java.nio.file.Files
*/
public class PathResource extends AbstractResource implements WritableResource {
@@ -52,8 +54,7 @@ public class PathResource extends AbstractResource implements WritableResource {
* Create a new PathResource from a Path handle.
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
* via {@link #createRelative}, the relative path will be built <i>underneath</i>
* the given root:
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* @param path a Path handle
*/
public PathResource(Path path) {
@@ -65,8 +66,7 @@ public class PathResource extends AbstractResource implements WritableResource {
* Create a new PathResource from a Path handle.
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
* via {@link #createRelative}, the relative path will be built <i>underneath</i>
* the given root:
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* @param path a path
* @see java.nio.file.Paths#get(String, String...)
*/
@@ -79,8 +79,7 @@ public class PathResource extends AbstractResource implements WritableResource {
* Create a new PathResource from a Path handle.
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
* via {@link #createRelative}, the relative path will be built <i>underneath</i>
* the given root:
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
* @see java.nio.file.Paths#get(URI)
* @param uri a path URI
*/
@@ -193,7 +192,7 @@ public class PathResource extends AbstractResource implements WritableResource {
catch (UnsupportedOperationException ex) {
// Only paths on the default file system can be converted to a File:
// Do exception translation for cases where conversion is not possible.
throw new FileNotFoundException(this.path + " cannot be resolved to " + "absolute file path");
throw new FileNotFoundException(this.path + " cannot be resolved to absolute file path");
}
}
@@ -231,11 +230,11 @@ public class PathResource extends AbstractResource implements WritableResource {
public long lastModified() throws IOException {
// We can not use the superclass method since it uses conversion to a File and
// only a Path on the default file system can be converted to a File...
return Files.getLastModifiedTime(path).toMillis();
return Files.getLastModifiedTime(this.path).toMillis();
}
/**
* This implementation creates a FileResource, applying the given path
* This implementation creates a PathResource, applying the given path
* relative to the path of the underlying file of this resource descriptor.
* @see java.nio.file.Path#resolve(String)
*/

View File

@@ -16,19 +16,16 @@
package org.springframework.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.file.Files;
import org.springframework.lang.Nullable;
@@ -42,6 +39,7 @@ import org.springframework.lang.Nullable;
* @author Juergen Hoeller
* @since 06.10.2003
* @see StreamUtils
* @see FileSystemUtils
*/
public abstract class FileCopyUtils {
@@ -62,9 +60,7 @@ public abstract class FileCopyUtils {
public static int copy(File in, File out) throws IOException {
Assert.notNull(in, "No input File specified");
Assert.notNull(out, "No output File specified");
return copy(new BufferedInputStream(new FileInputStream(in)),
new BufferedOutputStream(new FileOutputStream(out)));
return copy(Files.newInputStream(in.toPath()), Files.newOutputStream(out.toPath()));
}
/**
@@ -76,10 +72,7 @@ public abstract class FileCopyUtils {
public static void copy(byte[] in, File out) throws IOException {
Assert.notNull(in, "No input byte array specified");
Assert.notNull(out, "No output File specified");
ByteArrayInputStream inStream = new ByteArrayInputStream(in);
OutputStream outStream = new BufferedOutputStream(new FileOutputStream(out));
copy(inStream, outStream);
copy(new ByteArrayInputStream(in), Files.newOutputStream(out.toPath()));
}
/**
@@ -90,8 +83,7 @@ public abstract class FileCopyUtils {
*/
public static byte[] copyToByteArray(File in) throws IOException {
Assert.notNull(in, "No input File specified");
return copyToByteArray(new BufferedInputStream(new FileInputStream(in)));
return copyToByteArray(Files.newInputStream(in.toPath()));
}

View File

@@ -18,6 +18,11 @@ package org.springframework.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import org.springframework.lang.Nullable;
@@ -27,29 +32,60 @@ import org.springframework.lang.Nullable;
* @author Rob Harrop
* @author Juergen Hoeller
* @since 2.5.3
* @deprecated as of Spring Framework 5.0, in favor of native NIO API usage
* @see java.io.File
* @see java.nio.file.Path
* @see java.nio.file.Files
*/
@Deprecated
public abstract class FileSystemUtils {
/**
* Delete the supplied {@link File} - for directories,
* recursively delete any nested directories or files as well.
* <p>Note: Like {@link File#delete()}, this method does not throw any
* exception but rather silently returns {@code false} in case of I/O
* errors. Consider using {@link #deleteRecursively(Path)} for NIO-style
* handling of I/O errors, clearly differentiating between non-existence
* and failure to delete an existing file.
* @param root the root {@code File} to delete
* @return {@code true} if the {@code File} was deleted,
* @return {@code true} if the {@code File} was successfully deleted,
* otherwise {@code false}
*/
public static boolean deleteRecursively(@Nullable File root) {
if (root != null && root.exists()) {
if (root.isDirectory()) {
File[] children = root.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursively(child);
}
}
if (root != null) {
try {
return deleteRecursively(root.toPath());
}
return root.delete();
catch (IOException ex) {
return false;
}
}
return false;
}
/**
* Delete the supplied {@link File} - for directories,
* recursively delete any nested directories or files as well.
* @param root the root {@code File} to delete
* @return {@code true} if the {@code File} existed and was deleted,
* or {@code false} it it did not exist
* @throws IOException in the case of I/O errors
* @since 5.0
*/
public static boolean deleteRecursively(@Nullable Path root) throws IOException {
if (root != null) {
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
return Files.deleteIfExists(root);
}
return false;
}
@@ -61,43 +97,44 @@ public abstract class FileSystemUtils {
* @param dest the destination directory
* @throws IOException in the case of I/O errors
*/
public static void copyRecursively(@Nullable File src, File dest) throws IOException {
Assert.isTrue(src != null && (src.isDirectory() || src.isFile()),
"Source File must denote a directory or file");
public static void copyRecursively(File src, File dest) throws IOException {
Assert.notNull(src, "Source File must not be null");
Assert.notNull(dest, "Destination File must not be null");
doCopyRecursively(src, dest);
copyRecursively(src.toPath(), dest.toPath());
}
/**
* Actually copy the contents of the {@code src} file/directory
* Recursively copy the contents of the {@code src} file/directory
* to the {@code dest} file/directory.
* @param src the source directory
* @param dest the destination directory
* @throws IOException in the case of I/O errors
* @since 5.0
*/
private static void doCopyRecursively(File src, File dest) throws IOException {
if (src.isDirectory()) {
dest.mkdir();
File[] entries = src.listFiles();
if (entries == null) {
throw new IOException("Could not list files in directory: " + src);
}
for (File entry : entries) {
doCopyRecursively(entry, new File(dest, entry.getName()));
}
public static void copyRecursively(Path src, Path dest) throws IOException {
Assert.notNull(src, "Source Path must not be null");
Assert.notNull(dest, "Destination Path must not be null");
BasicFileAttributes srcAttr = Files.readAttributes(src, BasicFileAttributes.class);
if (srcAttr.isDirectory()) {
Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Files.createDirectories(dest.resolve(src.relativize(dir)));
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.copy(file, dest.resolve(src.relativize(file)));
return FileVisitResult.CONTINUE;
}
});
}
else if (src.isFile()) {
try {
dest.createNewFile();
}
catch (IOException ex) {
throw new IOException("Failed to create file: " + dest, ex);
}
FileCopyUtils.copy(src, dest);
else if (srcAttr.isRegularFile()) {
Files.copy(src, dest);
}
else {
// Special File handle: neither a file not a directory.
// Simply skip it when contained in nested directory...
throw new IllegalArgumentException("Source File must denote a directory or file");
}
}