1 /* 2 * Copyright 2013 Google Inc. 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 com.google.common.jimfs; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static com.google.common.jimfs.Feature.FILE_CHANNEL; 22 import static com.google.common.jimfs.Jimfs.URI_SCHEME; 23 import static java.nio.file.StandardOpenOption.APPEND; 24 25 import com.google.common.collect.ImmutableSet; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 import java.net.URI; 30 import java.nio.channels.AsynchronousFileChannel; 31 import java.nio.channels.FileChannel; 32 import java.nio.channels.SeekableByteChannel; 33 import java.nio.file.AccessMode; 34 import java.nio.file.CopyOption; 35 import java.nio.file.DirectoryStream; 36 import java.nio.file.FileStore; 37 import java.nio.file.FileSystem; 38 import java.nio.file.FileSystems; 39 import java.nio.file.LinkOption; 40 import java.nio.file.OpenOption; 41 import java.nio.file.Path; 42 import java.nio.file.ProviderMismatchException; 43 import java.nio.file.attribute.BasicFileAttributes; 44 import java.nio.file.attribute.DosFileAttributes; 45 import java.nio.file.attribute.FileAttribute; 46 import java.nio.file.attribute.FileAttributeView; 47 import java.nio.file.spi.FileSystemProvider; 48 import java.util.Map; 49 import java.util.Set; 50 import java.util.concurrent.ExecutorService; 51 import org.checkerframework.checker.nullness.compatqual.NullableDecl; 52 53 /** 54 * {@link FileSystemProvider} implementation for Jimfs. This provider implements the actual file 55 * system operations but does not handle creation, caching or lookup of file systems. See {@link 56 * SystemJimfsFileSystemProvider}, which is the {@code META-INF/services/} entry for Jimfs, for 57 * those operations. 58 * 59 * @author Colin Decker 60 */ 61 final class JimfsFileSystemProvider extends FileSystemProvider { 62 63 private static final JimfsFileSystemProvider INSTANCE = new JimfsFileSystemProvider(); 64 65 static { 66 // Register the URL stream handler implementation. 67 try { Handler.register()68 Handler.register(); 69 } catch (Throwable e) { 70 // Couldn't set the system property needed to register the handler. Nothing we can do really. 71 } 72 } 73 74 /** Returns the singleton instance of this provider. */ instance()75 static JimfsFileSystemProvider instance() { 76 return INSTANCE; 77 } 78 79 @Override getScheme()80 public String getScheme() { 81 return URI_SCHEME; 82 } 83 84 @Override newFileSystem(URI uri, Map<String, ?> env)85 public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException { 86 throw new UnsupportedOperationException( 87 "This method should not be called directly;" 88 + "use an overload of Jimfs.newFileSystem() to create a FileSystem."); 89 } 90 91 @Override newFileSystem(Path path, Map<String, ?> env)92 public FileSystem newFileSystem(Path path, Map<String, ?> env) throws IOException { 93 JimfsPath checkedPath = checkPath(path); 94 checkNotNull(env); 95 96 URI pathUri = checkedPath.toUri(); 97 URI jarUri = URI.create("jar:" + pathUri); 98 99 try { 100 // pass the new jar:jimfs://... URI to be handled by ZipFileSystemProvider 101 return FileSystems.newFileSystem(jarUri, env); 102 } catch (Exception e) { 103 // if any exception occurred, assume the file wasn't a zip file and that we don't support 104 // viewing it as a file system 105 throw new UnsupportedOperationException(e); 106 } 107 } 108 109 @Override getFileSystem(URI uri)110 public FileSystem getFileSystem(URI uri) { 111 throw new UnsupportedOperationException( 112 "This method should not be called directly; " 113 + "use FileSystems.getFileSystem(URI) instead."); 114 } 115 116 /** Gets the file system for the given path. */ getFileSystem(Path path)117 private static JimfsFileSystem getFileSystem(Path path) { 118 return (JimfsFileSystem) checkPath(path).getFileSystem(); 119 } 120 121 @Override getPath(URI uri)122 public Path getPath(URI uri) { 123 throw new UnsupportedOperationException( 124 "This method should not be called directly; " + "use Paths.get(URI) instead."); 125 } 126 checkPath(Path path)127 private static JimfsPath checkPath(Path path) { 128 if (path instanceof JimfsPath) { 129 return (JimfsPath) path; 130 } 131 throw new ProviderMismatchException( 132 "path " + path + " is not associated with a Jimfs file system"); 133 } 134 135 /** Returns the default file system view for the given path. */ getDefaultView(JimfsPath path)136 private static FileSystemView getDefaultView(JimfsPath path) { 137 return getFileSystem(path).getDefaultView(); 138 } 139 140 @Override newFileChannel( Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)141 public FileChannel newFileChannel( 142 Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException { 143 JimfsPath checkedPath = checkPath(path); 144 if (!checkedPath.getJimfsFileSystem().getFileStore().supportsFeature(FILE_CHANNEL)) { 145 throw new UnsupportedOperationException(); 146 } 147 return newJimfsFileChannel(checkedPath, options, attrs); 148 } 149 newJimfsFileChannel( JimfsPath path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)150 private JimfsFileChannel newJimfsFileChannel( 151 JimfsPath path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) 152 throws IOException { 153 ImmutableSet<OpenOption> opts = Options.getOptionsForChannel(options); 154 FileSystemView view = getDefaultView(path); 155 RegularFile file = view.getOrCreateRegularFile(path, opts, attrs); 156 return new JimfsFileChannel(file, opts, view.state()); 157 } 158 159 @Override newByteChannel( Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)160 public SeekableByteChannel newByteChannel( 161 Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException { 162 JimfsPath checkedPath = checkPath(path); 163 JimfsFileChannel channel = newJimfsFileChannel(checkedPath, options, attrs); 164 return checkedPath.getJimfsFileSystem().getFileStore().supportsFeature(FILE_CHANNEL) 165 ? channel 166 : new DowngradedSeekableByteChannel(channel); 167 } 168 169 @Override newAsynchronousFileChannel( Path path, Set<? extends OpenOption> options, @NullableDecl ExecutorService executor, FileAttribute<?>... attrs)170 public AsynchronousFileChannel newAsynchronousFileChannel( 171 Path path, 172 Set<? extends OpenOption> options, 173 @NullableDecl ExecutorService executor, 174 FileAttribute<?>... attrs) 175 throws IOException { 176 // call newFileChannel and cast so that FileChannel support is checked there 177 JimfsFileChannel channel = (JimfsFileChannel) newFileChannel(path, options, attrs); 178 if (executor == null) { 179 JimfsFileSystem fileSystem = (JimfsFileSystem) path.getFileSystem(); 180 executor = fileSystem.getDefaultThreadPool(); 181 } 182 return channel.asAsynchronousFileChannel(executor); 183 } 184 185 @Override newInputStream(Path path, OpenOption... options)186 public InputStream newInputStream(Path path, OpenOption... options) throws IOException { 187 JimfsPath checkedPath = checkPath(path); 188 ImmutableSet<OpenOption> opts = Options.getOptionsForInputStream(options); 189 FileSystemView view = getDefaultView(checkedPath); 190 RegularFile file = view.getOrCreateRegularFile(checkedPath, opts, NO_ATTRS); 191 return new JimfsInputStream(file, view.state()); 192 } 193 194 private static final FileAttribute<?>[] NO_ATTRS = {}; 195 196 @Override newOutputStream(Path path, OpenOption... options)197 public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException { 198 JimfsPath checkedPath = checkPath(path); 199 ImmutableSet<OpenOption> opts = Options.getOptionsForOutputStream(options); 200 FileSystemView view = getDefaultView(checkedPath); 201 RegularFile file = view.getOrCreateRegularFile(checkedPath, opts, NO_ATTRS); 202 return new JimfsOutputStream(file, opts.contains(APPEND), view.state()); 203 } 204 205 @Override newDirectoryStream( Path dir, DirectoryStream.Filter<? super Path> filter)206 public DirectoryStream<Path> newDirectoryStream( 207 Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException { 208 JimfsPath checkedPath = checkPath(dir); 209 return getDefaultView(checkedPath) 210 .newDirectoryStream(checkedPath, filter, Options.FOLLOW_LINKS, checkedPath); 211 } 212 213 @Override createDirectory(Path dir, FileAttribute<?>... attrs)214 public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException { 215 JimfsPath checkedPath = checkPath(dir); 216 FileSystemView view = getDefaultView(checkedPath); 217 view.createDirectory(checkedPath, attrs); 218 } 219 220 @Override createLink(Path link, Path existing)221 public void createLink(Path link, Path existing) throws IOException { 222 JimfsPath linkPath = checkPath(link); 223 JimfsPath existingPath = checkPath(existing); 224 checkArgument( 225 linkPath.getFileSystem().equals(existingPath.getFileSystem()), 226 "link and existing paths must belong to the same file system instance"); 227 FileSystemView view = getDefaultView(linkPath); 228 view.link(linkPath, getDefaultView(existingPath), existingPath); 229 } 230 231 @Override createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs)232 public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) 233 throws IOException { 234 JimfsPath linkPath = checkPath(link); 235 JimfsPath targetPath = checkPath(target); 236 checkArgument( 237 linkPath.getFileSystem().equals(targetPath.getFileSystem()), 238 "link and target paths must belong to the same file system instance"); 239 FileSystemView view = getDefaultView(linkPath); 240 view.createSymbolicLink(linkPath, targetPath, attrs); 241 } 242 243 @Override readSymbolicLink(Path link)244 public Path readSymbolicLink(Path link) throws IOException { 245 JimfsPath checkedPath = checkPath(link); 246 return getDefaultView(checkedPath).readSymbolicLink(checkedPath); 247 } 248 249 @Override delete(Path path)250 public void delete(Path path) throws IOException { 251 JimfsPath checkedPath = checkPath(path); 252 FileSystemView view = getDefaultView(checkedPath); 253 view.deleteFile(checkedPath, FileSystemView.DeleteMode.ANY); 254 } 255 256 @Override copy(Path source, Path target, CopyOption... options)257 public void copy(Path source, Path target, CopyOption... options) throws IOException { 258 copy(source, target, Options.getCopyOptions(options), false); 259 } 260 copy(Path source, Path target, ImmutableSet<CopyOption> options, boolean move)261 private void copy(Path source, Path target, ImmutableSet<CopyOption> options, boolean move) 262 throws IOException { 263 JimfsPath sourcePath = checkPath(source); 264 JimfsPath targetPath = checkPath(target); 265 266 FileSystemView sourceView = getDefaultView(sourcePath); 267 FileSystemView targetView = getDefaultView(targetPath); 268 sourceView.copy(sourcePath, targetView, targetPath, options, move); 269 } 270 271 @Override move(Path source, Path target, CopyOption... options)272 public void move(Path source, Path target, CopyOption... options) throws IOException { 273 copy(source, target, Options.getMoveOptions(options), true); 274 } 275 276 @Override isSameFile(Path path, Path path2)277 public boolean isSameFile(Path path, Path path2) throws IOException { 278 if (path.equals(path2)) { 279 return true; 280 } 281 282 if (!(path instanceof JimfsPath && path2 instanceof JimfsPath)) { 283 return false; 284 } 285 286 JimfsPath checkedPath = (JimfsPath) path; 287 JimfsPath checkedPath2 = (JimfsPath) path2; 288 289 FileSystemView view = getDefaultView(checkedPath); 290 FileSystemView view2 = getDefaultView(checkedPath2); 291 292 return view.isSameFile(checkedPath, view2, checkedPath2); 293 } 294 295 @Override isHidden(Path path)296 public boolean isHidden(Path path) throws IOException { 297 // TODO(cgdecker): This should probably be configurable, but this seems fine for now 298 /* 299 * If the DOS view is supported, use the Windows isHidden method (check the dos:hidden 300 * attribute). Otherwise, use the Unix isHidden method (just check if the file name starts with 301 * "."). 302 */ 303 JimfsPath checkedPath = checkPath(path); 304 FileSystemView view = getDefaultView(checkedPath); 305 if (getFileStore(path).supportsFileAttributeView("dos")) { 306 return view.readAttributes(checkedPath, DosFileAttributes.class, Options.NOFOLLOW_LINKS) 307 .isHidden(); 308 } 309 return path.getNameCount() > 0 && path.getFileName().toString().startsWith("."); 310 } 311 312 @Override getFileStore(Path path)313 public FileStore getFileStore(Path path) throws IOException { 314 return getFileSystem(path).getFileStore(); 315 } 316 317 @Override checkAccess(Path path, AccessMode... modes)318 public void checkAccess(Path path, AccessMode... modes) throws IOException { 319 JimfsPath checkedPath = checkPath(path); 320 getDefaultView(checkedPath).checkAccess(checkedPath); 321 } 322 323 @NullableDecl 324 @Override getFileAttributeView( Path path, Class<V> type, LinkOption... options)325 public <V extends FileAttributeView> V getFileAttributeView( 326 Path path, Class<V> type, LinkOption... options) { 327 JimfsPath checkedPath = checkPath(path); 328 return getDefaultView(checkedPath) 329 .getFileAttributeView(checkedPath, type, Options.getLinkOptions(options)); 330 } 331 332 @Override readAttributes( Path path, Class<A> type, LinkOption... options)333 public <A extends BasicFileAttributes> A readAttributes( 334 Path path, Class<A> type, LinkOption... options) throws IOException { 335 JimfsPath checkedPath = checkPath(path); 336 return getDefaultView(checkedPath) 337 .readAttributes(checkedPath, type, Options.getLinkOptions(options)); 338 } 339 340 @Override readAttributes(Path path, String attributes, LinkOption... options)341 public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) 342 throws IOException { 343 JimfsPath checkedPath = checkPath(path); 344 return getDefaultView(checkedPath) 345 .readAttributes(checkedPath, attributes, Options.getLinkOptions(options)); 346 } 347 348 @Override setAttribute(Path path, String attribute, Object value, LinkOption... options)349 public void setAttribute(Path path, String attribute, Object value, LinkOption... options) 350 throws IOException { 351 JimfsPath checkedPath = checkPath(path); 352 getDefaultView(checkedPath) 353 .setAttribute(checkedPath, attribute, value, Options.getLinkOptions(options)); 354 } 355 } 356