001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.core.io;
018
019import java.io.File;
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.net.URI;
025import java.net.URL;
026import java.nio.file.Files;
027import java.nio.file.OpenOption;
028import java.nio.file.Path;
029import java.nio.file.Paths;
030
031import org.springframework.lang.UsesJava7;
032import org.springframework.util.Assert;
033
034/**
035 * {@link Resource} implementation for {@code java.nio.file.Path} handles.
036 * Supports resolution as File, and also as URL.
037 * Implements the extended {@link WritableResource} interface.
038 *
039 * @author Philippe Marschall
040 * @author Juergen Hoeller
041 * @since 4.0
042 * @see java.nio.file.Path
043 * @see java.nio.file.Files
044 * @see FileSystemResource
045 */
046@UsesJava7
047public class PathResource extends AbstractResource implements WritableResource {
048
049        private final Path path;
050
051
052        /**
053         * Create a new PathResource from a Path handle.
054         * <p>Note: Unlike {@link FileSystemResource}, when building relative resources
055         * via {@link #createRelative}, the relative path will be built <i>underneath</i>
056         * the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
057         * @param path a Path handle
058         */
059        public PathResource(Path path) {
060                Assert.notNull(path, "Path must not be null");
061                this.path = path.normalize();
062        }
063
064        /**
065         * Create a new PathResource from a Path handle.
066         * <p>Note: Unlike {@link FileSystemResource}, when building relative resources
067         * via {@link #createRelative}, the relative path will be built <i>underneath</i>
068         * the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
069         * @param path a path
070         * @see java.nio.file.Paths#get(String, String...)
071         */
072        public PathResource(String path) {
073                Assert.notNull(path, "Path must not be null");
074                this.path = Paths.get(path).normalize();
075        }
076
077        /**
078         * Create a new PathResource from a Path handle.
079         * <p>Note: Unlike {@link FileSystemResource}, when building relative resources
080         * via {@link #createRelative}, the relative path will be built <i>underneath</i>
081         * the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
082         * @param uri a path URI
083         * @see java.nio.file.Paths#get(URI)
084         */
085        public PathResource(URI uri) {
086                Assert.notNull(uri, "URI must not be null");
087                this.path = Paths.get(uri).normalize();
088        }
089
090
091        /**
092         * Return the file path for this resource.
093         */
094        public final String getPath() {
095                return this.path.toString();
096        }
097
098        /**
099         * This implementation returns whether the underlying file exists.
100         * @see java.nio.file.Files#exists(Path, java.nio.file.LinkOption...)
101         */
102        @Override
103        public boolean exists() {
104                return Files.exists(this.path);
105        }
106
107        /**
108         * This implementation checks whether the underlying file is marked as readable
109         * (and corresponds to an actual file with content, not to a directory).
110         * @see java.nio.file.Files#isReadable(Path)
111         * @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...)
112         */
113        @Override
114        public boolean isReadable() {
115                return (Files.isReadable(this.path) && !Files.isDirectory(this.path));
116        }
117
118        /**
119         * This implementation opens a InputStream for the underlying file.
120         * @see java.nio.file.spi.FileSystemProvider#newInputStream(Path, OpenOption...)
121         */
122        @Override
123        public InputStream getInputStream() throws IOException {
124                if (!exists()) {
125                        throw new FileNotFoundException(getPath() + " (no such file or directory)");
126                }
127                if (Files.isDirectory(this.path)) {
128                        throw new FileNotFoundException(getPath() + " (is a directory)");
129                }
130                return Files.newInputStream(this.path);
131        }
132
133        /**
134         * This implementation checks whether the underlying file is marked as writable
135         * (and corresponds to an actual file with content, not to a directory).
136         * @see java.nio.file.Files#isWritable(Path)
137         * @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...)
138         */
139        @Override
140        public boolean isWritable() {
141                return (Files.isWritable(this.path) && !Files.isDirectory(this.path));
142        }
143
144        /**
145         * This implementation opens a OutputStream for the underlying file.
146         * @see java.nio.file.spi.FileSystemProvider#newOutputStream(Path, OpenOption...)
147         */
148        @Override
149        public OutputStream getOutputStream() throws IOException {
150                if (Files.isDirectory(this.path)) {
151                        throw new FileNotFoundException(getPath() + " (is a directory)");
152                }
153                return Files.newOutputStream(this.path);
154        }
155
156        /**
157         * This implementation returns a URL for the underlying file.
158         * @see java.nio.file.Path#toUri()
159         * @see java.net.URI#toURL()
160         */
161        @Override
162        public URL getURL() throws IOException {
163                return this.path.toUri().toURL();
164        }
165
166        /**
167         * This implementation returns a URI for the underlying file.
168         * @see java.nio.file.Path#toUri()
169         */
170        @Override
171        public URI getURI() throws IOException {
172                return this.path.toUri();
173        }
174
175        /**
176         * This implementation returns the underlying File reference.
177         */
178        @Override
179        public File getFile() throws IOException {
180                try {
181                        return this.path.toFile();
182                }
183                catch (UnsupportedOperationException ex) {
184                        // Only paths on the default file system can be converted to a File:
185                        // Do exception translation for cases where conversion is not possible.
186                        throw new FileNotFoundException(this.path + " cannot be resolved to absolute file path");
187                }
188        }
189
190        /**
191         * This implementation returns the underlying file's length.
192         */
193        @Override
194        public long contentLength() throws IOException {
195                return Files.size(this.path);
196        }
197
198        /**
199         * This implementation returns the underlying File's timestamp.
200         * @see java.nio.file.Files#getLastModifiedTime(Path, java.nio.file.LinkOption...)
201         */
202        @Override
203        public long lastModified() throws IOException {
204                // We can not use the superclass method since it uses conversion to a File and
205                // only a Path on the default file system can be converted to a File...
206                return Files.getLastModifiedTime(this.path).toMillis();
207        }
208
209        /**
210         * This implementation creates a PathResource, applying the given path
211         * relative to the path of the underlying file of this resource descriptor.
212         * @see java.nio.file.Path#resolve(String)
213         */
214        @Override
215        public Resource createRelative(String relativePath) throws IOException {
216                return new PathResource(this.path.resolve(relativePath));
217        }
218
219        /**
220         * This implementation returns the name of the file.
221         * @see java.nio.file.Path#getFileName()
222         */
223        @Override
224        public String getFilename() {
225                return this.path.getFileName().toString();
226        }
227
228        @Override
229        public String getDescription() {
230                return "path [" + this.path.toAbsolutePath() + "]";
231        }
232
233
234        /**
235         * This implementation compares the underlying Path references.
236         */
237        @Override
238        public boolean equals(Object obj) {
239                return (this == obj ||
240                        (obj instanceof PathResource && this.path.equals(((PathResource) obj).path)));
241        }
242
243        /**
244         * This implementation returns the hash code of the underlying Path reference.
245         */
246        @Override
247        public int hashCode() {
248                return this.path.hashCode();
249        }
250
251}