diff options
Diffstat (limited to 'jimfs/src/main/java/com/google/common/jimfs/Configuration.java')
-rw-r--r-- | jimfs/src/main/java/com/google/common/jimfs/Configuration.java | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/jimfs/src/main/java/com/google/common/jimfs/Configuration.java b/jimfs/src/main/java/com/google/common/jimfs/Configuration.java new file mode 100644 index 0000000..06630eb --- /dev/null +++ b/jimfs/src/main/java/com/google/common/jimfs/Configuration.java @@ -0,0 +1,700 @@ +/* + * 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.base.MoreObjects; +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.WatchService; +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 org.checkerframework.checker.nullness.compatqual.NullableDecl; + +/** + * 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 { + + /** + * 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>has root {@code /} and working directory {@code /work} + * <li>performs case-sensitive file lookup + * <li>supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to avoid + * overhead for unneeded attributes + * <li>supports hard links, symbolic links, {@link SecureDirectoryStream} and {@link + * FileChannel} + * </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()) + .setDisplayName("Unix") + .setRoots("/") + .setWorkingDirectory("/work") + .setAttributeViews("basic") + .setSupportedFeatures(LINKS, SYMBOLIC_LINKS, SECURE_DIRECTORY_STREAM, FILE_CHANNEL) + .build(); + } + + /** + * 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>has root {@code /} and working directory {@code /work} + * <li>does Unicode normalization on paths, both for lookup and for {@code Path} objects + * <li>does case-insensitive (for ASCII characters only) lookup + * <li>supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to avoid + * overhead for unneeded attributes + * <li>supports hard links, symbolic links and {@link FileChannel} + * </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() + .setDisplayName("OSX") + .setNameDisplayNormalization(NFC) // matches JDK 1.7u40+ behavior + .setNameCanonicalNormalization(NFD, CASE_FOLD_ASCII) // NFD is default in HFS+ + .setSupportedFeatures(LINKS, SYMBOLIC_LINKS, FILE_CHANNEL) + .build(); + } + + /** + * 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>has root {@code C:\} and working directory {@code C:\work} + * <li>performs case-insensitive (for ASCII characters only) file lookup + * <li>creates {@code Path} objects that use case-insensitive (for ASCII characters only) + * equality + * <li>supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to avoid + * overhead for unneeded attributes + * <li>supports hard links, symbolic links and {@link FileChannel} + * </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()) + .setDisplayName("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(); + } + + /** + * Returns a default configuration appropriate to the current operating system. + * + * <p>More specifically, if the operating system is Windows, {@link Configuration#windows()} is + * returned; if the operating system is Mac OS X, {@link Configuration#osX()} is returned; + * otherwise, {@link Configuration#unix()} is returned. + * + * <p>This is the configuration used by the {@code Jimfs.newFileSystem} methods that do not take a + * {@code Configuration} parameter. + * + * @since 1.1 + */ + public static Configuration forCurrentPlatform() { + String os = System.getProperty("os.name"); + + if (os.contains("Windows")) { + return windows(); + } else if (os.contains("OS X")) { + return osX(); + } else { + return unix(); + } + } + + /** 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; + + // Watch service + final WatchServiceConfiguration watchServiceConfig; + + // Other + final ImmutableSet<String> roots; + final String workingDirectory; + final ImmutableSet<Feature> supportedFeatures; + private final String displayName; + + /** 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.watchServiceConfig = builder.watchServiceConfig; + this.roots = builder.roots; + this.workingDirectory = builder.workingDirectory; + this.supportedFeatures = builder.supportedFeatures; + this.displayName = builder.displayName; + } + + @Override + public String toString() { + if (displayName != null) { + return MoreObjects.toStringHelper(this).addValue(displayName).toString(); + } + MoreObjects.ToStringHelper helper = + MoreObjects.toStringHelper(this) + .add("pathType", pathType) + .add("roots", roots) + .add("supportedFeatures", supportedFeatures) + .add("workingDirectory", workingDirectory); + if (!nameDisplayNormalization.isEmpty()) { + helper.add("nameDisplayNormalization", nameDisplayNormalization); + } + if (!nameCanonicalNormalization.isEmpty()) { + helper.add("nameCanonicalNormalization", nameCanonicalNormalization); + } + helper + .add("pathEqualityUsesCanonicalForm", pathEqualityUsesCanonicalForm) + .add("blockSize", blockSize) + .add("maxSize", maxSize); + if (maxCacheSize != Builder.DEFAULT_MAX_CACHE_SIZE) { + helper.add("maxCacheSize", maxCacheSize); + } + if (!attributeViews.isEmpty()) { + helper.add("attributeViews", attributeViews); + } + if (!attributeProviders.isEmpty()) { + helper.add("attributeProviders", attributeProviders); + } + if (!defaultAttributeValues.isEmpty()) { + helper.add("defaultAttributeValues", defaultAttributeValues); + } + if (watchServiceConfig != WatchServiceConfiguration.DEFAULT) { + helper.add("watchServiceConfig", watchServiceConfig); + } + return helper.toString(); + } + + /** + * 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; + + // Watch service + private WatchServiceConfiguration watchServiceConfig = WatchServiceConfiguration.DEFAULT; + + // Other + private ImmutableSet<String> roots = ImmutableSet.of(); + private String workingDirectory; + private ImmutableSet<Feature> supportedFeatures = ImmutableSet.of(); + private String displayName; + + 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.watchServiceConfig = configuration.watchServiceConfig; + this.roots = configuration.roots; + this.workingDirectory = configuration.workingDirectory; + this.supportedFeatures = configuration.supportedFeatures; + // displayName intentionally not copied from the Configuration + } + + /** + * 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, @NullableDecl 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; + } + + /** + * Sets the configuration that {@link WatchService} instances created by the file system should + * use. The default configuration polls watched directories for changes every 5 seconds. + * + * @since 1.1 + */ + public Builder setWatchServiceConfiguration(WatchServiceConfiguration config) { + this.watchServiceConfig = checkNotNull(config); + return this; + } + + private Builder setDisplayName(String displayName) { + this.displayName = checkNotNull(displayName); + return this; + } + + /** Creates a new immutable configuration object from this builder. */ + public Configuration build() { + return new Configuration(this); + } + } +} |