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