Package com.google.common.jimfs

Source Code of com.google.common.jimfs.JimfsFileSystemProvider

/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.common.jimfs;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.jimfs.Feature.FILE_CHANNEL;
import static com.google.common.jimfs.Jimfs.CONFIG_KEY;
import static com.google.common.jimfs.Jimfs.URI_SCHEME;
import static java.nio.file.StandardOpenOption.APPEND;

import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapMaker;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessMode;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.ProviderMismatchException;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;

import javax.annotation.Nullable;

/**
* {@link FileSystemProvider} implementation for Jimfs. While this class is public, it should not
* be used directly. To create a new file system instance, see {@link Jimfs}. For other operations,
* use the public APIs in {@code java.nio.file}.
*
* @author Colin Decker
*/
@AutoService(FileSystemProvider.class)
public final class JimfsFileSystemProvider extends FileSystemProvider {

  @Override
  public String getScheme() {
    return URI_SCHEME;
  }

  /**
   * Cache of file systems that have been created but not closed.
   *
   * <p>This cache is static to ensure that even when this provider isn't loaded by the system
   * class loader, meaning that a new instance of it must be created each time one of the methods
   * on {@link FileSystems} or {@link Paths#get(URI)} is called, cached file system instances are
   * still available.
   *
   * <p>The cache uses weak values so that it doesn't prevent file systems that are created but not
   * closed from being garbage collected if no references to them are held elsewhere. This is a
   * compromise between ensuring that any file URI continues to work as long as the file system
   * hasn't been closed (which is technically the correct thing to do but unlikely to be something
   * that most users care about) and ensuring that users don't get unexpected leaks of large
   * amounts of memory because they're creating many file systems in tests but forgetting to close
   * them (which seems likely to happen sometimes). Users that want to ensure that a file system
   * won't be garbage collected just need to ensure they hold a reference to it somewhere for as
   * long as they need it to stick around.
   */
  private static final ConcurrentMap<URI, JimfsFileSystem> fileSystems = new MapMaker()
      .weakValues()
      .makeMap();

  @Override
  public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
    checkArgument(uri.getScheme().equalsIgnoreCase(URI_SCHEME),
        "uri (%s) scheme must be '%s'", uri, URI_SCHEME);
    checkArgument(isValidFileSystemUri(uri),
        "uri (%s) may not have a path, query or fragment", uri);
    checkArgument(env.get(CONFIG_KEY) instanceof Configuration,
        "env map (%s) must contain key '%s' mapped to an instance of Jimfs.Configuration",
        env, CONFIG_KEY);

    Configuration config = (Configuration) env.get(CONFIG_KEY);
    JimfsFileSystem fileSystem = JimfsFileSystems.newFileSystem(this, uri, config);
    if (fileSystems.putIfAbsent(uri, fileSystem) != null) {
      throw new FileSystemAlreadyExistsException(uri.toString());
    }
    return fileSystem;
  }

  @Override
  public FileSystem getFileSystem(URI uri) {
    return getJimfsFileSystem(uri);
  }

  private JimfsFileSystem getJimfsFileSystem(URI uri) {
    JimfsFileSystem fileSystem = fileSystems.get(uri);
    if (fileSystem == null) {
      throw new FileSystemNotFoundException(uri.toString());
    }
    return fileSystem;
  }

  @Override
  public FileSystem newFileSystem(Path path, Map<String, ?> env) throws IOException {
    JimfsPath checkedPath = checkPath(path);
    checkNotNull(env);

    URI pathUri = checkedPath.toUri();
    URI jarUri = URI.create("jar:" + pathUri);

    try {
      // pass the new jar:jimfs://... URI to be handled by ZipFileSystemProvider
      return FileSystems.newFileSystem(jarUri, env);
    } catch (Exception e) {
      // if any exception occurred, assume the file wasn't a zip file and that we don't support
      // viewing it as a file system
      throw new UnsupportedOperationException(e);
    }
  }

  /**
   * Returns a runnable that, when run, removes the file system with the given URI from this
   * provider.
   */
  static Runnable removeFileSystemRunnable(final URI uri) {
    return new Runnable() {
      @Override
      public void run() {
        fileSystems.remove(uri);
      }
    };
  }

  @Override
  public Path getPath(URI uri) {
    checkArgument(URI_SCHEME.equalsIgnoreCase(uri.getScheme()),
        "uri scheme does not match this provider: %s", uri);
    checkArgument(!isNullOrEmpty(uri.getPath()), "uri must have a path: %s", uri);

    return getJimfsFileSystem(toFileSystemUri(uri)).toPath(uri);
  }

  /**
   * Returns whether or not the given URI is valid as a base file system URI. It must not have a
   * path, query or fragment.
   */
  private static boolean isValidFileSystemUri(URI uri) {
    // would like to just check null, but fragment appears to be the empty string when not present
    return isNullOrEmpty(uri.getPath())
        && isNullOrEmpty(uri.getQuery())
        && isNullOrEmpty(uri.getFragment());
  }

  /**
   * Returns the given URI with any path, query or fragment stripped off.
   */
  private static URI toFileSystemUri(URI uri) {
    try {
      return new URI(
          uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
          null, null, null);
    } catch (URISyntaxException e) {
      throw new AssertionError(e);
    }
  }

  private static JimfsPath checkPath(Path path) {
    if (path instanceof JimfsPath) {
      return (JimfsPath) path;
    }
    throw new ProviderMismatchException(
        "path " + path + " is not associated with a Jimfs file system");
  }

  /**
   * Gets the file system for the given path.
   */
  private static JimfsFileSystem getFileSystem(Path path) {
    return (JimfsFileSystem) checkPath(path).getFileSystem();
  }

  /**
   * Returns the default file system view for the given path.
   */
  private static FileSystemView getDefaultView(JimfsPath path) {
    return getFileSystem(path).getDefaultView();
  }

  @Override
  public FileChannel newFileChannel(
      Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
    JimfsPath checkedPath = checkPath(path);
    if (!checkedPath.getJimfsFileSystem().getFileStore().supportsFeature(FILE_CHANNEL)) {
      throw new UnsupportedOperationException();
    }
    return newJimfsFileChannel(checkedPath, options, attrs);
  }

  private JimfsFileChannel newJimfsFileChannel(
      JimfsPath path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
    ImmutableSet<OpenOption> opts = Options.getOptionsForChannel(options);
    FileSystemView view = getDefaultView(path);
    RegularFile file = view.getOrCreateRegularFile(path, opts, attrs);
    return new JimfsFileChannel(file, opts, view.state());
  }

  @Override
  public SeekableByteChannel newByteChannel(
      Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
    JimfsPath checkedPath = checkPath(path);
    JimfsFileChannel channel = newJimfsFileChannel(checkedPath, options, attrs);
    return checkedPath.getJimfsFileSystem().getFileStore().supportsFeature(FILE_CHANNEL)
        ? channel
        : new DowngradedSeekableByteChannel(channel);
  }

  @Override
  public AsynchronousFileChannel newAsynchronousFileChannel(
      Path path, Set<? extends OpenOption> options, @Nullable ExecutorService executor,
      FileAttribute<?>... attrs) throws IOException {
    // call newFileChannel and cast so that FileChannel support is checked there
    JimfsFileChannel channel = (JimfsFileChannel) newFileChannel(path, options, attrs);
    if (executor == null) {
      JimfsFileSystem fileSystem = (JimfsFileSystem) path.getFileSystem();
      executor = fileSystem.getDefaultThreadPool();
    }
    return channel.asAsynchronousFileChannel(executor);
  }

  @Override
  public InputStream newInputStream(Path path, OpenOption... options) throws IOException {
    JimfsPath checkedPath = checkPath(path);
    ImmutableSet<OpenOption> opts = Options.getOptionsForInputStream(options);
    FileSystemView view = getDefaultView(checkedPath);
    RegularFile file = view.getOrCreateRegularFile(checkedPath, opts, NO_ATTRS);
    return new JimfsInputStream(file, view.state());
  }

  private static final FileAttribute<?>[] NO_ATTRS = {};

  @Override
  public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException {
    JimfsPath checkedPath = checkPath(path);
    ImmutableSet<OpenOption> opts = Options.getOptionsForOutputStream(options);
    FileSystemView view = getDefaultView(checkedPath);
    RegularFile file = view.getOrCreateRegularFile(checkedPath, opts, NO_ATTRS);
    return new JimfsOutputStream(file, opts.contains(APPEND), view.state());
  }

  @Override
  public DirectoryStream<Path> newDirectoryStream(Path dir,
      DirectoryStream.Filter<? super Path> filter) throws IOException {
    JimfsPath checkedPath = checkPath(dir);
    return getDefaultView(checkedPath)
        .newDirectoryStream(checkedPath, filter, Options.FOLLOW_LINKS, checkedPath);
  }

  @Override
  public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
    JimfsPath checkedPath = checkPath(dir);
    FileSystemView view = getDefaultView(checkedPath);
    view.createDirectory(checkedPath, attrs);
  }

  @Override
  public void createLink(Path link, Path existing) throws IOException {
    JimfsPath linkPath = checkPath(link);
    JimfsPath existingPath = checkPath(existing);
    checkArgument(linkPath.getFileSystem().equals(existingPath.getFileSystem()),
        "link and existing paths must belong to the same file system instance");
    FileSystemView view = getDefaultView(linkPath);
    view.link(linkPath, getDefaultView(existingPath), existingPath);
  }

  @Override
  public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs)
      throws IOException {
    JimfsPath linkPath = checkPath(link);
    JimfsPath targetPath = checkPath(target);
    checkArgument(linkPath.getFileSystem().equals(targetPath.getFileSystem()),
        "link and target paths must belong to the same file system instance");
    FileSystemView view = getDefaultView(linkPath);
    view.createSymbolicLink(linkPath, targetPath, attrs);
  }

  @Override
  public Path readSymbolicLink(Path link) throws IOException {
    JimfsPath checkedPath = checkPath(link);
    return getDefaultView(checkedPath).readSymbolicLink(checkedPath);
  }

  @Override
  public void delete(Path path) throws IOException {
    JimfsPath checkedPath = checkPath(path);
    FileSystemView view = getDefaultView(checkedPath);
    view.deleteFile(checkedPath, FileSystemView.DeleteMode.ANY);
  }

  @Override
  public void copy(Path source, Path target, CopyOption... options) throws IOException {
    copy(source, target, Options.getCopyOptions(options), false);
  }

  @Override
  public void move(Path source, Path target, CopyOption... options) throws IOException {
    copy(source, target, Options.getMoveOptions(options), true);
  }

  private void copy(Path source, Path target,
      ImmutableSet<CopyOption> options, boolean move) throws IOException {
    JimfsPath sourcePath = checkPath(source);
    JimfsPath targetPath = checkPath(target);

    FileSystemView sourceView = getDefaultView(sourcePath);
    FileSystemView targetView = getDefaultView(targetPath);
    sourceView.copy(sourcePath, targetView, targetPath, options, move);
  }

  @Override
  public boolean isSameFile(Path path, Path path2) throws IOException {
    if (path.equals(path2)) {
      return true;
    }

    if (!(path instanceof JimfsPath && path2 instanceof JimfsPath)) {
      return false;
    }

    JimfsPath checkedPath = (JimfsPath) path;
    JimfsPath checkedPath2 = (JimfsPath) path2;

    FileSystemView view = getDefaultView(checkedPath);
    FileSystemView view2 = getDefaultView(checkedPath2);

    return view.isSameFile(checkedPath, view2, checkedPath2);
  }

  @Override
  public boolean isHidden(Path path) throws IOException {
    // TODO(cgdecker): This should probably be configurable, but this seems fine for now
    /*
     * If the DOS view is supported, use the Windows isHidden method (check the dos:hidden
     * attribute). Otherwise, use the Unix isHidden method (just check if the file name starts with
     * ".").
     */
    JimfsPath checkedPath = checkPath(path);
    FileSystemView view = getDefaultView(checkedPath);
    if (getFileStore(path).supportsFileAttributeView("dos")) {
      return view.readAttributes(checkedPath, DosFileAttributes.class, Options.NOFOLLOW_LINKS)
          .isHidden();
    }
    return path.getNameCount() > 0 && path.getFileName().toString().startsWith(".");
  }

  @Override
  public FileStore getFileStore(Path path) throws IOException {
    return getFileSystem(path).getFileStore();
  }

  @Override
  public void checkAccess(Path path, AccessMode... modes) throws IOException {
    JimfsPath checkedPath = checkPath(path);
    getDefaultView(checkedPath).checkAccess(checkedPath);
  }

  @Nullable
  @Override
  public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type,
      LinkOption... options) {
    JimfsPath checkedPath = checkPath(path);
    return getDefaultView(checkedPath)
        .getFileAttributeView(checkedPath, type, Options.getLinkOptions(options));
  }

  @Override
  public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type,
      LinkOption... options) throws IOException {
    JimfsPath checkedPath = checkPath(path);
    return getDefaultView(checkedPath)
        .readAttributes(checkedPath, type, Options.getLinkOptions(options));
  }

  @Override
  public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options)
      throws IOException {
    JimfsPath checkedPath = checkPath(path);
    return getDefaultView(checkedPath)
        .readAttributes(checkedPath, attributes, Options.getLinkOptions(options));
  }

  @Override
  public void setAttribute(Path path, String attribute, Object value, LinkOption... options)
      throws IOException {
    JimfsPath checkedPath = checkPath(path);
    getDefaultView(checkedPath)
        .setAttribute(checkedPath, attribute, value, Options.getLinkOptions(options));
  }
}
TOP

Related Classes of com.google.common.jimfs.JimfsFileSystemProvider

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.