Skip to content

Commit 38f9a7b

Browse files
committed
FileSystemResource supports java.nio.file.Path based setup
Issue: SPR-16833
1 parent bb6ab5d commit 38f9a7b

File tree

3 files changed

+85
-35
lines changed

3 files changed

+85
-35
lines changed

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

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,33 +28,59 @@
2828
import java.nio.channels.WritableByteChannel;
2929
import java.nio.file.Files;
3030
import java.nio.file.NoSuchFileException;
31+
import java.nio.file.Path;
3132
import java.nio.file.StandardOpenOption;
3233

34+
import org.springframework.lang.Nullable;
3335
import org.springframework.util.Assert;
3436
import org.springframework.util.StringUtils;
3537

3638
/**
37-
* {@link Resource} implementation for {@code java.io.File} handles.
39+
* {@link Resource} implementation for {@code java.io.File} and
40+
* {@code java.nio.file.Path} handles with a file system target.
3841
* Supports resolution as a {@code File} and also as a {@code URL}.
3942
* Implements the extended {@link WritableResource} interface.
4043
*
41-
* <p>Note: As of Spring Framework 5.0, this {@link Resource} implementation
42-
* uses NIO.2 API for read/write interactions. Nevertheless, in contrast to
43-
* {@link PathResource}, it primarily manages a {@code java.io.File} handle.
44+
* <p>Note: As of Spring Framework 5.0, this {@link Resource} implementation uses
45+
* NIO.2 API for read/write interactions. As of 5.1, it may be constructed with a
46+
* {@link java.nio.file.Path} handle in which case it will perform all file system
47+
* interactions via NIO.2, only resorting to {@link File} on {@link #getFile()}.
4448
*
4549
* @author Juergen Hoeller
4650
* @since 28.12.2003
47-
* @see PathResource
51+
* @see #FileSystemResource(File)
52+
* @see #FileSystemResource(Path)
4853
* @see java.io.File
4954
* @see java.nio.file.Files
5055
*/
5156
public class FileSystemResource extends AbstractResource implements WritableResource {
5257

58+
private final String path;
59+
60+
@Nullable
5361
private final File file;
5462

55-
private final String path;
63+
private final Path filePath;
5664

5765

66+
/**
67+
* Create a new {@code FileSystemResource} from a file path.
68+
* <p>Note: When building relative resources via {@link #createRelative},
69+
* it makes a difference whether the specified resource base path here
70+
* ends with a slash or not. In the case of "C:/dir1/", relative paths
71+
* will be built underneath that root: e.g. relative path "dir2" ->
72+
* "C:/dir1/dir2". In the case of "C:/dir1", relative paths will apply
73+
* at the same directory level: relative path "dir2" -> "C:/dir2".
74+
* @param path a file path
75+
* @see #FileSystemResource(Path)
76+
*/
77+
public FileSystemResource(String path) {
78+
Assert.notNull(path, "Path must not be null");
79+
this.path = StringUtils.cleanPath(path);
80+
this.file = new File(path);
81+
this.filePath = this.file.toPath();
82+
}
83+
5884
/**
5985
* Create a new {@code FileSystemResource} from a {@link File} handle.
6086
* <p>Note: When building relative resources via {@link #createRelative},
@@ -65,27 +91,31 @@ public class FileSystemResource extends AbstractResource implements WritableReso
6591
* to append a trailing slash to the root path: "C:/dir1/", which
6692
* indicates this directory as root for all relative paths.
6793
* @param file a File handle
94+
* @see #FileSystemResource(Path)
95+
* @see #getFile()
6896
*/
6997
public FileSystemResource(File file) {
7098
Assert.notNull(file, "File must not be null");
71-
this.file = file;
7299
this.path = StringUtils.cleanPath(file.getPath());
100+
this.file = file;
101+
this.filePath = file.toPath();
73102
}
74103

75104
/**
76-
* Create a new {@code FileSystemResource} from a file path.
77-
* <p>Note: When building relative resources via {@link #createRelative},
78-
* it makes a difference whether the specified resource base path here
79-
* ends with a slash or not. In the case of "C:/dir1/", relative paths
80-
* will be built underneath that root: e.g. relative path "dir2" ->
81-
* "C:/dir1/dir2". In the case of "C:/dir1", relative paths will apply
82-
* at the same directory level: relative path "dir2" -> "C:/dir2".
83-
* @param path a file path
105+
* Create a new {@code FileSystemResource} from a {@link Path} handle.
106+
* <p>In contrast to {@link PathResource}, this variant strictly follows the
107+
* general {@link FileSystemResource} conventions, in particular in terms of
108+
* path cleaning and {@link #createRelative(String)} handling.
109+
* @param filePath a Path handle to a file
110+
* @since 5.1
111+
* @see #FileSystemResource(File)
112+
* @see PathResource
84113
*/
85-
public FileSystemResource(String path) {
86-
Assert.notNull(path, "Path must not be null");
87-
this.file = new File(path);
88-
this.path = StringUtils.cleanPath(path);
114+
public FileSystemResource(Path filePath) {
115+
Assert.notNull(filePath, "Path must not be null");
116+
this.filePath = filePath;
117+
this.file = null;
118+
this.path = StringUtils.cleanPath(filePath.toString());
89119
}
90120

91121

@@ -102,7 +132,7 @@ public final String getPath() {
102132
*/
103133
@Override
104134
public boolean exists() {
105-
return this.file.exists();
135+
return (this.file != null ? this.file.exists() : Files.exists(this.filePath));
106136
}
107137

108138
/**
@@ -113,7 +143,8 @@ public boolean exists() {
113143
*/
114144
@Override
115145
public boolean isReadable() {
116-
return (this.file.canRead() && !this.file.isDirectory());
146+
return (this.file != null ? this.file.canRead() && !this.file.isDirectory() :
147+
Files.isReadable(this.filePath) && !Files.isDirectory(this.filePath));
117148
}
118149

119150
/**
@@ -123,7 +154,7 @@ public boolean isReadable() {
123154
@Override
124155
public InputStream getInputStream() throws IOException {
125156
try {
126-
return Files.newInputStream(this.file.toPath());
157+
return Files.newInputStream(this.filePath);
127158
}
128159
catch (NoSuchFileException ex) {
129160
throw new FileNotFoundException(ex.getMessage());
@@ -138,7 +169,8 @@ public InputStream getInputStream() throws IOException {
138169
*/
139170
@Override
140171
public boolean isWritable() {
141-
return (this.file.canWrite() && !this.file.isDirectory());
172+
return (this.file != null ? this.file.canWrite() && !this.file.isDirectory() :
173+
Files.isWritable(this.filePath) && !Files.isDirectory(this.filePath));
142174
}
143175

144176
/**
@@ -147,7 +179,7 @@ public boolean isWritable() {
147179
*/
148180
@Override
149181
public OutputStream getOutputStream() throws IOException {
150-
return Files.newOutputStream(this.file.toPath());
182+
return Files.newOutputStream(this.filePath);
151183
}
152184

153185
/**
@@ -156,7 +188,7 @@ public OutputStream getOutputStream() throws IOException {
156188
*/
157189
@Override
158190
public URL getURL() throws IOException {
159-
return this.file.toURI().toURL();
191+
return (this.file != null ? this.file.toURI().toURL() : this.filePath.toUri().toURL());
160192
}
161193

162194
/**
@@ -165,7 +197,7 @@ public URL getURL() throws IOException {
165197
*/
166198
@Override
167199
public URI getURI() throws IOException {
168-
return this.file.toURI();
200+
return (this.file != null ? this.file.toURI() : this.filePath.toUri());
169201
}
170202

171203
/**
@@ -181,7 +213,7 @@ public boolean isFile() {
181213
*/
182214
@Override
183215
public File getFile() {
184-
return this.file;
216+
return (this.file != null ? this.file : this.filePath.toFile());
185217
}
186218

187219
/**
@@ -191,7 +223,7 @@ public File getFile() {
191223
@Override
192224
public ReadableByteChannel readableChannel() throws IOException {
193225
try {
194-
return FileChannel.open(this.file.toPath(), StandardOpenOption.READ);
226+
return FileChannel.open(this.filePath, StandardOpenOption.READ);
195227
}
196228
catch (NoSuchFileException ex) {
197229
throw new FileNotFoundException(ex.getMessage());
@@ -204,15 +236,15 @@ public ReadableByteChannel readableChannel() throws IOException {
204236
*/
205237
@Override
206238
public WritableByteChannel writableChannel() throws IOException {
207-
return FileChannel.open(this.file.toPath(), StandardOpenOption.WRITE);
239+
return FileChannel.open(this.filePath, StandardOpenOption.WRITE);
208240
}
209241

210242
/**
211243
* This implementation returns the underlying File's length.
212244
*/
213245
@Override
214246
public long contentLength() throws IOException {
215-
return this.file.length();
247+
return (this.file != null ? this.file.length() : Files.size(this.filePath));
216248
}
217249

218250
/**
@@ -232,7 +264,7 @@ public Resource createRelative(String relativePath) {
232264
*/
233265
@Override
234266
public String getFilename() {
235-
return this.file.getName();
267+
return (this.file != null ? this.file.getName() : this.filePath.getFileName().toString());
236268
}
237269

238270
/**
@@ -242,7 +274,7 @@ public String getFilename() {
242274
*/
243275
@Override
244276
public String getDescription() {
245-
return "file [" + this.file.getAbsolutePath() + "]";
277+
return "file [" + (this.file != null ? this.file.getAbsolutePath() : this.filePath.toAbsolutePath()) + "]";
246278
}
247279

248280

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,20 @@
3535
import org.springframework.util.Assert;
3636

3737
/**
38-
* {@link Resource} implementation for {@code java.nio.file.Path} handles.
39-
* Supports resolution as File, and also as URL.
38+
* {@link Resource} implementation for {@link java.nio.file.Path} handles,
39+
* performing all operations and transformations via the {@code Path} API.
40+
* Supports resolution as a {@link File} and also as a {@link URL}.
4041
* Implements the extended {@link WritableResource} interface.
4142
*
43+
* <p>Note: As of 5.1, {@link java.nio.file.Path} support is also available
44+
* in {@link FileSystemResource#FileSystemResource(Path) FileSystemResource},
45+
* applying Spring's standard String-based path transformations but
46+
* performing all operations via the {@link java.nio.file.Files} API.
47+
*
4248
* @author Philippe Marschall
4349
* @author Juergen Hoeller
4450
* @since 4.0
45-
* @see FileSystemResource
51+
* @see FileSystemResource#FileSystemResource(Path)
4652
* @see java.nio.file.Path
4753
* @see java.nio.file.Files
4854
*/

spring-core/src/test/java/org/springframework/core/io/ResourceTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.io.InputStreamReader;
2424
import java.nio.ByteBuffer;
2525
import java.nio.channels.ReadableByteChannel;
26+
import java.nio.file.Path;
27+
import java.nio.file.Paths;
2628
import java.util.HashSet;
2729

2830
import org.junit.Ignore;
@@ -128,6 +130,16 @@ public void testFileSystemResource() throws IOException {
128130
assertEquals(resource2, new FileSystemResource("core/../core/io/./Resource.class"));
129131
}
130132

133+
@Test
134+
public void testFileSystemResourceWithFilePath() throws Exception {
135+
Path filePath = Paths.get(getClass().getResource("Resource.class").toURI());
136+
Resource resource = new FileSystemResource(filePath);
137+
doTestResource(resource);
138+
assertEquals(new FileSystemResource(filePath), resource);
139+
Resource resource2 = new FileSystemResource("core/io/Resource.class");
140+
assertEquals(resource2, new FileSystemResource("core/../core/io/./Resource.class"));
141+
}
142+
131143
@Test
132144
public void testUrlResource() throws IOException {
133145
Resource resource = new UrlResource(getClass().getResource("Resource.class"));

0 commit comments

Comments
 (0)