| /* |
| * 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); |
| } |
| } |
| } |