001/* 002 * Copyright 2002-2019 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.channels.ReadableByteChannel; 027import java.nio.channels.WritableByteChannel; 028import java.nio.file.Files; 029import java.nio.file.NoSuchFileException; 030import java.nio.file.OpenOption; 031import java.nio.file.Path; 032import java.nio.file.Paths; 033import java.nio.file.StandardOpenOption; 034 035import org.springframework.lang.Nullable; 036import org.springframework.util.Assert; 037 038/** 039 * {@link Resource} implementation for {@link java.nio.file.Path} handles, 040 * performing all operations and transformations via the {@code Path} API. 041 * Supports resolution as a {@link File} and also as a {@link URL}. 042 * Implements the extended {@link WritableResource} interface. 043 * 044 * <p>Note: As of 5.1, {@link java.nio.file.Path} support is also available 045 * in {@link FileSystemResource#FileSystemResource(Path) FileSystemResource}, 046 * applying Spring's standard String-based path transformations but 047 * performing all operations via the {@link java.nio.file.Files} API. 048 * This {@code PathResource} is effectively a pure {@code java.nio.path.Path} 049 * based alternative with different {@code createRelative} behavior. 050 * 051 * @author Philippe Marschall 052 * @author Juergen Hoeller 053 * @since 4.0 054 * @see java.nio.file.Path 055 * @see java.nio.file.Files 056 * @see FileSystemResource 057 */ 058public class PathResource extends AbstractResource implements WritableResource { 059 060 private final Path path; 061 062 063 /** 064 * Create a new PathResource from a Path handle. 065 * <p>Note: Unlike {@link FileSystemResource}, when building relative resources 066 * via {@link #createRelative}, the relative path will be built <i>underneath</i> 067 * the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"! 068 * @param path a Path handle 069 */ 070 public PathResource(Path path) { 071 Assert.notNull(path, "Path must not be null"); 072 this.path = path.normalize(); 073 } 074 075 /** 076 * Create a new PathResource from a Path handle. 077 * <p>Note: Unlike {@link FileSystemResource}, when building relative resources 078 * via {@link #createRelative}, the relative path will be built <i>underneath</i> 079 * the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"! 080 * @param path a path 081 * @see java.nio.file.Paths#get(String, String...) 082 */ 083 public PathResource(String path) { 084 Assert.notNull(path, "Path must not be null"); 085 this.path = Paths.get(path).normalize(); 086 } 087 088 /** 089 * Create a new PathResource from a Path handle. 090 * <p>Note: Unlike {@link FileSystemResource}, when building relative resources 091 * via {@link #createRelative}, the relative path will be built <i>underneath</i> 092 * the given root: e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"! 093 * @param uri a path URI 094 * @see java.nio.file.Paths#get(URI) 095 */ 096 public PathResource(URI uri) { 097 Assert.notNull(uri, "URI must not be null"); 098 this.path = Paths.get(uri).normalize(); 099 } 100 101 102 /** 103 * Return the file path for this resource. 104 */ 105 public final String getPath() { 106 return this.path.toString(); 107 } 108 109 /** 110 * This implementation returns whether the underlying file exists. 111 * @see java.nio.file.Files#exists(Path, java.nio.file.LinkOption...) 112 */ 113 @Override 114 public boolean exists() { 115 return Files.exists(this.path); 116 } 117 118 /** 119 * This implementation checks whether the underlying file is marked as readable 120 * (and corresponds to an actual file with content, not to a directory). 121 * @see java.nio.file.Files#isReadable(Path) 122 * @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...) 123 */ 124 @Override 125 public boolean isReadable() { 126 return (Files.isReadable(this.path) && !Files.isDirectory(this.path)); 127 } 128 129 /** 130 * This implementation opens a InputStream for the underlying file. 131 * @see java.nio.file.spi.FileSystemProvider#newInputStream(Path, OpenOption...) 132 */ 133 @Override 134 public InputStream getInputStream() throws IOException { 135 if (!exists()) { 136 throw new FileNotFoundException(getPath() + " (no such file or directory)"); 137 } 138 if (Files.isDirectory(this.path)) { 139 throw new FileNotFoundException(getPath() + " (is a directory)"); 140 } 141 return Files.newInputStream(this.path); 142 } 143 144 /** 145 * This implementation checks whether the underlying file is marked as writable 146 * (and corresponds to an actual file with content, not to a directory). 147 * @see java.nio.file.Files#isWritable(Path) 148 * @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...) 149 */ 150 @Override 151 public boolean isWritable() { 152 return (Files.isWritable(this.path) && !Files.isDirectory(this.path)); 153 } 154 155 /** 156 * This implementation opens a OutputStream for the underlying file. 157 * @see java.nio.file.spi.FileSystemProvider#newOutputStream(Path, OpenOption...) 158 */ 159 @Override 160 public OutputStream getOutputStream() throws IOException { 161 if (Files.isDirectory(this.path)) { 162 throw new FileNotFoundException(getPath() + " (is a directory)"); 163 } 164 return Files.newOutputStream(this.path); 165 } 166 167 /** 168 * This implementation returns a URL for the underlying file. 169 * @see java.nio.file.Path#toUri() 170 * @see java.net.URI#toURL() 171 */ 172 @Override 173 public URL getURL() throws IOException { 174 return this.path.toUri().toURL(); 175 } 176 177 /** 178 * This implementation returns a URI for the underlying file. 179 * @see java.nio.file.Path#toUri() 180 */ 181 @Override 182 public URI getURI() throws IOException { 183 return this.path.toUri(); 184 } 185 186 /** 187 * This implementation always indicates a file. 188 */ 189 @Override 190 public boolean isFile() { 191 return true; 192 } 193 194 /** 195 * This implementation returns the underlying File reference. 196 */ 197 @Override 198 public File getFile() throws IOException { 199 try { 200 return this.path.toFile(); 201 } 202 catch (UnsupportedOperationException ex) { 203 // Only paths on the default file system can be converted to a File: 204 // Do exception translation for cases where conversion is not possible. 205 throw new FileNotFoundException(this.path + " cannot be resolved to absolute file path"); 206 } 207 } 208 209 /** 210 * This implementation opens a Channel for the underlying file. 211 * @see Files#newByteChannel(Path, OpenOption...) 212 */ 213 @Override 214 public ReadableByteChannel readableChannel() throws IOException { 215 try { 216 return Files.newByteChannel(this.path, StandardOpenOption.READ); 217 } 218 catch (NoSuchFileException ex) { 219 throw new FileNotFoundException(ex.getMessage()); 220 } 221 } 222 223 /** 224 * This implementation opens a Channel for the underlying file. 225 * @see Files#newByteChannel(Path, OpenOption...) 226 */ 227 @Override 228 public WritableByteChannel writableChannel() throws IOException { 229 return Files.newByteChannel(this.path, StandardOpenOption.WRITE); 230 } 231 232 /** 233 * This implementation returns the underlying file's length. 234 */ 235 @Override 236 public long contentLength() throws IOException { 237 return Files.size(this.path); 238 } 239 240 /** 241 * This implementation returns the underlying File's timestamp. 242 * @see java.nio.file.Files#getLastModifiedTime(Path, java.nio.file.LinkOption...) 243 */ 244 @Override 245 public long lastModified() throws IOException { 246 // We can not use the superclass method since it uses conversion to a File and 247 // only a Path on the default file system can be converted to a File... 248 return Files.getLastModifiedTime(this.path).toMillis(); 249 } 250 251 /** 252 * This implementation creates a PathResource, applying the given path 253 * relative to the path of the underlying file of this resource descriptor. 254 * @see java.nio.file.Path#resolve(String) 255 */ 256 @Override 257 public Resource createRelative(String relativePath) { 258 return new PathResource(this.path.resolve(relativePath)); 259 } 260 261 /** 262 * This implementation returns the name of the file. 263 * @see java.nio.file.Path#getFileName() 264 */ 265 @Override 266 public String getFilename() { 267 return this.path.getFileName().toString(); 268 } 269 270 @Override 271 public String getDescription() { 272 return "path [" + this.path.toAbsolutePath() + "]"; 273 } 274 275 276 /** 277 * This implementation compares the underlying Path references. 278 */ 279 @Override 280 public boolean equals(@Nullable Object other) { 281 return (this == other || (other instanceof PathResource && 282 this.path.equals(((PathResource) other).path))); 283 } 284 285 /** 286 * This implementation returns the hash code of the underlying Path reference. 287 */ 288 @Override 289 public int hashCode() { 290 return this.path.hashCode(); 291 } 292 293}