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}