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