28
28
import java .nio .channels .WritableByteChannel ;
29
29
import java .nio .file .Files ;
30
30
import java .nio .file .NoSuchFileException ;
31
+ import java .nio .file .Path ;
31
32
import java .nio .file .StandardOpenOption ;
32
33
34
+ import org .springframework .lang .Nullable ;
33
35
import org .springframework .util .Assert ;
34
36
import org .springframework .util .StringUtils ;
35
37
36
38
/**
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.
38
41
* Supports resolution as a {@code File} and also as a {@code URL}.
39
42
* Implements the extended {@link WritableResource} interface.
40
43
*
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()}.
44
48
*
45
49
* @author Juergen Hoeller
46
50
* @since 28.12.2003
47
- * @see PathResource
51
+ * @see #FileSystemResource(File)
52
+ * @see #FileSystemResource(Path)
48
53
* @see java.io.File
49
54
* @see java.nio.file.Files
50
55
*/
51
56
public class FileSystemResource extends AbstractResource implements WritableResource {
52
57
58
+ private final String path ;
59
+
60
+ @ Nullable
53
61
private final File file ;
54
62
55
- private final String path ;
63
+ private final Path filePath ;
56
64
57
65
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
+
58
84
/**
59
85
* Create a new {@code FileSystemResource} from a {@link File} handle.
60
86
* <p>Note: When building relative resources via {@link #createRelative},
@@ -65,27 +91,31 @@ public class FileSystemResource extends AbstractResource implements WritableReso
65
91
* to append a trailing slash to the root path: "C:/dir1/", which
66
92
* indicates this directory as root for all relative paths.
67
93
* @param file a File handle
94
+ * @see #FileSystemResource(Path)
95
+ * @see #getFile()
68
96
*/
69
97
public FileSystemResource (File file ) {
70
98
Assert .notNull (file , "File must not be null" );
71
- this .file = file ;
72
99
this .path = StringUtils .cleanPath (file .getPath ());
100
+ this .file = file ;
101
+ this .filePath = file .toPath ();
73
102
}
74
103
75
104
/**
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
84
113
*/
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 ());
89
119
}
90
120
91
121
@@ -102,7 +132,7 @@ public final String getPath() {
102
132
*/
103
133
@ Override
104
134
public boolean exists () {
105
- return this .file . exists ();
135
+ return ( this .file != null ? this . file . exists () : Files . exists ( this . filePath ) );
106
136
}
107
137
108
138
/**
@@ -113,7 +143,8 @@ public boolean exists() {
113
143
*/
114
144
@ Override
115
145
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 ));
117
148
}
118
149
119
150
/**
@@ -123,7 +154,7 @@ public boolean isReadable() {
123
154
@ Override
124
155
public InputStream getInputStream () throws IOException {
125
156
try {
126
- return Files .newInputStream (this .file . toPath () );
157
+ return Files .newInputStream (this .filePath );
127
158
}
128
159
catch (NoSuchFileException ex ) {
129
160
throw new FileNotFoundException (ex .getMessage ());
@@ -138,7 +169,8 @@ public InputStream getInputStream() throws IOException {
138
169
*/
139
170
@ Override
140
171
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 ));
142
174
}
143
175
144
176
/**
@@ -147,7 +179,7 @@ public boolean isWritable() {
147
179
*/
148
180
@ Override
149
181
public OutputStream getOutputStream () throws IOException {
150
- return Files .newOutputStream (this .file . toPath () );
182
+ return Files .newOutputStream (this .filePath );
151
183
}
152
184
153
185
/**
@@ -156,7 +188,7 @@ public OutputStream getOutputStream() throws IOException {
156
188
*/
157
189
@ Override
158
190
public URL getURL () throws IOException {
159
- return this .file . toURI ().toURL ();
191
+ return ( this .file != null ? this . file . toURI ().toURL () : this . filePath . toUri (). toURL () );
160
192
}
161
193
162
194
/**
@@ -165,7 +197,7 @@ public URL getURL() throws IOException {
165
197
*/
166
198
@ Override
167
199
public URI getURI () throws IOException {
168
- return this .file . toURI ();
200
+ return ( this .file != null ? this . file . toURI () : this . filePath . toUri () );
169
201
}
170
202
171
203
/**
@@ -181,7 +213,7 @@ public boolean isFile() {
181
213
*/
182
214
@ Override
183
215
public File getFile () {
184
- return this .file ;
216
+ return ( this .file != null ? this . file : this . filePath . toFile ()) ;
185
217
}
186
218
187
219
/**
@@ -191,7 +223,7 @@ public File getFile() {
191
223
@ Override
192
224
public ReadableByteChannel readableChannel () throws IOException {
193
225
try {
194
- return FileChannel .open (this .file . toPath () , StandardOpenOption .READ );
226
+ return FileChannel .open (this .filePath , StandardOpenOption .READ );
195
227
}
196
228
catch (NoSuchFileException ex ) {
197
229
throw new FileNotFoundException (ex .getMessage ());
@@ -204,15 +236,15 @@ public ReadableByteChannel readableChannel() throws IOException {
204
236
*/
205
237
@ Override
206
238
public WritableByteChannel writableChannel () throws IOException {
207
- return FileChannel .open (this .file . toPath () , StandardOpenOption .WRITE );
239
+ return FileChannel .open (this .filePath , StandardOpenOption .WRITE );
208
240
}
209
241
210
242
/**
211
243
* This implementation returns the underlying File's length.
212
244
*/
213
245
@ Override
214
246
public long contentLength () throws IOException {
215
- return this .file . length ();
247
+ return ( this .file != null ? this . file . length () : Files . size ( this . filePath ) );
216
248
}
217
249
218
250
/**
@@ -232,7 +264,7 @@ public Resource createRelative(String relativePath) {
232
264
*/
233
265
@ Override
234
266
public String getFilename () {
235
- return this .file . getName ();
267
+ return ( this .file != null ? this . file . getName () : this . filePath . getFileName (). toString () );
236
268
}
237
269
238
270
/**
@@ -242,7 +274,7 @@ public String getFilename() {
242
274
*/
243
275
@ Override
244
276
public String getDescription () {
245
- return "file [" + this .file . getAbsolutePath () + "]" ;
277
+ return "file [" + ( this .file != null ? this . file . getAbsolutePath () : this . filePath . toAbsolutePath () ) + "]" ;
246
278
}
247
279
248
280
0 commit comments