Skip to content

Commit 2313c9a

Browse files
marschallphilwebb
authored andcommitted
Introduce java.nio.file.Path based Resource
Develop new org.springframework.core.io.Resource implementation backed by java.nio.file.Path. Primarily developed to allow custom file system implementations to be used with Spring. Since the minimum requirement for Spring is still Java 6 the existing FileSystemResource can't be retrofitted (and no #getPath method can be added to the Resource interface). Unlike FileSystemResource, PathResource delegates to the underlying file system instead of StringUtils. It has therefore slightly different semantics. First, when building relative resources via createRelative the relative path will apply to this path (like URL or Unix). Second, equality is delegated to the underlying file system provider so it's case-insensitive on Windows. Issue: SPR-10608
1 parent 1f5467a commit 2313c9a

File tree

3 files changed

+531
-0
lines changed

3 files changed

+531
-0
lines changed
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.io;
18+
19+
import java.io.File;
20+
import java.io.FileNotFoundException;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.io.OutputStream;
24+
import java.net.URI;
25+
import java.net.URL;
26+
import java.nio.file.Files;
27+
import java.nio.file.OpenOption;
28+
import java.nio.file.Path;
29+
import java.nio.file.Paths;
30+
31+
import org.springframework.util.Assert;
32+
33+
/**
34+
* {@link Resource} implementation for {@code java.nio.file.Path} handles.
35+
* Supports resolution as File, and also as URL.
36+
* Implements the extended {@link WritableResource} interface.
37+
*
38+
* @author Philippe Marschall
39+
* @since 4.0
40+
* @see java.nio.file.Path
41+
*/
42+
public class PathResource extends AbstractResource implements WritableResource {
43+
44+
private final Path path;
45+
46+
47+
/**
48+
* Create a new PathResource from a Path handle.
49+
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
50+
* via {@link #createRelative}, the relative path will be built <i>underneath</i> the
51+
* given root:
52+
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
53+
* @param path a Path handle
54+
*/
55+
public PathResource(Path path) {
56+
Assert.notNull(path, "Path must not be null");
57+
this.path = path.normalize();
58+
}
59+
60+
/**
61+
* Create a new PathResource from a Path handle.
62+
* Create a new PathResource from a Path handle.
63+
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
64+
* via {@link #createRelative}, the relative path will be built <i>underneath</i> the
65+
* given root:
66+
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
67+
* @param path a path
68+
* @see java.nio.file.Paths#get(String, String...)
69+
*/
70+
public PathResource(String path) {
71+
Assert.notNull(path, "Path must not be null");
72+
this.path = Paths.get(path).normalize();
73+
}
74+
75+
/**
76+
* Create a new PathResource from a Path handle.
77+
* <p>Note: Unlike {@link FileSystemResource}, when building relative resources
78+
* via {@link #createRelative}, the relative path will be built <i>underneath</i> the
79+
* given root:
80+
* e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
81+
* @see java.nio.file.Paths#get(URI)
82+
* @param uri a path URI
83+
*/
84+
public PathResource(URI uri) {
85+
Assert.notNull(uri, "URI must not be null");
86+
this.path = Paths.get(uri).normalize();
87+
}
88+
89+
90+
/**
91+
* Return the file path for this resource.
92+
*/
93+
public final String getPath() {
94+
return this.path.toString();
95+
}
96+
97+
/**
98+
* This implementation returns whether the underlying file exists.
99+
* @see org.springframework.core.io.PathResource#exists()
100+
*/
101+
@Override
102+
public boolean exists() {
103+
return Files.exists(this.path);
104+
}
105+
106+
/**
107+
* This implementation checks whether the underlying file is marked as readable
108+
* (and corresponds to an actual file with content, not to a directory).
109+
* @see java.nio.file.Files#isReadable(Path)
110+
* @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...)
111+
*/
112+
@Override
113+
public boolean isReadable() {
114+
return (Files.isReadable(this.path) && !Files.isDirectory(this.path));
115+
}
116+
117+
/**
118+
* This implementation opens a InputStream for the underlying file.
119+
* @see java.nio.file.spi.FileSystemProvider#newInputStream(Path, OpenOption...)
120+
*/
121+
@Override
122+
public InputStream getInputStream() throws IOException {
123+
if(!exists()) {
124+
throw new FileNotFoundException(getPath() + " (No such file or directory)");
125+
}
126+
if(Files.isDirectory(this.path)) {
127+
throw new FileNotFoundException(getPath() + " (Is a directory)");
128+
}
129+
return Files.newInputStream(this.path);
130+
}
131+
132+
/**
133+
* This implementation returns a URL for the underlying file.
134+
* @see java.nio.file.Path#toUri()
135+
* @see java.net.URI#toURL()
136+
*/
137+
@Override
138+
public URL getURL() throws IOException {
139+
return this.path.toUri().toURL();
140+
}
141+
142+
/**
143+
* This implementation returns a URI for the underlying file.
144+
* @see java.nio.file.Path#toUri()
145+
*/
146+
@Override
147+
public URI getURI() throws IOException {
148+
return this.path.toUri();
149+
}
150+
151+
/**
152+
* This implementation returns the underlying File reference.
153+
*/
154+
@Override
155+
public File getFile() throws IOException {
156+
try {
157+
return this.path.toFile();
158+
}
159+
catch (UnsupportedOperationException ex) {
160+
// only Paths on the default file system can be converted to a File
161+
// do exception translation for cases where conversion is not possible
162+
throw new FileNotFoundException(this.path + " cannot be resolved to "
163+
+ "absolute file path");
164+
}
165+
}
166+
167+
/**
168+
* This implementation returns the underlying File's length.
169+
*/
170+
@Override
171+
public long contentLength() throws IOException {
172+
return Files.size(this.path);
173+
}
174+
175+
/**
176+
* This implementation returns the underlying File's timestamp.
177+
* @see java.nio.file.Files#getLastModifiedTime(Path, java.nio.file.LinkOption...)
178+
*/
179+
@Override
180+
public long lastModified() throws IOException {
181+
// we can not use the super class method since it uses conversion to a File and
182+
// only Paths on the default file system can be converted to a File
183+
return Files.getLastModifiedTime(path).toMillis();
184+
}
185+
186+
/**
187+
* This implementation creates a FileResource, applying the given path
188+
* relative to the path of the underlying file of this resource descriptor.
189+
* @see java.nio.file.Path#resolve(String)
190+
*/
191+
@Override
192+
public Resource createRelative(String relativePath) throws IOException {
193+
return new PathResource(this.path.resolve(relativePath));
194+
}
195+
196+
/**
197+
* This implementation returns the name of the file.
198+
* @see java.nio.file.Path#getFileName()
199+
*/
200+
@Override
201+
public String getFilename() {
202+
return this.path.getFileName().toString();
203+
}
204+
205+
@Override
206+
public String getDescription() {
207+
return "path [" + this.path.toAbsolutePath() + "]";
208+
}
209+
210+
// implementation of WritableResource
211+
212+
/**
213+
* This implementation checks whether the underlying file is marked as writable
214+
* (and corresponds to an actual file with content, not to a directory).
215+
* @see java.nio.file.Files#isWritable(Path)
216+
* @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...)
217+
*/
218+
@Override
219+
public boolean isWritable() {
220+
return Files.isWritable(this.path) && !Files.isDirectory(this.path);
221+
}
222+
223+
/**
224+
* This implementation opens a OutputStream for the underlying file.
225+
* @see java.nio.file.spi.FileSystemProvider#newOutputStream(Path, OpenOption...)
226+
*/
227+
@Override
228+
public OutputStream getOutputStream() throws IOException {
229+
if(Files.isDirectory(this.path)) {
230+
throw new FileNotFoundException(getPath() + " (Is a directory)");
231+
}
232+
return Files.newOutputStream(this.path);
233+
}
234+
235+
236+
/**
237+
* This implementation compares the underlying Path references.
238+
*/
239+
@Override
240+
public boolean equals(Object obj) {
241+
return (obj == this ||
242+
(obj instanceof PathResource && this.path.equals(((PathResource) obj).path)));
243+
}
244+
245+
/**
246+
* This implementation returns the hash code of the underlying Path reference.
247+
*/
248+
@Override
249+
public int hashCode() {
250+
return this.path.hashCode();
251+
}
252+
253+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* @see UrlResource
4343
* @see ByteArrayResource
4444
* @see InputStreamResource
45+
* @see PathResource
4546
*/
4647
public interface Resource extends InputStreamSource {
4748

0 commit comments

Comments
 (0)