com.google.common.jimfs.Configuration.java Source code

Java tutorial

Introduction

Here is the source code for com.google.common.jimfs.Configuration.java

Source

/*
 * 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.jimfs.Feature.FILE_CHANNEL;
import static com.google.common.jimfs.Feature.LINKS;
import static com.google.common.jimfs.Feature.SECURE_DIRECTORY_STREAM;
import static com.google.common.jimfs.Feature.SYMBOLIC_LINKS;
import static com.google.common.jimfs.PathNormalization.CASE_FOLD_ASCII;
import static com.google.common.jimfs.PathNormalization.NFC;
import static com.google.common.jimfs.PathNormalization.NFD;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import java.nio.channels.FileChannel;
import java.nio.file.FileSystem;
import java.nio.file.InvalidPathException;
import java.nio.file.SecureDirectoryStream;
import java.nio.file.attribute.BasicFileAttributeView;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.annotation.Nullable;

/**
 * Immutable configuration for an in-memory file system. A {@code Configuration} is passed to a
 * method in {@link Jimfs} such as {@link Jimfs#newFileSystem(Configuration)} to create a new
 * {@link FileSystem} instance.
 *
 * @author Colin Decker
 */
public final class Configuration {

    /**
     * <p>Returns the default configuration for a UNIX-like file system. A file system created with
     * this configuration:
     *
     * <ul>
     *   <li>uses {@code /} as the path name separator (see {@link PathType#unix()} for more
     *   information on the path format)</li>
     *   <li>has root {@code /} and working directory {@code /work}</li>
     *   <li>performs case-sensitive file lookup</li>
     *   <li>supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to
     *   avoid overhead for unneeded attributes</li>
     *   <li>supports hard links, symbolic links, {@link SecureDirectoryStream} and
     *   {@link FileChannel}</li>
     * </ul>
     *
     * <p>To create a modified version of this configuration, such as to include the full set of UNIX
     * file attribute views, {@linkplain #toBuilder() create a builder}.
     *
     * <p>Example:
     *
     * <pre>
     *   Configuration config = Configuration.unix().toBuilder()
     *       .setAttributeViews("basic", "owner", "posix", "unix")
     *       .setWorkingDirectory("/home/user")
     *       .build();  </pre>
     */
    public static Configuration unix() {
        return UnixHolder.UNIX;
    }

    private static final class UnixHolder {
        private static final Configuration UNIX = Configuration.builder(PathType.unix()).setRoots("/")
                .setWorkingDirectory("/work").setAttributeViews("basic")
                .setSupportedFeatures(LINKS, SYMBOLIC_LINKS, SECURE_DIRECTORY_STREAM, FILE_CHANNEL).build();
    }

    /**
     * <p>Returns the default configuration for a Mac OS X-like file system.
     *
     * <p>The primary differences between this configuration and the default {@link #unix()}
     * configuration are that this configuration does Unicode normalization on the display and
     * canonical forms of filenames and does case insensitive file lookup.
     *
     * <p>A file system created with this configuration:
     *
     * <ul>
     *   <li>uses {@code /} as the path name separator (see {@link PathType#unix()} for more
     *   information on the path format)</li>
     *   <li>has root {@code /} and working directory {@code /work}</li>
     *   <li>does Unicode normalization on paths, both for lookup and for {@code Path} objects</li>
     *   <li>does case-insensitive (for ASCII characters only) lookup</li>
     *   <li>supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to
     *   avoid overhead for unneeded attributes</li>
     *   <li>supports hard links, symbolic links and {@link FileChannel}</li>
     * </ul>
     *
     * <p>To create a modified version of this configuration, such as to include the full set of UNIX
     * file attribute views or to use full Unicode case insensitivity,
     * {@linkplain #toBuilder() create a builder}.
     *
     * <p>Example:
     *
     * <pre>
     *   Configuration config = Configuration.osX().toBuilder()
     *       .setAttributeViews("basic", "owner", "posix", "unix")
     *       .setNameCanonicalNormalization(NFD, CASE_FOLD_UNICODE)
     *       .setWorkingDirectory("/Users/user")
     *       .build();  </pre>
     */
    public static Configuration osX() {
        return OsxHolder.OS_X;
    }

    private static final class OsxHolder {
        private static final Configuration OS_X = unix().toBuilder().setNameDisplayNormalization(NFC) // matches JDK 1.7u40+ behavior
                .setNameCanonicalNormalization(NFD, CASE_FOLD_ASCII) // NFD is default in HFS+
                .setSupportedFeatures(LINKS, SYMBOLIC_LINKS, FILE_CHANNEL).build();
    }

    /**
     * <p>Returns the default configuration for a Windows-like file system. A file system created
     * with this configuration:
     *
     * <ul>
     *   <li>uses {@code \} as the path name separator and recognizes {@code /} as a separator when
     *   parsing paths (see {@link PathType#windows()} for more information on path format)</li>
     *   <li>has root {@code C:\} and working directory {@code C:\work}</li>
     *   <li>performs case-insensitive (for ASCII characters only) file lookup</li>
     *   <li>creates {@code Path} objects that use case-insensitive (for ASCII characters only)
     *   equality</li>
     *   <li>supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to
     *   avoid overhead for unneeded attributes</li>
     *   <li>supports hard links, symbolic links and {@link FileChannel}</li>
     * </ul>
     *
     * <p>To create a modified version of this configuration, such as to include the full set of
     * Windows file attribute views or to use full Unicode case insensitivity,
     * {@linkplain #toBuilder() create a builder}.
     *
     * <p>Example:
     *
     * <pre>
     *   Configuration config = Configuration.windows().toBuilder()
     *       .setAttributeViews("basic", "owner", "dos", "acl", "user")
     *       .setNameCanonicalNormalization(CASE_FOLD_UNICODE)
     *       .setWorkingDirectory("C:\\Users\\user") // or "C:/Users/user"
     *       .build();  </pre>
     */
    public static Configuration windows() {
        return WindowsHolder.WINDOWS;
    }

    private static final class WindowsHolder {
        private static final Configuration WINDOWS = Configuration.builder(PathType.windows()).setRoots("C:\\")
                .setWorkingDirectory("C:\\work").setNameCanonicalNormalization(CASE_FOLD_ASCII)
                .setPathEqualityUsesCanonicalForm(true) // matches real behavior of WindowsPath
                .setAttributeViews("basic").setSupportedFeatures(LINKS, SYMBOLIC_LINKS, FILE_CHANNEL).build();
    }

    /**
     * Creates a new mutable {@link Configuration} builder using the given path type.
     */
    public static Builder builder(PathType pathType) {
        return new Builder(pathType);
    }

    // Path configuration
    final PathType pathType;
    final ImmutableSet<PathNormalization> nameDisplayNormalization;
    final ImmutableSet<PathNormalization> nameCanonicalNormalization;
    final boolean pathEqualityUsesCanonicalForm;

    // Disk configuration
    final int blockSize;
    final long maxSize;
    final long maxCacheSize;

    // Attribute configuration
    final ImmutableSet<String> attributeViews;
    final ImmutableSet<AttributeProvider> attributeProviders;
    final ImmutableMap<String, Object> defaultAttributeValues;

    // Other
    final ImmutableSet<String> roots;
    final String workingDirectory;
    final ImmutableSet<Feature> supportedFeatures;

    /**
     * Creates an immutable configuration object from the given builder.
     */
    private Configuration(Builder builder) {
        this.pathType = builder.pathType;
        this.nameDisplayNormalization = builder.nameDisplayNormalization;
        this.nameCanonicalNormalization = builder.nameCanonicalNormalization;
        this.pathEqualityUsesCanonicalForm = builder.pathEqualityUsesCanonicalForm;
        this.blockSize = builder.blockSize;
        this.maxSize = builder.maxSize;
        this.maxCacheSize = builder.maxCacheSize;
        this.attributeViews = builder.attributeViews;
        this.attributeProviders = builder.attributeProviders == null ? ImmutableSet.<AttributeProvider>of()
                : ImmutableSet.copyOf(builder.attributeProviders);
        this.defaultAttributeValues = builder.defaultAttributeValues == null ? ImmutableMap.<String, Object>of()
                : ImmutableMap.copyOf(builder.defaultAttributeValues);
        this.roots = builder.roots;
        this.workingDirectory = builder.workingDirectory;
        this.supportedFeatures = builder.supportedFeatures;
    }

    /**
     * Returns a new mutable builder that initially contains the same settings as this configuration.
     */
    public Builder toBuilder() {
        return new Builder(this);
    }

    /**
     * Mutable builder for {@link Configuration} objects.
     */
    public static final class Builder {

        /** 8 KB. */
        public static final int DEFAULT_BLOCK_SIZE = 8192;

        /** 4 GB. */
        public static final long DEFAULT_MAX_SIZE = 4L * 1024 * 1024 * 1024;

        /** Equal to the configured max size. */
        public static final long DEFAULT_MAX_CACHE_SIZE = -1;

        // Path configuration
        private final PathType pathType;
        private ImmutableSet<PathNormalization> nameDisplayNormalization = ImmutableSet.of();
        private ImmutableSet<PathNormalization> nameCanonicalNormalization = ImmutableSet.of();
        private boolean pathEqualityUsesCanonicalForm = false;

        // Disk configuration
        private int blockSize = DEFAULT_BLOCK_SIZE;
        private long maxSize = DEFAULT_MAX_SIZE;
        private long maxCacheSize = DEFAULT_MAX_CACHE_SIZE;

        // Attribute configuration
        private ImmutableSet<String> attributeViews = ImmutableSet.of();
        private Set<AttributeProvider> attributeProviders = null;
        private Map<String, Object> defaultAttributeValues;

        // Other
        private ImmutableSet<String> roots = ImmutableSet.of();
        private String workingDirectory;
        private ImmutableSet<Feature> supportedFeatures = ImmutableSet.of();

        private Builder(PathType pathType) {
            this.pathType = checkNotNull(pathType);
        }

        private Builder(Configuration configuration) {
            this.pathType = configuration.pathType;
            this.nameDisplayNormalization = configuration.nameDisplayNormalization;
            this.nameCanonicalNormalization = configuration.nameCanonicalNormalization;
            this.pathEqualityUsesCanonicalForm = configuration.pathEqualityUsesCanonicalForm;
            this.blockSize = configuration.blockSize;
            this.maxSize = configuration.maxSize;
            this.maxCacheSize = configuration.maxCacheSize;
            this.attributeViews = configuration.attributeViews;
            this.attributeProviders = configuration.attributeProviders.isEmpty() ? null
                    : new HashSet<>(configuration.attributeProviders);
            this.defaultAttributeValues = configuration.defaultAttributeValues.isEmpty() ? null
                    : new HashMap<>(configuration.defaultAttributeValues);
            this.roots = configuration.roots;
            this.workingDirectory = configuration.workingDirectory;
            this.supportedFeatures = configuration.supportedFeatures;
        }

        /**
         * Sets the normalizations that will be applied to the display form of filenames. The display
         * form is used in the {@code toString()} of {@code Path} objects.
         */
        public Builder setNameDisplayNormalization(PathNormalization first, PathNormalization... more) {
            this.nameDisplayNormalization = checkNormalizations(Lists.asList(first, more));
            return this;
        }

        /**
         * Returns the normalizations that will be applied to the canonical form of filenames in the
         * file system. The canonical form is used to determine the equality of two filenames when
         * performing a file lookup.
         */
        public Builder setNameCanonicalNormalization(PathNormalization first, PathNormalization... more) {
            this.nameCanonicalNormalization = checkNormalizations(Lists.asList(first, more));
            return this;
        }

        private ImmutableSet<PathNormalization> checkNormalizations(List<PathNormalization> normalizations) {
            PathNormalization none = null;
            PathNormalization normalization = null;
            PathNormalization caseFold = null;
            for (PathNormalization n : normalizations) {
                checkNotNull(n);
                checkNormalizationNotSet(n, none);

                switch (n) {
                case NONE:
                    none = n;
                    break;
                case NFC:
                case NFD:
                    checkNormalizationNotSet(n, normalization);
                    normalization = n;
                    break;
                case CASE_FOLD_UNICODE:
                case CASE_FOLD_ASCII:
                    checkNormalizationNotSet(n, caseFold);
                    caseFold = n;
                    break;
                default:
                    throw new AssertionError(); // there are no other cases
                }
            }

            if (none != null) {
                return ImmutableSet.of();
            }
            return Sets.immutableEnumSet(normalizations);
        }

        private static void checkNormalizationNotSet(PathNormalization n, @Nullable PathNormalization set) {
            if (set != null) {
                throw new IllegalArgumentException(
                        "can't set normalization " + n + ": normalization " + set + " already set");
            }
        }

        /**
         * Sets whether {@code Path} objects in the file system use the canonical form (true) or the
         * display form (false) of filenames for determining equality of two paths.
         *
         * <p>The default is false.
         */
        public Builder setPathEqualityUsesCanonicalForm(boolean useCanonicalForm) {
            this.pathEqualityUsesCanonicalForm = useCanonicalForm;
            return this;
        }

        /**
         * Sets the block size (in bytes) for the file system to use. All regular files will be
         * allocated blocks of the given size, so this is the minimum granularity for file size.
         *
         * <p>The default is 8192 bytes (8 KB).
         */
        public Builder setBlockSize(int blockSize) {
            checkArgument(blockSize > 0, "blockSize (%s) must be positive", blockSize);
            this.blockSize = blockSize;
            return this;
        }

        /**
         * Sets the maximum size (in bytes) for the file system's in-memory file storage. This maximum
         * size determines the maximum number of blocks that can be allocated to regular files, so it
         * should generally be a multiple of the {@linkplain #setBlockSize(int) block size}. The actual
         * maximum size will be the nearest multiple of the block size that is less than or equal to
         * the given size.
         *
         * <p><b>Note:</b> The in-memory file storage will not be eagerly initialized to this size, so
         * it won't use more memory than is needed for the files you create. Also note that in addition
         * to this limit, you will of course be limited by the amount of heap space available to the
         * JVM and the amount of heap used by other objects, both in the file system and elsewhere.
         *
         * <p>The default is 4 GB.
         */
        public Builder setMaxSize(long maxSize) {
            checkArgument(maxSize > 0, "maxSize (%s) must be positive", maxSize);
            this.maxSize = maxSize;
            return this;
        }

        /**
         * Sets the maximum amount of unused space (in bytes) in the file system's in-memory file
         * storage that should be cached for reuse. By default, this will be equal to the
         * {@linkplain #setMaxSize(long) maximum size} of the storage, meaning that all space that is
         * freed when files are truncated or deleted is cached for reuse. This helps to avoid lots of
         * garbage collection when creating and deleting many files quickly. This can be set to 0 to
         * disable caching entirely (all freed blocks become available for garbage collection) or to
         * some other number to put an upper bound on the maximum amount of unused space the file
         * system will keep around.
         *
         * <p>Like the maximum size, the actual value will be the closest multiple of the block size
         * that is less than or equal to the given size.
         */
        public Builder setMaxCacheSize(long maxCacheSize) {
            checkArgument(maxCacheSize >= 0, "maxCacheSize (%s) may not be negative", maxCacheSize);
            this.maxCacheSize = maxCacheSize;
            return this;
        }

        /**
         * Sets the attribute views the file system should support. By default, the following views may
         * be specified:
         *
         * <table>
         *   <tr>
         *     <td><b>Name</b></td>
         *     <td><b>View Interface</b></td>
         *     <td><b>Attributes Interface</b></td>
         *   </tr>
         *   <tr>
         *     <td>{@code "basic"}</td>
         *     <td>{@link java.nio.file.attribute.BasicFileAttributeView BasicFileAttributeView}</td>
         *     <td>{@link java.nio.file.attribute.BasicFileAttributes BasicFileAttributes}</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "owner"}</td>
         *     <td>{@link java.nio.file.attribute.FileOwnerAttributeView FileOwnerAttributeView}</td>
         *     <td>--</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "posix"}</td>
         *     <td>{@link java.nio.file.attribute.PosixFileAttributeView PosixFileAttributeView}</td>
         *     <td>{@link java.nio.file.attribute.PosixFileAttributes PosixFileAttributes}</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "unix"}</td>
         *     <td>--</td>
         *     <td>--</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "dos"}</td>
         *     <td>{@link java.nio.file.attribute.DosFileAttributeView DosFileAttributeView}</td>
         *     <td>{@link java.nio.file.attribute.DosFileAttributes DosFileAttributes}</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "acl"}</td>
         *     <td>{@link java.nio.file.attribute.AclFileAttributeView AclFileAttributeView}</td>
         *     <td>--</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "user"}</td>
         *     <td>{@link java.nio.file.attribute.UserDefinedFileAttributeView UserDefinedFileAttributeView}</td>
         *     <td>--</td>
         *   </tr>
         * </table>
         *
         * <p>If any other views should be supported, attribute providers for those views must be
         * {@linkplain #addAttributeProvider(AttributeProvider) added}.
         */
        public Builder setAttributeViews(String first, String... more) {
            this.attributeViews = ImmutableSet.copyOf(Lists.asList(first, more));
            return this;
        }

        /**
         * Adds an attribute provider for a custom view for the file system to support.
         */
        public Builder addAttributeProvider(AttributeProvider provider) {
            checkNotNull(provider);
            if (attributeProviders == null) {
                attributeProviders = new HashSet<>();
            }
            attributeProviders.add(provider);
            return this;
        }

        /**
         * Sets the default value to use for the given file attribute when creating new files. The
         * attribute must be in the form "view:attribute". The value must be of a type that the
         * provider for the view accepts.
         *
         * <p>For the included attribute views, default values can be set for the following attributes:
         *
         * <table>
         *   <tr>
         *     <th>Attribute</th>
         *     <th>Legal Types</th>
         *   </tr>
         *   <tr>
         *     <td>{@code "owner:owner"}</td>
         *     <td>{@code String} (user name)</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "posix:group"}</td>
         *     <td>{@code String} (group name)</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "posix:permissions"}</td>
         *     <td>{@code String} (format "rwxrw-r--"), {@code Set<PosixFilePermission>}</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "dos:readonly"}</td>
         *     <td>{@code Boolean}</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "dos:hidden"}</td>
         *     <td>{@code Boolean}</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "dos:archive"}</td>
         *     <td>{@code Boolean}</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "dos:system"}</td>
         *     <td>{@code Boolean}</td>
         *   </tr>
         *   <tr>
         *     <td>{@code "acl:acl"}</td>
         *     <td>{@code List<AclEntry>}</td>
         *   </tr>
         * </table>
         */
        public Builder setDefaultAttributeValue(String attribute, Object value) {
            checkArgument(ATTRIBUTE_PATTERN.matcher(attribute).matches(),
                    "attribute (%s) must be of the form \"view:attribute\"", attribute);
            checkNotNull(value);

            if (defaultAttributeValues == null) {
                defaultAttributeValues = new HashMap<>();
            }

            defaultAttributeValues.put(attribute, value);
            return this;
        }

        private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("[^:]+:[^:]+");

        /**
         * Sets the roots for the file system.
         *
         * @throws InvalidPathException if any of the given roots is not a valid path for this
         *     builder's path type
         * @throws IllegalArgumentException if any of the given roots is a valid path for this
         *     builder's path type but is not a root path with no name elements
         */
        public Builder setRoots(String first, String... more) {
            List<String> roots = Lists.asList(first, more);
            for (String root : roots) {
                PathType.ParseResult parseResult = pathType.parsePath(root);
                checkArgument(parseResult.isRoot(), "invalid root: %s", root);
            }
            this.roots = ImmutableSet.copyOf(roots);
            return this;
        }

        /**
         * Sets the path to the working directory for the file system. The working directory must be
         * an absolute path starting with one of the configured roots.
         *
         * @throws InvalidPathException if the given path is not valid for this builder's path type
         * @throws IllegalArgumentException if the given path is valid for this builder's path type but
         *     is not an absolute path
         */
        public Builder setWorkingDirectory(String workingDirectory) {
            PathType.ParseResult parseResult = pathType.parsePath(workingDirectory);
            checkArgument(parseResult.isAbsolute(), "working directory must be an absolute path: %s",
                    workingDirectory);
            this.workingDirectory = checkNotNull(workingDirectory);
            return this;
        }

        /**
         * Sets the given features to be supported by the file system. Any features not provided here
         * will not be supported.
         */
        public Builder setSupportedFeatures(Feature... features) {
            supportedFeatures = Sets.immutableEnumSet(Arrays.asList(features));
            return this;
        }

        /**
         * Creates a new immutable configuration object from this builder.
         */
        public Configuration build() {
            return new Configuration(this);
        }
    }
}