• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.jimfs;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static com.google.common.jimfs.Feature.FILE_CHANNEL;
22 import static com.google.common.jimfs.Feature.LINKS;
23 import static com.google.common.jimfs.Feature.SECURE_DIRECTORY_STREAM;
24 import static com.google.common.jimfs.Feature.SYMBOLIC_LINKS;
25 import static com.google.common.jimfs.PathNormalization.CASE_FOLD_ASCII;
26 import static com.google.common.jimfs.PathNormalization.NFC;
27 import static com.google.common.jimfs.PathNormalization.NFD;
28 
29 import com.google.common.base.MoreObjects;
30 import com.google.common.collect.ImmutableMap;
31 import com.google.common.collect.ImmutableSet;
32 import com.google.common.collect.Lists;
33 import com.google.common.collect.Sets;
34 import java.nio.channels.FileChannel;
35 import java.nio.file.FileSystem;
36 import java.nio.file.InvalidPathException;
37 import java.nio.file.SecureDirectoryStream;
38 import java.nio.file.WatchService;
39 import java.nio.file.attribute.BasicFileAttributeView;
40 import java.util.Arrays;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.regex.Pattern;
47 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
48 
49 /**
50  * Immutable configuration for an in-memory file system. A {@code Configuration} is passed to a
51  * method in {@link Jimfs} such as {@link Jimfs#newFileSystem(Configuration)} to create a new {@link
52  * FileSystem} instance.
53  *
54  * @author Colin Decker
55  */
56 public final class Configuration {
57 
58   /**
59    * Returns the default configuration for a UNIX-like file system. A file system created with this
60    * configuration:
61    *
62    * <ul>
63    *   <li>uses {@code /} as the path name separator (see {@link PathType#unix()} for more
64    *       information on the path format)
65    *   <li>has root {@code /} and working directory {@code /work}
66    *   <li>performs case-sensitive file lookup
67    *   <li>supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to avoid
68    *       overhead for unneeded attributes
69    *   <li>supports hard links, symbolic links, {@link SecureDirectoryStream} and {@link
70    *       FileChannel}
71    * </ul>
72    *
73    * <p>To create a modified version of this configuration, such as to include the full set of UNIX
74    * file attribute views, {@linkplain #toBuilder() create a builder}.
75    *
76    * <p>Example:
77    *
78    * <pre>
79    *   Configuration config = Configuration.unix().toBuilder()
80    *       .setAttributeViews("basic", "owner", "posix", "unix")
81    *       .setWorkingDirectory("/home/user")
82    *       .build();  </pre>
83    */
unix()84   public static Configuration unix() {
85     return UnixHolder.UNIX;
86   }
87 
88   private static final class UnixHolder {
89     private static final Configuration UNIX =
90         Configuration.builder(PathType.unix())
91             .setDisplayName("Unix")
92             .setRoots("/")
93             .setWorkingDirectory("/work")
94             .setAttributeViews("basic")
95             .setSupportedFeatures(LINKS, SYMBOLIC_LINKS, SECURE_DIRECTORY_STREAM, FILE_CHANNEL)
96             .build();
97   }
98 
99   /**
100    * Returns the default configuration for a Mac OS X-like file system.
101    *
102    * <p>The primary differences between this configuration and the default {@link #unix()}
103    * configuration are that this configuration does Unicode normalization on the display and
104    * canonical forms of filenames and does case insensitive file lookup.
105    *
106    * <p>A file system created with this configuration:
107    *
108    * <ul>
109    *   <li>uses {@code /} as the path name separator (see {@link PathType#unix()} for more
110    *       information on the path format)
111    *   <li>has root {@code /} and working directory {@code /work}
112    *   <li>does Unicode normalization on paths, both for lookup and for {@code Path} objects
113    *   <li>does case-insensitive (for ASCII characters only) lookup
114    *   <li>supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to avoid
115    *       overhead for unneeded attributes
116    *   <li>supports hard links, symbolic links and {@link FileChannel}
117    * </ul>
118    *
119    * <p>To create a modified version of this configuration, such as to include the full set of UNIX
120    * file attribute views or to use full Unicode case insensitivity, {@linkplain #toBuilder() create
121    * a builder}.
122    *
123    * <p>Example:
124    *
125    * <pre>
126    *   Configuration config = Configuration.osX().toBuilder()
127    *       .setAttributeViews("basic", "owner", "posix", "unix")
128    *       .setNameCanonicalNormalization(NFD, CASE_FOLD_UNICODE)
129    *       .setWorkingDirectory("/Users/user")
130    *       .build();  </pre>
131    */
osX()132   public static Configuration osX() {
133     return OsxHolder.OS_X;
134   }
135 
136   private static final class OsxHolder {
137     private static final Configuration OS_X =
138         unix().toBuilder()
139             .setDisplayName("OSX")
140             .setNameDisplayNormalization(NFC) // matches JDK 1.7u40+ behavior
141             .setNameCanonicalNormalization(NFD, CASE_FOLD_ASCII) // NFD is default in HFS+
142             .setSupportedFeatures(LINKS, SYMBOLIC_LINKS, FILE_CHANNEL)
143             .build();
144   }
145 
146   /**
147    * Returns the default configuration for a Windows-like file system. A file system created with
148    * this configuration:
149    *
150    * <ul>
151    *   <li>uses {@code \} as the path name separator and recognizes {@code /} as a separator when
152    *       parsing paths (see {@link PathType#windows()} for more information on path format)
153    *   <li>has root {@code C:\} and working directory {@code C:\work}
154    *   <li>performs case-insensitive (for ASCII characters only) file lookup
155    *   <li>creates {@code Path} objects that use case-insensitive (for ASCII characters only)
156    *       equality
157    *   <li>supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to avoid
158    *       overhead for unneeded attributes
159    *   <li>supports hard links, symbolic links and {@link FileChannel}
160    * </ul>
161    *
162    * <p>To create a modified version of this configuration, such as to include the full set of
163    * Windows file attribute views or to use full Unicode case insensitivity, {@linkplain
164    * #toBuilder() create a builder}.
165    *
166    * <p>Example:
167    *
168    * <pre>
169    *   Configuration config = Configuration.windows().toBuilder()
170    *       .setAttributeViews("basic", "owner", "dos", "acl", "user")
171    *       .setNameCanonicalNormalization(CASE_FOLD_UNICODE)
172    *       .setWorkingDirectory("C:\\Users\\user") // or "C:/Users/user"
173    *       .build();  </pre>
174    */
windows()175   public static Configuration windows() {
176     return WindowsHolder.WINDOWS;
177   }
178 
179   private static final class WindowsHolder {
180     private static final Configuration WINDOWS =
181         Configuration.builder(PathType.windows())
182             .setDisplayName("Windows")
183             .setRoots("C:\\")
184             .setWorkingDirectory("C:\\work")
185             .setNameCanonicalNormalization(CASE_FOLD_ASCII)
186             .setPathEqualityUsesCanonicalForm(true) // matches real behavior of WindowsPath
187             .setAttributeViews("basic")
188             .setSupportedFeatures(LINKS, SYMBOLIC_LINKS, FILE_CHANNEL)
189             .build();
190   }
191 
192   /**
193    * Returns a default configuration appropriate to the current operating system.
194    *
195    * <p>More specifically, if the operating system is Windows, {@link Configuration#windows()} is
196    * returned; if the operating system is Mac OS X, {@link Configuration#osX()} is returned;
197    * otherwise, {@link Configuration#unix()} is returned.
198    *
199    * <p>This is the configuration used by the {@code Jimfs.newFileSystem} methods that do not take a
200    * {@code Configuration} parameter.
201    *
202    * @since 1.1
203    */
forCurrentPlatform()204   public static Configuration forCurrentPlatform() {
205     String os = System.getProperty("os.name");
206 
207     if (os.contains("Windows")) {
208       return windows();
209     } else if (os.contains("OS X")) {
210       return osX();
211     } else {
212       return unix();
213     }
214   }
215 
216   /** Creates a new mutable {@link Configuration} builder using the given path type. */
builder(PathType pathType)217   public static Builder builder(PathType pathType) {
218     return new Builder(pathType);
219   }
220 
221   // Path configuration
222   final PathType pathType;
223   final ImmutableSet<PathNormalization> nameDisplayNormalization;
224   final ImmutableSet<PathNormalization> nameCanonicalNormalization;
225   final boolean pathEqualityUsesCanonicalForm;
226 
227   // Disk configuration
228   final int blockSize;
229   final long maxSize;
230   final long maxCacheSize;
231 
232   // Attribute configuration
233   final ImmutableSet<String> attributeViews;
234   final ImmutableSet<AttributeProvider> attributeProviders;
235   final ImmutableMap<String, Object> defaultAttributeValues;
236 
237   // Watch service
238   final WatchServiceConfiguration watchServiceConfig;
239 
240   // Other
241   final ImmutableSet<String> roots;
242   final String workingDirectory;
243   final ImmutableSet<Feature> supportedFeatures;
244   private final String displayName;
245 
246   /** Creates an immutable configuration object from the given builder. */
Configuration(Builder builder)247   private Configuration(Builder builder) {
248     this.pathType = builder.pathType;
249     this.nameDisplayNormalization = builder.nameDisplayNormalization;
250     this.nameCanonicalNormalization = builder.nameCanonicalNormalization;
251     this.pathEqualityUsesCanonicalForm = builder.pathEqualityUsesCanonicalForm;
252     this.blockSize = builder.blockSize;
253     this.maxSize = builder.maxSize;
254     this.maxCacheSize = builder.maxCacheSize;
255     this.attributeViews = builder.attributeViews;
256     this.attributeProviders =
257         builder.attributeProviders == null
258             ? ImmutableSet.<AttributeProvider>of()
259             : ImmutableSet.copyOf(builder.attributeProviders);
260     this.defaultAttributeValues =
261         builder.defaultAttributeValues == null
262             ? ImmutableMap.<String, Object>of()
263             : ImmutableMap.copyOf(builder.defaultAttributeValues);
264     this.watchServiceConfig = builder.watchServiceConfig;
265     this.roots = builder.roots;
266     this.workingDirectory = builder.workingDirectory;
267     this.supportedFeatures = builder.supportedFeatures;
268     this.displayName = builder.displayName;
269   }
270 
271   @Override
toString()272   public String toString() {
273     if (displayName != null) {
274       return MoreObjects.toStringHelper(this).addValue(displayName).toString();
275     }
276     MoreObjects.ToStringHelper helper =
277         MoreObjects.toStringHelper(this)
278             .add("pathType", pathType)
279             .add("roots", roots)
280             .add("supportedFeatures", supportedFeatures)
281             .add("workingDirectory", workingDirectory);
282     if (!nameDisplayNormalization.isEmpty()) {
283       helper.add("nameDisplayNormalization", nameDisplayNormalization);
284     }
285     if (!nameCanonicalNormalization.isEmpty()) {
286       helper.add("nameCanonicalNormalization", nameCanonicalNormalization);
287     }
288     helper
289         .add("pathEqualityUsesCanonicalForm", pathEqualityUsesCanonicalForm)
290         .add("blockSize", blockSize)
291         .add("maxSize", maxSize);
292     if (maxCacheSize != Builder.DEFAULT_MAX_CACHE_SIZE) {
293       helper.add("maxCacheSize", maxCacheSize);
294     }
295     if (!attributeViews.isEmpty()) {
296       helper.add("attributeViews", attributeViews);
297     }
298     if (!attributeProviders.isEmpty()) {
299       helper.add("attributeProviders", attributeProviders);
300     }
301     if (!defaultAttributeValues.isEmpty()) {
302       helper.add("defaultAttributeValues", defaultAttributeValues);
303     }
304     if (watchServiceConfig != WatchServiceConfiguration.DEFAULT) {
305       helper.add("watchServiceConfig", watchServiceConfig);
306     }
307     return helper.toString();
308   }
309 
310   /**
311    * Returns a new mutable builder that initially contains the same settings as this configuration.
312    */
toBuilder()313   public Builder toBuilder() {
314     return new Builder(this);
315   }
316 
317   /** Mutable builder for {@link Configuration} objects. */
318   public static final class Builder {
319 
320     /** 8 KB. */
321     public static final int DEFAULT_BLOCK_SIZE = 8192;
322 
323     /** 4 GB. */
324     public static final long DEFAULT_MAX_SIZE = 4L * 1024 * 1024 * 1024;
325 
326     /** Equal to the configured max size. */
327     public static final long DEFAULT_MAX_CACHE_SIZE = -1;
328 
329     // Path configuration
330     private final PathType pathType;
331     private ImmutableSet<PathNormalization> nameDisplayNormalization = ImmutableSet.of();
332     private ImmutableSet<PathNormalization> nameCanonicalNormalization = ImmutableSet.of();
333     private boolean pathEqualityUsesCanonicalForm = false;
334 
335     // Disk configuration
336     private int blockSize = DEFAULT_BLOCK_SIZE;
337     private long maxSize = DEFAULT_MAX_SIZE;
338     private long maxCacheSize = DEFAULT_MAX_CACHE_SIZE;
339 
340     // Attribute configuration
341     private ImmutableSet<String> attributeViews = ImmutableSet.of();
342     private Set<AttributeProvider> attributeProviders = null;
343     private Map<String, Object> defaultAttributeValues;
344 
345     // Watch service
346     private WatchServiceConfiguration watchServiceConfig = WatchServiceConfiguration.DEFAULT;
347 
348     // Other
349     private ImmutableSet<String> roots = ImmutableSet.of();
350     private String workingDirectory;
351     private ImmutableSet<Feature> supportedFeatures = ImmutableSet.of();
352     private String displayName;
353 
Builder(PathType pathType)354     private Builder(PathType pathType) {
355       this.pathType = checkNotNull(pathType);
356     }
357 
Builder(Configuration configuration)358     private Builder(Configuration configuration) {
359       this.pathType = configuration.pathType;
360       this.nameDisplayNormalization = configuration.nameDisplayNormalization;
361       this.nameCanonicalNormalization = configuration.nameCanonicalNormalization;
362       this.pathEqualityUsesCanonicalForm = configuration.pathEqualityUsesCanonicalForm;
363       this.blockSize = configuration.blockSize;
364       this.maxSize = configuration.maxSize;
365       this.maxCacheSize = configuration.maxCacheSize;
366       this.attributeViews = configuration.attributeViews;
367       this.attributeProviders =
368           configuration.attributeProviders.isEmpty()
369               ? null
370               : new HashSet<>(configuration.attributeProviders);
371       this.defaultAttributeValues =
372           configuration.defaultAttributeValues.isEmpty()
373               ? null
374               : new HashMap<>(configuration.defaultAttributeValues);
375       this.watchServiceConfig = configuration.watchServiceConfig;
376       this.roots = configuration.roots;
377       this.workingDirectory = configuration.workingDirectory;
378       this.supportedFeatures = configuration.supportedFeatures;
379       // displayName intentionally not copied from the Configuration
380     }
381 
382     /**
383      * Sets the normalizations that will be applied to the display form of filenames. The display
384      * form is used in the {@code toString()} of {@code Path} objects.
385      */
setNameDisplayNormalization(PathNormalization first, PathNormalization... more)386     public Builder setNameDisplayNormalization(PathNormalization first, PathNormalization... more) {
387       this.nameDisplayNormalization = checkNormalizations(Lists.asList(first, more));
388       return this;
389     }
390 
391     /**
392      * Returns the normalizations that will be applied to the canonical form of filenames in the
393      * file system. The canonical form is used to determine the equality of two filenames when
394      * performing a file lookup.
395      */
setNameCanonicalNormalization( PathNormalization first, PathNormalization... more)396     public Builder setNameCanonicalNormalization(
397         PathNormalization first, PathNormalization... more) {
398       this.nameCanonicalNormalization = checkNormalizations(Lists.asList(first, more));
399       return this;
400     }
401 
checkNormalizations( List<PathNormalization> normalizations)402     private ImmutableSet<PathNormalization> checkNormalizations(
403         List<PathNormalization> normalizations) {
404       PathNormalization none = null;
405       PathNormalization normalization = null;
406       PathNormalization caseFold = null;
407       for (PathNormalization n : normalizations) {
408         checkNotNull(n);
409         checkNormalizationNotSet(n, none);
410 
411         switch (n) {
412           case NONE:
413             none = n;
414             break;
415           case NFC:
416           case NFD:
417             checkNormalizationNotSet(n, normalization);
418             normalization = n;
419             break;
420           case CASE_FOLD_UNICODE:
421           case CASE_FOLD_ASCII:
422             checkNormalizationNotSet(n, caseFold);
423             caseFold = n;
424             break;
425           default:
426             throw new AssertionError(); // there are no other cases
427         }
428       }
429 
430       if (none != null) {
431         return ImmutableSet.of();
432       }
433       return Sets.immutableEnumSet(normalizations);
434     }
435 
checkNormalizationNotSet( PathNormalization n, @NullableDecl PathNormalization set)436     private static void checkNormalizationNotSet(
437         PathNormalization n, @NullableDecl PathNormalization set) {
438       if (set != null) {
439         throw new IllegalArgumentException(
440             "can't set normalization " + n + ": normalization " + set + " already set");
441       }
442     }
443 
444     /**
445      * Sets whether {@code Path} objects in the file system use the canonical form (true) or the
446      * display form (false) of filenames for determining equality of two paths.
447      *
448      * <p>The default is false.
449      */
setPathEqualityUsesCanonicalForm(boolean useCanonicalForm)450     public Builder setPathEqualityUsesCanonicalForm(boolean useCanonicalForm) {
451       this.pathEqualityUsesCanonicalForm = useCanonicalForm;
452       return this;
453     }
454 
455     /**
456      * Sets the block size (in bytes) for the file system to use. All regular files will be
457      * allocated blocks of the given size, so this is the minimum granularity for file size.
458      *
459      * <p>The default is 8192 bytes (8 KB).
460      */
setBlockSize(int blockSize)461     public Builder setBlockSize(int blockSize) {
462       checkArgument(blockSize > 0, "blockSize (%s) must be positive", blockSize);
463       this.blockSize = blockSize;
464       return this;
465     }
466 
467     /**
468      * Sets the maximum size (in bytes) for the file system's in-memory file storage. This maximum
469      * size determines the maximum number of blocks that can be allocated to regular files, so it
470      * should generally be a multiple of the {@linkplain #setBlockSize(int) block size}. The actual
471      * maximum size will be the nearest multiple of the block size that is less than or equal to the
472      * given size.
473      *
474      * <p><b>Note:</b> The in-memory file storage will not be eagerly initialized to this size, so
475      * it won't use more memory than is needed for the files you create. Also note that in addition
476      * to this limit, you will of course be limited by the amount of heap space available to the JVM
477      * and the amount of heap used by other objects, both in the file system and elsewhere.
478      *
479      * <p>The default is 4 GB.
480      */
setMaxSize(long maxSize)481     public Builder setMaxSize(long maxSize) {
482       checkArgument(maxSize > 0, "maxSize (%s) must be positive", maxSize);
483       this.maxSize = maxSize;
484       return this;
485     }
486 
487     /**
488      * Sets the maximum amount of unused space (in bytes) in the file system's in-memory file
489      * storage that should be cached for reuse. By default, this will be equal to the {@linkplain
490      * #setMaxSize(long) maximum size} of the storage, meaning that all space that is freed when
491      * files are truncated or deleted is cached for reuse. This helps to avoid lots of garbage
492      * collection when creating and deleting many files quickly. This can be set to 0 to disable
493      * caching entirely (all freed blocks become available for garbage collection) or to some other
494      * number to put an upper bound on the maximum amount of unused space the file system will keep
495      * around.
496      *
497      * <p>Like the maximum size, the actual value will be the closest multiple of the block size
498      * that is less than or equal to the given size.
499      */
setMaxCacheSize(long maxCacheSize)500     public Builder setMaxCacheSize(long maxCacheSize) {
501       checkArgument(maxCacheSize >= 0, "maxCacheSize (%s) may not be negative", maxCacheSize);
502       this.maxCacheSize = maxCacheSize;
503       return this;
504     }
505 
506     /**
507      * Sets the attribute views the file system should support. By default, the following views may
508      * be specified:
509      *
510      * <table>
511      *   <tr>
512      *     <td><b>Name</b></td>
513      *     <td><b>View Interface</b></td>
514      *     <td><b>Attributes Interface</b></td>
515      *   </tr>
516      *   <tr>
517      *     <td>{@code "basic"}</td>
518      *     <td>{@link java.nio.file.attribute.BasicFileAttributeView BasicFileAttributeView}</td>
519      *     <td>{@link java.nio.file.attribute.BasicFileAttributes BasicFileAttributes}</td>
520      *   </tr>
521      *   <tr>
522      *     <td>{@code "owner"}</td>
523      *     <td>{@link java.nio.file.attribute.FileOwnerAttributeView FileOwnerAttributeView}</td>
524      *     <td>--</td>
525      *   </tr>
526      *   <tr>
527      *     <td>{@code "posix"}</td>
528      *     <td>{@link java.nio.file.attribute.PosixFileAttributeView PosixFileAttributeView}</td>
529      *     <td>{@link java.nio.file.attribute.PosixFileAttributes PosixFileAttributes}</td>
530      *   </tr>
531      *   <tr>
532      *     <td>{@code "unix"}</td>
533      *     <td>--</td>
534      *     <td>--</td>
535      *   </tr>
536      *   <tr>
537      *     <td>{@code "dos"}</td>
538      *     <td>{@link java.nio.file.attribute.DosFileAttributeView DosFileAttributeView}</td>
539      *     <td>{@link java.nio.file.attribute.DosFileAttributes DosFileAttributes}</td>
540      *   </tr>
541      *   <tr>
542      *     <td>{@code "acl"}</td>
543      *     <td>{@link java.nio.file.attribute.AclFileAttributeView AclFileAttributeView}</td>
544      *     <td>--</td>
545      *   </tr>
546      *   <tr>
547      *     <td>{@code "user"}</td>
548      *     <td>{@link java.nio.file.attribute.UserDefinedFileAttributeView UserDefinedFileAttributeView}</td>
549      *     <td>--</td>
550      *   </tr>
551      * </table>
552      *
553      * <p>If any other views should be supported, attribute providers for those views must be
554      * {@linkplain #addAttributeProvider(AttributeProvider) added}.
555      */
setAttributeViews(String first, String... more)556     public Builder setAttributeViews(String first, String... more) {
557       this.attributeViews = ImmutableSet.copyOf(Lists.asList(first, more));
558       return this;
559     }
560 
561     /** Adds an attribute provider for a custom view for the file system to support. */
addAttributeProvider(AttributeProvider provider)562     public Builder addAttributeProvider(AttributeProvider provider) {
563       checkNotNull(provider);
564       if (attributeProviders == null) {
565         attributeProviders = new HashSet<>();
566       }
567       attributeProviders.add(provider);
568       return this;
569     }
570 
571     /**
572      * Sets the default value to use for the given file attribute when creating new files. The
573      * attribute must be in the form "view:attribute". The value must be of a type that the provider
574      * for the view accepts.
575      *
576      * <p>For the included attribute views, default values can be set for the following attributes:
577      *
578      * <table>
579      *   <tr>
580      *     <th>Attribute</th>
581      *     <th>Legal Types</th>
582      *   </tr>
583      *   <tr>
584      *     <td>{@code "owner:owner"}</td>
585      *     <td>{@code String} (user name)</td>
586      *   </tr>
587      *   <tr>
588      *     <td>{@code "posix:group"}</td>
589      *     <td>{@code String} (group name)</td>
590      *   </tr>
591      *   <tr>
592      *     <td>{@code "posix:permissions"}</td>
593      *     <td>{@code String} (format "rwxrw-r--"), {@code Set<PosixFilePermission>}</td>
594      *   </tr>
595      *   <tr>
596      *     <td>{@code "dos:readonly"}</td>
597      *     <td>{@code Boolean}</td>
598      *   </tr>
599      *   <tr>
600      *     <td>{@code "dos:hidden"}</td>
601      *     <td>{@code Boolean}</td>
602      *   </tr>
603      *   <tr>
604      *     <td>{@code "dos:archive"}</td>
605      *     <td>{@code Boolean}</td>
606      *   </tr>
607      *   <tr>
608      *     <td>{@code "dos:system"}</td>
609      *     <td>{@code Boolean}</td>
610      *   </tr>
611      *   <tr>
612      *     <td>{@code "acl:acl"}</td>
613      *     <td>{@code List<AclEntry>}</td>
614      *   </tr>
615      * </table>
616      */
setDefaultAttributeValue(String attribute, Object value)617     public Builder setDefaultAttributeValue(String attribute, Object value) {
618       checkArgument(
619           ATTRIBUTE_PATTERN.matcher(attribute).matches(),
620           "attribute (%s) must be of the form \"view:attribute\"",
621           attribute);
622       checkNotNull(value);
623 
624       if (defaultAttributeValues == null) {
625         defaultAttributeValues = new HashMap<>();
626       }
627 
628       defaultAttributeValues.put(attribute, value);
629       return this;
630     }
631 
632     private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("[^:]+:[^:]+");
633 
634     /**
635      * Sets the roots for the file system.
636      *
637      * @throws InvalidPathException if any of the given roots is not a valid path for this builder's
638      *     path type
639      * @throws IllegalArgumentException if any of the given roots is a valid path for this builder's
640      *     path type but is not a root path with no name elements
641      */
setRoots(String first, String... more)642     public Builder setRoots(String first, String... more) {
643       List<String> roots = Lists.asList(first, more);
644       for (String root : roots) {
645         PathType.ParseResult parseResult = pathType.parsePath(root);
646         checkArgument(parseResult.isRoot(), "invalid root: %s", root);
647       }
648       this.roots = ImmutableSet.copyOf(roots);
649       return this;
650     }
651 
652     /**
653      * Sets the path to the working directory for the file system. The working directory must be an
654      * absolute path starting with one of the configured roots.
655      *
656      * @throws InvalidPathException if the given path is not valid for this builder's path type
657      * @throws IllegalArgumentException if the given path is valid for this builder's path type but
658      *     is not an absolute path
659      */
setWorkingDirectory(String workingDirectory)660     public Builder setWorkingDirectory(String workingDirectory) {
661       PathType.ParseResult parseResult = pathType.parsePath(workingDirectory);
662       checkArgument(
663           parseResult.isAbsolute(),
664           "working directory must be an absolute path: %s",
665           workingDirectory);
666       this.workingDirectory = checkNotNull(workingDirectory);
667       return this;
668     }
669 
670     /**
671      * Sets the given features to be supported by the file system. Any features not provided here
672      * will not be supported.
673      */
setSupportedFeatures(Feature... features)674     public Builder setSupportedFeatures(Feature... features) {
675       supportedFeatures = Sets.immutableEnumSet(Arrays.asList(features));
676       return this;
677     }
678 
679     /**
680      * Sets the configuration that {@link WatchService} instances created by the file system should
681      * use. The default configuration polls watched directories for changes every 5 seconds.
682      *
683      * @since 1.1
684      */
setWatchServiceConfiguration(WatchServiceConfiguration config)685     public Builder setWatchServiceConfiguration(WatchServiceConfiguration config) {
686       this.watchServiceConfig = checkNotNull(config);
687       return this;
688     }
689 
setDisplayName(String displayName)690     private Builder setDisplayName(String displayName) {
691       this.displayName = checkNotNull(displayName);
692       return this;
693     }
694 
695     /** Creates a new immutable configuration object from this builder. */
build()696     public Configuration build() {
697       return new Configuration(this);
698     }
699   }
700 }
701