pax_global_header00006660000000000000000000000064126574540550014527gustar00rootroot0000000000000052 comment=75eb99c42b00e70830dc53eb252dd03da48d0f67 jimfs-1.1/000077500000000000000000000000001265745405500125005ustar00rootroot00000000000000jimfs-1.1/.gitignore000066400000000000000000000001071265745405500144660ustar00rootroot00000000000000.idea/ *.ims *.iml .classpath .project .settings/ target/ bin/ out/ jimfs-1.1/.travis.yml000066400000000000000000000016051265745405500146130ustar00rootroot00000000000000sudo: false language: java jdk: - oraclejdk8 - oraclejdk7 - openjdk7 install: mvn install -U -DskipTests=true script: mvn verify -U -Dmaven.javadoc.skip=true after_success: - util/deploy_snapshot.sh - util/update_snapshot_docs.sh cache: directories: - $HOME/.m2 env: global: - secure: "YlCxYTG64KLbyyD2tvA7LwCrNMDCxBigClh8enVicY2Rw6EN9ZTE1YYZivsXAN42YtI1snpy4fTn1z42KUx6FhrlkXVnhLi9TO1lz1lVL4czhqj8MGew20+DJs7tlw3xWRJlRVhqGIXFfximqBsYskm7/+qnHga6uyyV59/VwEI=" - secure: "bTcwsovwxPXplZysfwgNkTR3hfHjb7UvWMlxeEkHHt3GQiZxIDKkiJbgW2mHAG/e/H0wfKQyujeCgQwxn1fa5ttR+UbGz+TIIY2tgjpIFkSbBRzlNGOO0Y23wQpFXXUv3lAY//cV1pa0HlCz+IWNq7ZqPZAoReDAkxExbbmydtE=" - secure: "JZnVEfpNSCLBZQg1MP7MuhzP9H8t2gGUU4salm5VsRKck27fgg1HwBxADolcVeON2k+2masSKLEQPkeYQizc/VN5hZsCZpTgYjuMke1ZLe1v0KsIdH3Rdt77fhhTqiT1BEkMV8tlBwiraYZz+41iLo+Ug5yjgfmXXayDjYm4h4w=" branches: except: - gh-pages jimfs-1.1/CONTRIBUTING.md000066400000000000000000000011471265745405500147340ustar00rootroot00000000000000Contributing to Jimfs ===================== Contributions to Jimfs can be made by forking the repostitory and sending a pull request. Before we can merge any pull requests from you, you must first sign the Google [Contributor License Agreement][1]. When making changes to the code, please try to stay consistent with the style of the existing code, specified by the [Google Java Style Guide][2]. Please also ensure that the code compiles and that changes have appropriate tests. [1]: https://developers.google.com/open-source/cla/individual [2]: http://google-styleguide.googlecode.com/svn/trunk/javaguide.html jimfs-1.1/LICENSE000066400000000000000000000261351265745405500135140ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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.jimfs-1.1/README.md000066400000000000000000000061571265745405500137700ustar00rootroot00000000000000Jimfs ===== Jimfs is an in-memory file system for Java 7 and above, implementing the [java.nio.file](http://docs.oracle.com/javase/7/docs/api/java/nio/file/package-summary.html) abstract file system APIs. [![Build Status](https://travis-ci.org/google/jimfs.svg?branch=master)](https://travis-ci.org/google/jimfs) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.google.jimfs/jimfs/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.google.jimfs/jimfs) Getting started --------------- The latest release is [1.1](https://github.com/google/jimfs/releases/tag/v1.1). It is available in Maven Central as [com.google.jimfs:jimfs:1.1](http://search.maven.org/#artifactdetails%7Ccom.google.jimfs%7Cjimfs%7C1.1%7Cjar): ```xml com.google.jimfs jimfs 1.1 ``` Basic use --------- The simplest way to use Jimfs is to just get a new `FileSystem` instance from the `Jimfs` class and start using it: ```java import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; ... // For a simple file system with Unix-style paths and behavior: FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); Path foo = fs.getPath("/foo"); Files.createDirectory(foo); Path hello = foo.resolve("hello.txt"); // /foo/hello.txt Files.write(hello, ImmutableList.of("hello world"), StandardCharsets.UTF_8); ``` What's supported? ----------------- Jimfs supports almost all the APIs under `java.nio.file`. It supports: - Creating, deleting, moving and copying files and directories. - Reading and writing files with `FileChannel` or `SeekableByteChannel`, `InputStream`, `OutputStream`, etc. - Symbolic links. - Hard links to regular files. - `SecureDirectoryStream`, for operations relative to an _open_ directory. - Glob and regex path filtering with `PathMatcher`. - Watching for changes to a directory with a `WatchService`. - File attributes. Built-in attribute views that can be supported include "basic", "owner", "posix", "unix", "dos", "acl" and "user". Do note, however, that not all attribute views provide _useful_ attributes. For example, while setting and reading POSIX file permissions is possible with the "posix" view, those permissions will not actually affect the behavior of the file system. Jimfs also supports creating file systems that, for example, use Windows-style paths and (to an extent) behavior. In general, however, file system behavior is modeled after UNIX and may not exactly match any particular real file system or platform. License ------- ``` 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. ``` jimfs-1.1/jimfs/000077500000000000000000000000001265745405500136105ustar00rootroot00000000000000jimfs-1.1/jimfs/pom.xml000066400000000000000000000100631265745405500151250ustar00rootroot00000000000000 4.0.0 com.google.jimfs jimfs-parent 1.1 jimfs bundle Jimfs Jimfs is an in-memory implementation of Java 7's java.nio.file abstract file system API. com.google.guava guava com.ibm.icu icu4j true com.google.auto.service auto-service true com.google.code.findbugs jsr305 true junit junit com.google.guava guava-testlib com.google.truth truth maven-source-plugin attach-sources post-integration-test jar-no-fork maven-javadoc-plugin UTF-8 UTF-8 UTF-8 com.google.jimfs.internal http://docs.guava-libraries.googlecode.com/git-history/v${guava.version}/javadoc/ http://jsr-305.googlecode.com/svn/trunk/javadoc/ http://icu-project.org/apiref/icu4j/ http://docs.oracle.com/javase/7/docs/api/ attach-docs post-integration-test jar org.apache.felix maven-bundle-plugin true com.google.common.jimfs.* META-INF/services=target/classes/META-INF/services jimfs-1.1/jimfs/src/000077500000000000000000000000001265745405500143775ustar00rootroot00000000000000jimfs-1.1/jimfs/src/main/000077500000000000000000000000001265745405500153235ustar00rootroot00000000000000jimfs-1.1/jimfs/src/main/java/000077500000000000000000000000001265745405500162445ustar00rootroot00000000000000jimfs-1.1/jimfs/src/main/java/com/000077500000000000000000000000001265745405500170225ustar00rootroot00000000000000jimfs-1.1/jimfs/src/main/java/com/google/000077500000000000000000000000001265745405500202765ustar00rootroot00000000000000jimfs-1.1/jimfs/src/main/java/com/google/common/000077500000000000000000000000001265745405500215665ustar00rootroot00000000000000jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/000077500000000000000000000000001265745405500226765ustar00rootroot00000000000000jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/AbstractAttributeView.java000066400000000000000000000023301265745405500300210ustar00rootroot00000000000000/* * 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.checkNotNull; import java.io.IOException; import java.nio.file.attribute.FileAttributeView; /** * Abstract base class for {@link FileAttributeView} implementations. * * @author Colin Decker */ abstract class AbstractAttributeView implements FileAttributeView { private final FileLookup lookup; protected AbstractAttributeView(FileLookup lookup) { this.lookup = checkNotNull(lookup); } /** * Looks up the file to get or set attributes on. */ protected final File lookupFile() throws IOException { return lookup.lookup(); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/AbstractWatchService.java000066400000000000000000000220161265745405500276150ustar00rootroot00000000000000/* * 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 java.nio.file.StandardWatchEventKinds.OVERFLOW; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.ClosedWatchServiceException; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.Watchable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; /** * Abstract implementation of {@link WatchService}. Provides the means for registering and managing * keys but does not handle actually watching. Subclasses should implement the means of watching * watchables, posting events to registered keys and queueing keys with the service by signalling * them. * * @author Colin Decker */ abstract class AbstractWatchService implements WatchService { private final BlockingQueue queue = new LinkedBlockingQueue<>(); private final WatchKey poison = new Key(this, null, ImmutableSet.>of()); private final AtomicBoolean open = new AtomicBoolean(true); /** * Registers the given watchable with this service, returning a new watch key for it. This * implementation just checks that the service is open and creates a key; subclasses may override * it to do other things as well. */ public Key register(Watchable watchable, Iterable> eventTypes) throws IOException { checkOpen(); return new Key(this, watchable, eventTypes); } /** * Returns whether or not this watch service is open. */ @VisibleForTesting public boolean isOpen() { return open.get(); } /** * Enqueues the given key if the watch service is open; does nothing otherwise. */ final void enqueue(Key key) { if (isOpen()) { queue.add(key); } } /** * Called when the given key is cancelled. Does nothing by default. */ public void cancelled(Key key) {} @VisibleForTesting ImmutableList queuedKeys() { return ImmutableList.copyOf(queue); } @Nullable @Override public WatchKey poll() { checkOpen(); return check(queue.poll()); } @Nullable @Override public WatchKey poll(long timeout, TimeUnit unit) throws InterruptedException { checkOpen(); return check(queue.poll(timeout, unit)); } @Override public WatchKey take() throws InterruptedException { checkOpen(); return check(queue.take()); } /** * Returns the given key, throwing an exception if it's the poison. */ @Nullable private WatchKey check(@Nullable WatchKey key) { if (key == poison) { // ensure other blocking threads get the poison queue.offer(poison); throw new ClosedWatchServiceException(); } return key; } /** * Checks that the watch service is open, throwing {@link ClosedWatchServiceException} if not. */ protected final void checkOpen() { if (!open.get()) { throw new ClosedWatchServiceException(); } } @Override public void close() { if (open.compareAndSet(true, false)) { queue.clear(); queue.offer(poison); } } /** * A basic implementation of {@link WatchEvent}. */ static final class Event implements WatchEvent { private final Kind kind; private final int count; @Nullable private final T context; public Event(Kind kind, int count, @Nullable T context) { this.kind = checkNotNull(kind); checkArgument(count >= 0, "count (%s) must be non-negative", count); this.count = count; this.context = context; } @Override public Kind kind() { return kind; } @Override public int count() { return count; } @Nullable @Override public T context() { return context; } @Override public boolean equals(Object obj) { if (obj instanceof Event) { Event other = (Event) obj; return kind().equals(other.kind()) && count() == other.count() && Objects.equals(context(), other.context()); } return false; } @Override public int hashCode() { return Objects.hash(kind(), count(), context()); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("kind", kind()) .add("count", count()) .add("context", context()) .toString(); } } /** * Implementation of {@link WatchKey} for an {@link AbstractWatchService}. */ static final class Key implements WatchKey { @VisibleForTesting static final int MAX_QUEUE_SIZE = 256; private static WatchEvent overflowEvent(int count) { return new Event<>(OVERFLOW, count, null); } private final AbstractWatchService watcher; private final Watchable watchable; private final ImmutableSet> subscribedTypes; private final AtomicReference state = new AtomicReference<>(State.READY); private final AtomicBoolean valid = new AtomicBoolean(true); private final AtomicInteger overflow = new AtomicInteger(); private final BlockingQueue> events = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE); public Key( AbstractWatchService watcher, @Nullable Watchable watchable, Iterable> subscribedTypes) { this.watcher = checkNotNull(watcher); this.watchable = watchable; // nullable for Watcher poison this.subscribedTypes = ImmutableSet.copyOf(subscribedTypes); } /** * Gets the current state of this key, State.READY or SIGNALLED. */ @VisibleForTesting State state() { return state.get(); } /** * Gets whether or not this key is subscribed to the given type of event. */ public boolean subscribesTo(WatchEvent.Kind eventType) { return subscribedTypes.contains(eventType); } /** * Posts the given event to this key. After posting one or more events, {@link #signal()} must * be called to cause the key to be enqueued with the watch service. */ public void post(WatchEvent event) { if (!events.offer(event)) { overflow.incrementAndGet(); } } /** * Sets the state to SIGNALLED and enqueues this key with the watcher if it was previously in * the READY state. */ public void signal() { if (state.getAndSet(State.SIGNALLED) == State.READY) { watcher.enqueue(this); } } @Override public boolean isValid() { return watcher.isOpen() && valid.get(); } @Override public List> pollEvents() { // note: it's correct to be able to retrieve more events from a key without calling reset() // reset() is ONLY for "returning" the key to the watch service to potentially be retrieved by // another thread when you're finished with it List> result = new ArrayList<>(events.size()); events.drainTo(result); int overflowCount = overflow.getAndSet(0); if (overflowCount != 0) { result.add(overflowEvent(overflowCount)); } return Collections.unmodifiableList(result); } @Override public boolean reset() { // calling reset() multiple times without polling events would cause key to be placed in // watcher queue multiple times, but not much that can be done about that if (isValid() && state.compareAndSet(State.SIGNALLED, State.READY)) { // requeue if events are pending if (!events.isEmpty()) { signal(); } } return isValid(); } @Override public void cancel() { valid.set(false); watcher.cancelled(this); } @Override public Watchable watchable() { return watchable; } @VisibleForTesting enum State { READY, SIGNALLED } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/AclAttributeProvider.java000066400000000000000000000107621265745405500276450ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.attribute.AclEntry; import java.nio.file.attribute.AclFileAttributeView; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileOwnerAttributeView; import java.nio.file.attribute.UserPrincipal; import java.util.List; import java.util.Map; import javax.annotation.Nullable; /** * Attribute provider that provides the {@link AclFileAttributeView} ("acl"). * * @author Colin Decker */ final class AclAttributeProvider extends AttributeProvider { private static final ImmutableSet ATTRIBUTES = ImmutableSet.of("acl"); private static final ImmutableSet INHERITED_VIEWS = ImmutableSet.of("owner"); private static final ImmutableList DEFAULT_ACL = ImmutableList.of(); @Override public String name() { return "acl"; } @Override public ImmutableSet inherits() { return INHERITED_VIEWS; } @Override public ImmutableSet fixedAttributes() { return ATTRIBUTES; } @Override public ImmutableMap defaultValues(Map userProvidedDefaults) { Object userProvidedAcl = userProvidedDefaults.get("acl:acl"); ImmutableList acl = DEFAULT_ACL; if (userProvidedAcl != null) { acl = toAcl(checkType("acl", "acl", userProvidedAcl, List.class)); } return ImmutableMap.of("acl:acl", acl); } @Nullable @Override public Object get(File file, String attribute) { if (attribute.equals("acl")) { return file.getAttribute("acl", "acl"); } return null; } @Override public void set(File file, String view, String attribute, Object value, boolean create) { if (attribute.equals("acl")) { checkNotCreate(view, attribute, create); file.setAttribute("acl", "acl", toAcl(checkType(view, attribute, value, List.class))); } } @SuppressWarnings("unchecked") // only cast after checking each element's type private static ImmutableList toAcl(List list) { ImmutableList copy = ImmutableList.copyOf(list); for (Object obj : copy) { if (!(obj instanceof AclEntry)) { throw new IllegalArgumentException( "invalid element for attribute 'acl:acl': should be List, " + "found element of type " + obj.getClass()); } } return (ImmutableList) copy; } @Override public Class viewType() { return AclFileAttributeView.class; } @Override public AclFileAttributeView view( FileLookup lookup, ImmutableMap inheritedViews) { return new View(lookup, (FileOwnerAttributeView) inheritedViews.get("owner")); } /** * Implementation of {@link AclFileAttributeView}. */ private static final class View extends AbstractAttributeView implements AclFileAttributeView { private final FileOwnerAttributeView ownerView; public View(FileLookup lookup, FileOwnerAttributeView ownerView) { super(lookup); this.ownerView = checkNotNull(ownerView); } @Override public String name() { return "acl"; } @SuppressWarnings("unchecked") @Override public List getAcl() throws IOException { return (List) lookupFile().getAttribute("acl", "acl"); } @Override public void setAcl(List acl) throws IOException { checkNotNull(acl); lookupFile().setAttribute("acl", "acl", ImmutableList.copyOf(acl)); } @Override public UserPrincipal getOwner() throws IOException { return ownerView.getOwner(); } @Override public void setOwner(UserPrincipal owner) throws IOException { ownerView.setOwner(owner); } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/AttributeCopyOption.java000066400000000000000000000016711265745405500275350ustar00rootroot00000000000000/* * Copyright 2014 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; /** * Options for how to handle copying of file attributes when copying a file. * * @author Colin Decker */ enum AttributeCopyOption { /** Copy all attributes on the file. */ ALL, /** Copy only the basic attributes (file times) of the file. */ BASIC, /** Do not copy any of the file's attributes. */ NONE } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/AttributeProvider.java000066400000000000000000000147231265745405500272260ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttributeView; import java.util.Arrays; import java.util.Map; import javax.annotation.Nullable; /** * Abstract provider for handling a specific file attribute view. * * @author Colin Decker */ public abstract class AttributeProvider { /** * Returns the view name that's used to get attributes from this provider. */ public abstract String name(); /** * Returns the names of other providers that this provider inherits attributes from. */ public ImmutableSet inherits() { return ImmutableSet.of(); } /** * Returns the type of the view interface that this provider supports. */ public abstract Class viewType(); /** * Returns a view of the file located by the given lookup callback. The given map contains the * views inherited by this view. */ public abstract FileAttributeView view( FileLookup lookup, ImmutableMap inheritedViews); /** * Returns a map containing the default attribute values for this provider. The keys of the map * are attribute identifier strings (in "view:attribute" form) and the value for each is the * default value that should be set for that attribute when creating a new file. * *

The given map should be in the same format and contains user-provided default values. If * the user provided any default values for attributes handled by this provider, those values * should be checked to ensure they are of the correct type. Additionally, if any changes to a * user-provided attribute are necessary (for example, creating an immutable defensive copy), * that should be done. The resulting values should be included in the result map along with * default values for any attributes the user did not provide a value for. */ public ImmutableMap defaultValues(Map userDefaults) { return ImmutableMap.of(); } /** * Returns the set of attributes that are always available from this provider. */ public abstract ImmutableSet fixedAttributes(); /** * Returns whether or not this provider supports the given attribute directly. */ public boolean supports(String attribute) { return fixedAttributes().contains(attribute); } /** * Returns the set of attributes supported by this view that are present in the given file. For * most providers, this will be a fixed set of attributes. */ public ImmutableSet attributes(File file) { return fixedAttributes(); } /** * Returns the value of the given attribute in the given file or null if the attribute is not * supported by this provider. */ @Nullable public abstract Object get(File file, String attribute); /** * Sets the value of the given attribute in the given file object. The {@code create} * parameter indicates whether or not the value is being set upon creation of a new file via a * user-provided {@code FileAttribute}. * * @throws IllegalArgumentException if the given attribute is one supported by this provider but * it is not allowed to be set by the user * @throws UnsupportedOperationException if the given attribute is one supported by this provider * and is allowed to be set by the user, but not on file creation and {@code create} is true */ public abstract void set(File file, String view, String attribute, Object value, boolean create); // optional /** * Returns the type of file attributes object this provider supports, or null if it doesn't * support reading its attributes as an object. */ @Nullable public Class attributesType() { return null; } /** * Reads this provider's attributes from the given file as an attributes object. * * @throws UnsupportedOperationException if this provider does not support reading an attributes * object */ public BasicFileAttributes readAttributes(File file) { throw new UnsupportedOperationException(); } // exception helpers /** * Throws an illegal argument exception indicating that the given attribute cannot be set. */ protected static IllegalArgumentException unsettable(String view, String attribute) { throw new IllegalArgumentException("cannot set attribute '" + view + ":" + attribute + "'"); } /** * Checks that the attribute is not being set by the user on file creation, throwing an * unsupported operation exception if it is. */ protected static void checkNotCreate(String view, String attribute, boolean create) { if (create) { throw new UnsupportedOperationException( "cannot set attribute '" + view + ":" + attribute + "' during file creation"); } } /** * Checks that the given value is of the given type, returning the value if so and throwing an * exception if not. */ protected static T checkType(String view, String attribute, Object value, Class type) { checkNotNull(value); if (type.isInstance(value)) { return type.cast(value); } throw invalidType(view, attribute, value, type); } /** * Throws an illegal argument exception indicating that the given value is not one of the * expected types for the given attribute. */ protected static IllegalArgumentException invalidType( String view, String attribute, Object value, Class... expectedTypes) { Object expected = expectedTypes.length == 1 ? expectedTypes[0] : "one of " + Arrays.toString(expectedTypes); throw new IllegalArgumentException( "invalid type " + value.getClass() + " for attribute '" + view + ":" + attribute + "': expected " + expected); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/AttributeService.java000066400000000000000000000345761265745405500270440ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Service providing all attribute related operations for a file store. One piece of the file store * implementation. * * @author Colin Decker */ final class AttributeService { private static final String ALL_ATTRIBUTES = "*"; private final ImmutableMap providersByName; private final ImmutableMap, AttributeProvider> providersByViewType; private final ImmutableMap, AttributeProvider> providersByAttributesType; private final ImmutableList> defaultValues; /** * Creates a new attribute service using the given configuration. */ public AttributeService(Configuration configuration) { this(getProviders(configuration), configuration.defaultAttributeValues); } /** * Creates a new attribute service using the given providers and user provided default attribute * values. */ public AttributeService( Iterable providers, Map userProvidedDefaults) { ImmutableMap.Builder byViewNameBuilder = ImmutableMap.builder(); ImmutableMap.Builder, AttributeProvider> byViewTypeBuilder = ImmutableMap.builder(); ImmutableMap.Builder, AttributeProvider> byAttributesTypeBuilder = ImmutableMap.builder(); ImmutableList.Builder> defaultAttributesBuilder = ImmutableList.builder(); for (AttributeProvider provider : providers) { byViewNameBuilder.put(provider.name(), provider); byViewTypeBuilder.put(provider.viewType(), provider); if (provider.attributesType() != null) { byAttributesTypeBuilder.put(provider.attributesType(), provider); } for (Map.Entry entry : provider.defaultValues(userProvidedDefaults).entrySet()) { defaultAttributesBuilder.add(new SimpleFileAttribute<>(entry.getKey(), entry.getValue())); } } this.providersByName = byViewNameBuilder.build(); this.providersByViewType = byViewTypeBuilder.build(); this.providersByAttributesType = byAttributesTypeBuilder.build(); this.defaultValues = defaultAttributesBuilder.build(); } private static Iterable getProviders(Configuration configuration) { Map result = new HashMap<>(); for (AttributeProvider provider : configuration.attributeProviders) { result.put(provider.name(), provider); } for (String view : configuration.attributeViews) { addStandardProvider(result, view); } addMissingProviders(result); return Collections.unmodifiableCollection(result.values()); } private static void addMissingProviders(Map providers) { Set missingViews = new HashSet<>(); for (AttributeProvider provider : providers.values()) { for (String inheritedView : provider.inherits()) { if (!providers.containsKey(inheritedView)) { missingViews.add(inheritedView); } } } if (missingViews.isEmpty()) { return; } // add any inherited views that were not listed directly for (String view : missingViews) { addStandardProvider(providers, view); } // in case any of the providers that were added themselves have missing views they inherit addMissingProviders(providers); } private static void addStandardProvider(Map result, String view) { AttributeProvider provider = StandardAttributeProviders.get(view); if (provider == null) { if (!result.containsKey(view)) { throw new IllegalStateException("no provider found for attribute view '" + view + "'"); } } else { result.put(provider.name(), provider); } } /** * Implements {@link FileSystem#supportedFileAttributeViews()}. */ public ImmutableSet supportedFileAttributeViews() { return providersByName.keySet(); } /** * Implements {@link FileStore#supportsFileAttributeView(Class)}. */ public boolean supportsFileAttributeView(Class type) { return providersByViewType.containsKey(type); } /** * Sets all initial attributes for the given file, including the given attributes if possible. */ public void setInitialAttributes(File file, FileAttribute... attrs) { // default values should already be sanitized by their providers for (int i = 0; i < defaultValues.size(); i++) { FileAttribute attribute = defaultValues.get(i); int separatorIndex = attribute.name().indexOf(':'); String view = attribute.name().substring(0, separatorIndex); String attr = attribute.name().substring(separatorIndex + 1); file.setAttribute(view, attr, attribute.value()); } for (FileAttribute attr : attrs) { setAttribute(file, attr.name(), attr.value(), true); } } /** * Copies the attributes of the given file to the given copy file. */ public void copyAttributes(File file, File copy, AttributeCopyOption copyOption) { switch (copyOption) { case ALL: file.copyAttributes(copy); break; case BASIC: file.copyBasicAttributes(copy); break; default: // don't copy } } /** * Gets the value of the given attribute for the given file. {@code attribute} must be of the * form "view:attribute" or "attribute". */ public Object getAttribute(File file, String attribute) { String view = getViewName(attribute); String attr = getSingleAttribute(attribute); return getAttribute(file, view, attr); } /** * Gets the value of the given attribute for the given view and file. Neither view nor attribute * may have a ':' character. */ public Object getAttribute(File file, String view, String attribute) { Object value = getAttributeInternal(file, view, attribute); if (value == null) { throw new IllegalArgumentException("invalid attribute for view '" + view + "': " + attribute); } return value; } @Nullable private Object getAttributeInternal(File file, String view, String attribute) { AttributeProvider provider = providersByName.get(view); if (provider == null) { return null; } Object value = provider.get(file, attribute); if (value == null) { for (String inheritedView : provider.inherits()) { value = getAttributeInternal(file, inheritedView, attribute); if (value != null) { break; } } } return value; } /** * Sets the value of the given attribute to the given value for the given file. */ public void setAttribute(File file, String attribute, Object value, boolean create) { String view = getViewName(attribute); String attr = getSingleAttribute(attribute); setAttributeInternal(file, view, attr, value, create); } private void setAttributeInternal( File file, String view, String attribute, Object value, boolean create) { AttributeProvider provider = providersByName.get(view); if (provider != null) { if (provider.supports(attribute)) { provider.set(file, view, attribute, value, create); return; } for (String inheritedView : provider.inherits()) { AttributeProvider inheritedProvider = providersByName.get(inheritedView); if (inheritedProvider.supports(attribute)) { inheritedProvider.set(file, view, attribute, value, create); return; } } } throw new IllegalArgumentException("cannot set attribute '" + view + ":" + attribute + "'"); } /** * Returns an attribute view of the given type for the given file lookup callback, or * {@code null} if the view type is not supported. */ @SuppressWarnings("unchecked") @Nullable public V getFileAttributeView(FileLookup lookup, Class type) { AttributeProvider provider = providersByViewType.get(type); if (provider != null) { return (V) provider.view(lookup, createInheritedViews(lookup, provider)); } return null; } private ImmutableMap createInheritedViews( FileLookup lookup, AttributeProvider provider) { if (provider.inherits().isEmpty()) { return ImmutableMap.of(); } Map inheritedViews = new HashMap<>(); createInheritedViews(lookup, provider, inheritedViews); return ImmutableMap.copyOf(inheritedViews); } private void createInheritedViews( FileLookup lookup, AttributeProvider provider, Map inheritedViews) { for (String inherited : provider.inherits()) { if (!inheritedViews.containsKey(inherited)) { AttributeProvider inheritedProvider = providersByName.get(inherited); FileAttributeView inheritedView = getFileAttributeView(lookup, inheritedProvider.viewType(), inheritedViews); inheritedViews.put(inherited, inheritedView); } } } private FileAttributeView getFileAttributeView( FileLookup lookup, Class viewType, Map inheritedViews) { AttributeProvider provider = providersByViewType.get(viewType); createInheritedViews(lookup, provider, inheritedViews); return provider.view(lookup, ImmutableMap.copyOf(inheritedViews)); } /** * Implements {@link Files#readAttributes(Path, String, LinkOption...)}. */ public ImmutableMap readAttributes(File file, String attributes) { String view = getViewName(attributes); List attrs = getAttributeNames(attributes); if (attrs.size() > 1 && attrs.contains(ALL_ATTRIBUTES)) { // attrs contains * and other attributes throw new IllegalArgumentException("invalid attributes: " + attributes); } Map result = new HashMap<>(); if (attrs.size() == 1 && attrs.contains(ALL_ATTRIBUTES)) { // for 'view:*' format, get all keys for all providers for the view AttributeProvider provider = providersByName.get(view); readAll(file, provider, result); for (String inheritedView : provider.inherits()) { AttributeProvider inheritedProvider = providersByName.get(inheritedView); readAll(file, inheritedProvider, result); } } else { // for 'view:attr1,attr2,etc' for (String attr : attrs) { result.put(attr, getAttribute(file, view, attr)); } } return ImmutableMap.copyOf(result); } private static void readAll(File file, AttributeProvider provider, Map map) { for (String attribute : provider.attributes(file)) { Object value = provider.get(file, attribute); // check for null to protect against race condition when an attribute present when // attributes(file) was called is deleted before get() is called for that attribute if (value != null) { map.put(attribute, value); } } } /** * Returns attributes of the given file as an object of the given type. * * @throws UnsupportedOperationException if the given attributes type is not supported */ @SuppressWarnings("unchecked") public A readAttributes(File file, Class type) { AttributeProvider provider = providersByAttributesType.get(type); if (provider != null) { return (A) provider.readAttributes(file); } throw new UnsupportedOperationException("unsupported attributes type: " + type); } private static String getViewName(String attribute) { int separatorIndex = attribute.indexOf(':'); if (separatorIndex == -1) { return "basic"; } // separator must not be at the start or end of the string or appear more than once if (separatorIndex == 0 || separatorIndex == attribute.length() - 1 || attribute.indexOf(':', separatorIndex + 1) != -1) { throw new IllegalArgumentException("illegal attribute format: " + attribute); } return attribute.substring(0, separatorIndex); } private static final Splitter ATTRIBUTE_SPLITTER = Splitter.on(','); private static ImmutableList getAttributeNames(String attributes) { int separatorIndex = attributes.indexOf(':'); String attributesPart = attributes.substring(separatorIndex + 1); return ImmutableList.copyOf(ATTRIBUTE_SPLITTER.split(attributesPart)); } private static String getSingleAttribute(String attribute) { ImmutableList attributeNames = getAttributeNames(attribute); if (attributeNames.size() != 1 || ALL_ATTRIBUTES.equals(attributeNames.get(0))) { throw new IllegalArgumentException("must specify a single attribute: " + attribute); } return attributeNames.get(0); } /** * Simple implementation of {@link FileAttribute}. */ private static final class SimpleFileAttribute implements FileAttribute { private final String name; private final T value; SimpleFileAttribute(String name, T value) { this.name = checkNotNull(name); this.value = checkNotNull(value); } @Override public String name() { return name; } @Override public T value() { return value; } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/BasicAttributeProvider.java000066400000000000000000000146531265745405500301720ustar00rootroot00000000000000/* * 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 com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileTime; import javax.annotation.Nullable; /** * Attribute provider that provides attributes common to all file systems, * the {@link BasicFileAttributeView} ("basic" or no view prefix), and allows the reading of * {@link BasicFileAttributes}. * * @author Colin Decker */ final class BasicAttributeProvider extends AttributeProvider { private static final ImmutableSet ATTRIBUTES = ImmutableSet.of( "size", "fileKey", "isDirectory", "isRegularFile", "isSymbolicLink", "isOther", "creationTime", "lastAccessTime", "lastModifiedTime"); @Override public String name() { return "basic"; } @Override public ImmutableSet fixedAttributes() { return ATTRIBUTES; } @Override public Object get(File file, String attribute) { switch (attribute) { case "size": return file.size(); case "fileKey": return file.id(); case "isDirectory": return file.isDirectory(); case "isRegularFile": return file.isRegularFile(); case "isSymbolicLink": return file.isSymbolicLink(); case "isOther": return !file.isDirectory() && !file.isRegularFile() && !file.isSymbolicLink(); case "creationTime": return FileTime.fromMillis(file.getCreationTime()); case "lastAccessTime": return FileTime.fromMillis(file.getLastAccessTime()); case "lastModifiedTime": return FileTime.fromMillis(file.getLastModifiedTime()); default: return null; } } @Override public void set(File file, String view, String attribute, Object value, boolean create) { switch (attribute) { case "creationTime": checkNotCreate(view, attribute, create); file.setCreationTime(checkType(view, attribute, value, FileTime.class).toMillis()); break; case "lastAccessTime": checkNotCreate(view, attribute, create); file.setLastAccessTime(checkType(view, attribute, value, FileTime.class).toMillis()); break; case "lastModifiedTime": checkNotCreate(view, attribute, create); file.setLastModifiedTime(checkType(view, attribute, value, FileTime.class).toMillis()); break; case "size": case "fileKey": case "isDirectory": case "isRegularFile": case "isSymbolicLink": case "isOther": throw unsettable(view, attribute); default: } } @Override public Class viewType() { return BasicFileAttributeView.class; } @Override public BasicFileAttributeView view( FileLookup lookup, ImmutableMap inheritedViews) { return new View(lookup); } @Override public Class attributesType() { return BasicFileAttributes.class; } @Override public BasicFileAttributes readAttributes(File file) { return new Attributes(file); } /** * Implementation of {@link BasicFileAttributeView}. */ private static final class View extends AbstractAttributeView implements BasicFileAttributeView { protected View(FileLookup lookup) { super(lookup); } @Override public String name() { return "basic"; } @Override public BasicFileAttributes readAttributes() throws IOException { return new Attributes(lookupFile()); } @Override public void setTimes( @Nullable FileTime lastModifiedTime, @Nullable FileTime lastAccessTime, @Nullable FileTime createTime) throws IOException { File file = lookupFile(); if (lastModifiedTime != null) { file.setLastModifiedTime(lastModifiedTime.toMillis()); } if (lastAccessTime != null) { file.setLastAccessTime(lastAccessTime.toMillis()); } if (createTime != null) { file.setCreationTime(createTime.toMillis()); } } } /** * Implementation of {@link BasicFileAttributes}. */ static class Attributes implements BasicFileAttributes { private final FileTime lastModifiedTime; private final FileTime lastAccessTime; private final FileTime creationTime; private final boolean regularFile; private final boolean directory; private final boolean symbolicLink; private final long size; private final Object fileKey; protected Attributes(File file) { this.lastModifiedTime = FileTime.fromMillis(file.getLastModifiedTime()); this.lastAccessTime = FileTime.fromMillis(file.getLastAccessTime()); this.creationTime = FileTime.fromMillis(file.getCreationTime()); this.regularFile = file.isRegularFile(); this.directory = file.isDirectory(); this.symbolicLink = file.isSymbolicLink(); this.size = file.size(); this.fileKey = file.id(); } @Override public FileTime lastModifiedTime() { return lastModifiedTime; } @Override public FileTime lastAccessTime() { return lastAccessTime; } @Override public FileTime creationTime() { return creationTime; } @Override public boolean isRegularFile() { return regularFile; } @Override public boolean isDirectory() { return directory; } @Override public boolean isSymbolicLink() { return symbolicLink; } @Override public boolean isOther() { return false; } @Override public long size() { return size; } @Override public Object fileKey() { return fileKey; } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/Configuration.java000066400000000000000000000612241265745405500263550ustar00rootroot00000000000000/* * 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.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 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 { /** *

Returns the default configuration for a UNIX-like file system. A file system created with * this configuration: * *

* *

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}. * *

Example: * *

   *   Configuration config = Configuration.unix().toBuilder()
   *       .setAttributeViews("basic", "owner", "posix", "unix")
   *       .setWorkingDirectory("/home/user")
   *       .build();  
*/ 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(); } /** *

Returns the default configuration for a Mac OS X-like file system. * *

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. * *

A file system created with this configuration: * *

    *
  • uses {@code /} as the path name separator (see {@link PathType#unix()} for more * information on the path format)
  • *
  • has root {@code /} and working directory {@code /work}
  • *
  • does Unicode normalization on paths, both for lookup and for {@code Path} objects
  • *
  • does case-insensitive (for ASCII characters only) lookup
  • *
  • supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to * avoid overhead for unneeded attributes
  • *
  • supports hard links, symbolic links and {@link FileChannel}
  • *
* *

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}. * *

Example: * *

   *   Configuration config = Configuration.osX().toBuilder()
   *       .setAttributeViews("basic", "owner", "posix", "unix")
   *       .setNameCanonicalNormalization(NFD, CASE_FOLD_UNICODE)
   *       .setWorkingDirectory("/Users/user")
   *       .build();  
*/ 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(); } /** *

Returns the default configuration for a Windows-like file system. A file system created * with this configuration: * *

    *
  • 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)
  • *
  • has root {@code C:\} and working directory {@code C:\work}
  • *
  • performs case-insensitive (for ASCII characters only) file lookup
  • *
  • creates {@code Path} objects that use case-insensitive (for ASCII characters only) * equality
  • *
  • supports only the {@linkplain BasicFileAttributeView basic} file attribute view, to * avoid overhead for unneeded attributes
  • *
  • supports hard links, symbolic links and {@link FileChannel}
  • *
* *

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}. * *

Example: * *

   *   Configuration config = Configuration.windows().toBuilder()
   *       .setAttributeViews("basic", "owner", "dos", "acl", "user")
   *       .setNameCanonicalNormalization(CASE_FOLD_UNICODE)
   *       .setWorkingDirectory("C:\\Users\\user") // or "C:/Users/user"
   *       .build();  
*/ 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(); } /** * Returns a default configuration appropriate to the current operating system. * *

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. * *

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 nameDisplayNormalization; final ImmutableSet nameCanonicalNormalization; final boolean pathEqualityUsesCanonicalForm; // Disk configuration final int blockSize; final long maxSize; final long maxCacheSize; // Attribute configuration final ImmutableSet attributeViews; final ImmutableSet attributeProviders; final ImmutableMap defaultAttributeValues; // Watch service final WatchServiceConfiguration watchServiceConfig; // Other final ImmutableSet roots; final String workingDirectory; final ImmutableSet 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.of() : ImmutableSet.copyOf(builder.attributeProviders); this.defaultAttributeValues = builder.defaultAttributeValues == null ? ImmutableMap.of() : ImmutableMap.copyOf(builder.defaultAttributeValues); this.watchServiceConfig = builder.watchServiceConfig; 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 nameDisplayNormalization = ImmutableSet.of(); private ImmutableSet 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 attributeViews = ImmutableSet.of(); private Set attributeProviders = null; private Map defaultAttributeValues; // Watch service private WatchServiceConfiguration watchServiceConfig = WatchServiceConfiguration.DEFAULT; // Other private ImmutableSet roots = ImmutableSet.of(); private String workingDirectory; private ImmutableSet 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.watchServiceConfig = configuration.watchServiceConfig; 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 checkNormalizations( List 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. * *

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. * *

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. * *

Note: 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. * *

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. * *

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: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
NameView InterfaceAttributes Interface
{@code "basic"}{@link java.nio.file.attribute.BasicFileAttributeView BasicFileAttributeView}{@link java.nio.file.attribute.BasicFileAttributes BasicFileAttributes}
{@code "owner"}{@link java.nio.file.attribute.FileOwnerAttributeView FileOwnerAttributeView}--
{@code "posix"}{@link java.nio.file.attribute.PosixFileAttributeView PosixFileAttributeView}{@link java.nio.file.attribute.PosixFileAttributes PosixFileAttributes}
{@code "unix"}----
{@code "dos"}{@link java.nio.file.attribute.DosFileAttributeView DosFileAttributeView}{@link java.nio.file.attribute.DosFileAttributes DosFileAttributes}
{@code "acl"}{@link java.nio.file.attribute.AclFileAttributeView AclFileAttributeView}--
{@code "user"}{@link java.nio.file.attribute.UserDefinedFileAttributeView UserDefinedFileAttributeView}--
* *

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. * *

For the included attribute views, default values can be set for the following attributes: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
AttributeLegal Types
{@code "owner:owner"}{@code String} (user name)
{@code "posix:group"}{@code String} (group name)
{@code "posix:permissions"}{@code String} (format "rwxrw-r--"), {@code Set}
{@code "dos:readonly"}{@code Boolean}
{@code "dos:hidden"}{@code Boolean}
{@code "dos:archive"}{@code Boolean}
{@code "dos:system"}{@code Boolean}
{@code "acl:acl"}{@code List}
*/ 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 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; } /** * Creates a new immutable configuration object from this builder. */ public Configuration build() { return new Configuration(this); } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/Directory.java000066400000000000000000000243101265745405500255050ustar00rootroot00000000000000/* * 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 com.google.common.annotations.VisibleForTesting; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableSortedSet; import java.util.Iterator; import javax.annotation.Nullable; /** * A table of {@linkplain DirectoryEntry directory entries}. * * @author Colin Decker */ final class Directory extends File implements Iterable { /** The entry linking to this directory in its parent directory. */ private DirectoryEntry entryInParent; /** * Creates a new normal directory with the given ID. */ public static Directory create(int id) { return new Directory(id); } /** * Creates a new root directory with the given ID and name. */ public static Directory createRoot(int id, Name name) { return new Directory(id, name); } private Directory(int id) { super(id); put(new DirectoryEntry(this, Name.SELF, this)); } private Directory(int id, Name rootName) { this(id); linked(new DirectoryEntry(this, rootName, this)); } /** * Creates a copy of this directory. The copy does not contain a copy of the entries in * this directory. */ @Override Directory copyWithoutContent(int id) { return Directory.create(id); } /** * Returns the entry linking to this directory in its parent. If this directory has been deleted, * this returns the entry for it in the directory it was in when it was deleted. */ public DirectoryEntry entryInParent() { return entryInParent; } /** * Returns the parent of this directory. If this directory has been deleted, this returns the * directory it was in when it was deleted. */ public Directory parent() { return entryInParent.directory(); } @Override void linked(DirectoryEntry entry) { File parent = entry.directory(); // handles null check this.entryInParent = entry; forcePut(new DirectoryEntry(this, Name.PARENT, parent)); } @Override void unlinked() { // we don't actually remove the parent link when this directory is unlinked, but the parent's // link count should go down all the same parent().decrementLinkCount(); } /** * Returns the number of entries in this directory. */ @VisibleForTesting int entryCount() { return entryCount; } /** * Returns true if this directory has no entries other than those to itself and its parent. */ public boolean isEmpty() { return entryCount() == 2; } /** * Returns the entry for the given name in this table or null if no such entry exists. */ @Nullable public DirectoryEntry get(Name name) { int index = bucketIndex(name, table.length); DirectoryEntry entry = table[index]; while (entry != null) { if (name.equals(entry.name())) { return entry; } entry = entry.next; } return null; } /** * Links the given name to the given file in this directory. * * @throws IllegalArgumentException if {@code name} is a reserved name such as "." or if an * entry already exists for the name */ public void link(Name name, File file) { DirectoryEntry entry = new DirectoryEntry(this, checkNotReserved(name, "link"), file); put(entry); file.linked(entry); } /** * Unlinks the given name from the file it is linked to. * * @throws IllegalArgumentException if {@code name} is a reserved name such as "." or no entry * exists for the name */ public void unlink(Name name) { DirectoryEntry entry = remove(checkNotReserved(name, "unlink")); entry.file().unlinked(); } /** * Creates an immutable sorted snapshot of the names this directory contains, excluding "." and * "..". */ public ImmutableSortedSet snapshot() { ImmutableSortedSet.Builder builder = new ImmutableSortedSet.Builder<>(Name.displayOrdering()); for (DirectoryEntry entry : this) { if (!isReserved(entry.name())) { builder.add(entry.name()); } } return builder.build(); } /** * Checks that the given name is not "." or "..". Those names cannot be set/removed by users. */ private static Name checkNotReserved(Name name, String action) { if (isReserved(name)) { throw new IllegalArgumentException("cannot " + action + ": " + name); } return name; } /** * Returns true if the given name is "." or "..". */ private static boolean isReserved(Name name) { // all "." and ".." names are canonicalized to the same objects, so we can use identity return name == Name.SELF || name == Name.PARENT; } // Simple hash table code to avoid allocation of Map.Entry objects when DirectoryEntry can // serve the same purpose. private static final int INITIAL_CAPACITY = 16; private static final int INITIAL_RESIZE_THRESHOLD = (int) (INITIAL_CAPACITY * 0.75); private DirectoryEntry[] table = new DirectoryEntry[INITIAL_CAPACITY]; private int resizeThreshold = INITIAL_RESIZE_THRESHOLD; private int entryCount; /** * Returns the index of the bucket in the array where an entry for the given name should go. */ private static int bucketIndex(Name name, int tableLength) { return name.hashCode() & (tableLength - 1); } /** * Adds the given entry to the directory. * * @throws IllegalArgumentException if an entry with the given entry's name already exists in the * directory */ @VisibleForTesting void put(DirectoryEntry entry) { put(entry, false); } /** * Adds the given entry to the directory, overwriting an existing entry with the same name if * such an entry exists. */ private void forcePut(DirectoryEntry entry) { put(entry, true); } /** * Adds the given entry to the directory. {@code overwriteExisting} determines whether an existing * entry with the same name should be overwritten or an exception should be thrown. */ private void put(DirectoryEntry entry, boolean overwriteExisting) { int index = bucketIndex(entry.name(), table.length); // find the place the new entry should go, ensuring an entry with the same name doesn't already // exist along the way DirectoryEntry prev = null; DirectoryEntry curr = table[index]; while (curr != null) { if (curr.name().equals(entry.name())) { if (overwriteExisting) { // just replace the existing entry; no need to expand, and entryCount doesn't change if (prev != null) { prev.next = entry; } else { table[index] = entry; } entry.next = curr.next; curr.next = null; entry.file().incrementLinkCount(); return; } else { throw new IllegalArgumentException("entry '" + entry.name() + "' already exists"); } } prev = curr; curr = curr.next; } entryCount++; if (expandIfNeeded()) { // if the table was expanded, the index/entry we found is no longer applicable, so just add // the entry normally index = bucketIndex(entry.name(), table.length); addToBucket(index, table, entry); } else { // otherwise, we just can use the index/entry we found if (prev != null) { prev.next = entry; } else { table[index] = entry; } } entry.file().incrementLinkCount(); } private boolean expandIfNeeded() { if (entryCount <= resizeThreshold) { return false; } DirectoryEntry[] newTable = new DirectoryEntry[table.length << 1]; // redistribute all current entries in the new table for (DirectoryEntry entry : table) { while (entry != null) { int index = bucketIndex(entry.name(), newTable.length); addToBucket(index, newTable, entry); DirectoryEntry next = entry.next; // set entry.next to null; it's always the last entry in its bucket after being added entry.next = null; entry = next; } } this.table = newTable; resizeThreshold <<= 1; return true; } private static void addToBucket( int bucketIndex, DirectoryEntry[] table, DirectoryEntry entryToAdd) { DirectoryEntry prev = null; DirectoryEntry existing = table[bucketIndex]; while (existing != null) { prev = existing; existing = existing.next; } if (prev != null) { prev.next = entryToAdd; } else { table[bucketIndex] = entryToAdd; } } /** * Removes and returns the entry for the given name from the directory. * * @throws IllegalArgumentException if there is no entry with the given name in the directory */ @VisibleForTesting DirectoryEntry remove(Name name) { int index = bucketIndex(name, table.length); DirectoryEntry prev = null; DirectoryEntry entry = table[index]; while (entry != null) { if (name.equals(entry.name())) { if (prev != null) { prev.next = entry.next; } else { table[index] = entry.next; } entry.next = null; entryCount--; entry.file().decrementLinkCount(); return entry; } prev = entry; entry = entry.next; } throw new IllegalArgumentException("no entry matching '" + name + "' in this directory"); } @Override public Iterator iterator() { return new AbstractIterator() { int index; @Nullable DirectoryEntry entry; @Override protected DirectoryEntry computeNext() { if (entry != null) { entry = entry.next; } while (entry == null && index < table.length) { entry = table[index++]; } return entry != null ? entry : endOfData(); } }; } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/DirectoryEntry.java000066400000000000000000000114271265745405500265340ustar00rootroot00000000000000/* * 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.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.google.common.base.MoreObjects; import java.nio.file.FileAlreadyExistsException; import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; import java.nio.file.NotLinkException; import java.nio.file.Path; import java.util.Objects; import javax.annotation.Nullable; /** * Entry in a directory, containing references to the directory itself, the file the entry links to * and the name of the entry. * *

May also represent a non-existent entry if the name does not link to any file in the * directory. */ final class DirectoryEntry { private final Directory directory; private final Name name; @Nullable private final File file; @Nullable DirectoryEntry next; // for use in Directory DirectoryEntry(Directory directory, Name name, @Nullable File file) { this.directory = checkNotNull(directory); this.name = checkNotNull(name); this.file = file; } /** * Returns {@code true} if and only if this entry represents an existing file. */ public boolean exists() { return file != null; } /** * Checks that this entry exists, throwing an exception if not. * * @return this * @throws NoSuchFileException if this entry does not exist */ public DirectoryEntry requireExists(Path pathForException) throws NoSuchFileException { if (!exists()) { throw new NoSuchFileException(pathForException.toString()); } return this; } /** * Checks that this entry does not exist, throwing an exception if it does. * * @return this * @throws FileAlreadyExistsException if this entry does not exist */ public DirectoryEntry requireDoesNotExist(Path pathForException) throws FileAlreadyExistsException { if (exists()) { throw new FileAlreadyExistsException(pathForException.toString()); } return this; } /** * Checks that this entry exists and links to a directory, throwing an exception if not. * * @return this * @throws NoSuchFileException if this entry does not exist * @throws NotDirectoryException if this entry does not link to a directory */ public DirectoryEntry requireDirectory(Path pathForException) throws NoSuchFileException, NotDirectoryException { requireExists(pathForException); if (!file().isDirectory()) { throw new NotDirectoryException(pathForException.toString()); } return this; } /** * Checks that this entry exists and links to a symbolic link, throwing an exception if not. * * @return this * @throws NoSuchFileException if this entry does not exist * @throws NotLinkException if this entry does not link to a symbolic link */ public DirectoryEntry requireSymbolicLink(Path pathForException) throws NoSuchFileException, NotLinkException { requireExists(pathForException); if (!file().isSymbolicLink()) { throw new NotLinkException(pathForException.toString()); } return this; } /** * Returns the directory containing this entry. */ public Directory directory() { return directory; } /** * Returns the name of this entry. */ public Name name() { return name; } /** * Returns the file this entry links to. * * @throws IllegalStateException if the file does not exist */ public File file() { checkState(exists()); return file; } /** * Returns the file this entry links to or {@code null} if the file does not exist */ @Nullable public File fileOrNull() { return file; } @Override public boolean equals(Object obj) { if (obj instanceof DirectoryEntry) { DirectoryEntry other = (DirectoryEntry) obj; return directory.equals(other.directory) && name.equals(other.name) && Objects.equals(file, other.file); } return false; } @Override public int hashCode() { return Objects.hash(directory, name, file); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("directory", directory) .add("name", name) .add("file", file) .toString(); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/DosAttributeProvider.java000066400000000000000000000133651265745405500276750ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.DosFileAttributeView; import java.nio.file.attribute.DosFileAttributes; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileTime; import java.util.Map; import javax.annotation.Nullable; /** * Attribute provider that provides the {@link DosFileAttributeView} ("dos") and allows the reading * of {@link DosFileAttributes}. * * @author Colin Decker */ final class DosAttributeProvider extends AttributeProvider { private static final ImmutableSet ATTRIBUTES = ImmutableSet.of("readonly", "hidden", "archive", "system"); private static final ImmutableSet INHERITED_VIEWS = ImmutableSet.of("basic", "owner"); @Override public String name() { return "dos"; } @Override public ImmutableSet inherits() { return INHERITED_VIEWS; } @Override public ImmutableSet fixedAttributes() { return ATTRIBUTES; } @Override public ImmutableMap defaultValues(Map userProvidedDefaults) { return ImmutableMap.of( "dos:readonly", getDefaultValue("dos:readonly", userProvidedDefaults), "dos:hidden", getDefaultValue("dos:hidden", userProvidedDefaults), "dos:archive", getDefaultValue("dos:archive", userProvidedDefaults), "dos:system", getDefaultValue("dos:system", userProvidedDefaults)); } private static Boolean getDefaultValue(String attribute, Map userProvidedDefaults) { Object userProvidedValue = userProvidedDefaults.get(attribute); if (userProvidedValue != null) { return checkType("dos", attribute, userProvidedValue, Boolean.class); } return false; } @Nullable @Override public Object get(File file, String attribute) { if (ATTRIBUTES.contains(attribute)) { return file.getAttribute("dos", attribute); } return null; } @Override public void set(File file, String view, String attribute, Object value, boolean create) { if (supports(attribute)) { checkNotCreate(view, attribute, create); file.setAttribute("dos", attribute, checkType(view, attribute, value, Boolean.class)); } } @Override public Class viewType() { return DosFileAttributeView.class; } @Override public DosFileAttributeView view( FileLookup lookup, ImmutableMap inheritedViews) { return new View(lookup, (BasicFileAttributeView) inheritedViews.get("basic")); } @Override public Class attributesType() { return DosFileAttributes.class; } @Override public DosFileAttributes readAttributes(File file) { return new Attributes(file); } /** * Implementation of {@link DosFileAttributeView}. */ private static final class View extends AbstractAttributeView implements DosFileAttributeView { private final BasicFileAttributeView basicView; public View(FileLookup lookup, BasicFileAttributeView basicView) { super(lookup); this.basicView = checkNotNull(basicView); } @Override public String name() { return "dos"; } @Override public DosFileAttributes readAttributes() throws IOException { return new Attributes(lookupFile()); } @Override public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException { basicView.setTimes(lastModifiedTime, lastAccessTime, createTime); } @Override public void setReadOnly(boolean value) throws IOException { lookupFile().setAttribute("dos", "readonly", value); } @Override public void setHidden(boolean value) throws IOException { lookupFile().setAttribute("dos", "hidden", value); } @Override public void setSystem(boolean value) throws IOException { lookupFile().setAttribute("dos", "system", value); } @Override public void setArchive(boolean value) throws IOException { lookupFile().setAttribute("dos", "archive", value); } } /** * Implementation of {@link DosFileAttributes}. */ static class Attributes extends BasicAttributeProvider.Attributes implements DosFileAttributes { private final boolean readOnly; private final boolean hidden; private final boolean archive; private final boolean system; protected Attributes(File file) { super(file); this.readOnly = (boolean) file.getAttribute("dos", "readonly"); this.hidden = (boolean) file.getAttribute("dos", "hidden"); this.archive = (boolean) file.getAttribute("dos", "archive"); this.system = (boolean) file.getAttribute("dos", "system"); } @Override public boolean isReadOnly() { return readOnly; } @Override public boolean isHidden() { return hidden; } @Override public boolean isArchive() { return archive; } @Override public boolean isSystem() { return system; } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/DowngradedDirectoryStream.java000066400000000000000000000030201265745405500306530ustar00rootroot00000000000000/* * Copyright 2014 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.checkNotNull; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.nio.file.SecureDirectoryStream; import java.util.Iterator; /** * A thin wrapper around a {@link SecureDirectoryStream} that exists only to implement * {@link DirectoryStream} and NOT implement {@link SecureDirectoryStream}. * * @author Colin Decker */ final class DowngradedDirectoryStream implements DirectoryStream { private final SecureDirectoryStream secureDirectoryStream; DowngradedDirectoryStream(SecureDirectoryStream secureDirectoryStream) { this.secureDirectoryStream = checkNotNull(secureDirectoryStream); } @Override public Iterator iterator() { return secureDirectoryStream.iterator(); } @Override public void close() throws IOException { secureDirectoryStream.close(); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/DowngradedSeekableByteChannel.java000066400000000000000000000040041265745405500313660ustar00rootroot00000000000000/* * Copyright 2014 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.checkNotNull; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; /** * A thin wrapper around a {@link FileChannel} that exists only to implement * {@link SeekableByteChannel} but NOT extend {@link FileChannel}. * * @author Colin Decker */ final class DowngradedSeekableByteChannel implements SeekableByteChannel { private final FileChannel channel; DowngradedSeekableByteChannel(FileChannel channel) { this.channel = checkNotNull(channel); } @Override public int read(ByteBuffer dst) throws IOException { return channel.read(dst); } @Override public int write(ByteBuffer src) throws IOException { return channel.write(src); } @Override public long position() throws IOException { return channel.position(); } @Override public SeekableByteChannel position(long newPosition) throws IOException { channel.position(newPosition); return this; } @Override public long size() throws IOException { return channel.size(); } @Override public SeekableByteChannel truncate(long size) throws IOException { channel.truncate(size); return this; } @Override public boolean isOpen() { return channel.isOpen(); } @Override public void close() throws IOException { channel.close(); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/Feature.java000066400000000000000000000065351265745405500251450ustar00rootroot00000000000000/* * Copyright 2014 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 java.nio.channels.AsynchronousFileChannel; import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.SecureDirectoryStream; import java.nio.file.attribute.FileAttribute; import java.util.Set; import java.util.concurrent.ExecutorService; /** * Optional file system features that may be supported or unsupported by a Jimfs file system * instance. * * @author Colin Decker */ public enum Feature { /** * Feature controlling support for hard links to regular files. * *

Affected method: * *

    *
  • {@link Files#createLink(Path, Path)}
  • *
* *

If this feature is not enabled, this method will throw * {@link UnsupportedOperationException}. */ LINKS, /** * Feature controlling support for symbolic links. * *

Affected methods: * *

    *
  • {@link Files#createSymbolicLink(Path, Path, FileAttribute...)}
  • *
  • {@link Files#readSymbolicLink(Path)}
  • *
* *

If this feature is not enabled, these methods will throw * {@link UnsupportedOperationException}. */ SYMBOLIC_LINKS, /** * Feature controlling support for {@link SecureDirectoryStream}. * *

Affected methods: * *

    *
  • {@link Files#newDirectoryStream(Path)}
  • *
  • {@link Files#newDirectoryStream(Path, DirectoryStream.Filter)}
  • *
  • {@link Files#newDirectoryStream(Path, String)}
  • *
* *

If this feature is enabled, the {@link DirectoryStream} instances returned by these methods * will also implement {@link SecureDirectoryStream}. */ SECURE_DIRECTORY_STREAM, /** * Feature controlling support for {@link FileChannel}. * *

Affected methods: * *

    *
  • {@link Files#newByteChannel(Path, OpenOption...)}
  • *
  • {@link Files#newByteChannel(Path, Set, FileAttribute...)}
  • *
  • {@link FileChannel#open(Path, OpenOption...)}
  • *
  • {@link FileChannel#open(Path, Set, FileAttribute...)}
  • *
  • {@link AsynchronousFileChannel#open(Path, OpenOption...)}
  • *
  • {@link AsynchronousFileChannel#open(Path, Set, ExecutorService, FileAttribute...)}
  • *
* *

If this feature is not enabled, the {@link SeekableByteChannel} instances returned by the * {@code Files} methods will not be {@code FileChannel} instances and the * {@code FileChannel.open} and {@code AsynchronousFileChannel.open} methods will throw * {@link UnsupportedOperationException}. */ // TODO(cgdecker): Should support for AsynchronousFileChannel be a separate feature? FILE_CHANNEL } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/File.java000066400000000000000000000207241265745405500244250ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Table; import java.io.IOException; import java.util.concurrent.locks.ReadWriteLock; import javax.annotation.Nullable; /** * A file object, containing both the file's metadata and content. * * @author Colin Decker */ public abstract class File { private final int id; private int links; private long creationTime; private long lastAccessTime; private long lastModifiedTime; @Nullable // null when only the basic view is used (default) private Table attributes; File(int id) { this.id = id; long now = System.currentTimeMillis(); // TODO(cgdecker): Use a Clock this.creationTime = now; this.lastAccessTime = now; this.lastModifiedTime = now; } /** * Returns the ID of this file. */ public int id() { return id; } /** * Returns the size, in bytes, of this file's content. Directories and symbolic links have a size * of 0. */ public long size() { return 0; } /** * Returns whether or not this file is a directory. */ public final boolean isDirectory() { return this instanceof Directory; } /** * Returns whether or not this file is a regular file. */ public final boolean isRegularFile() { return this instanceof RegularFile; } /** * Returns whether or not this file is a symbolic link. */ public final boolean isSymbolicLink() { return this instanceof SymbolicLink; } /** * Creates a new file of the same type as this file with the given ID. Does not copy the content * of this file unless the cost of copying the content is minimal. This is because this method is * called with a hold on the file system's lock. */ abstract File copyWithoutContent(int id); /** * Copies the content of this file to the given file. The given file must be the same type of * file as this file and should have no content. * *

This method is used for copying the content of a file after copying the file itself. Does * nothing by default. */ void copyContentTo(File file) throws IOException {} /** * Returns the read-write lock for this file's content, or {@code null} if there is no content * lock. */ @Nullable ReadWriteLock contentLock() { return null; } /** * Called when a stream or channel to this file is opened. */ void opened() {} /** * Called when a stream or channel to this file is closed. If there are no more streams or * channels open to the file and it has been deleted, its contents may be deleted. */ void closed() {} /** * Called when (a single link to) this file is deleted. There may be links remaining. Does * nothing by default. */ void deleted() {} /** * Returns whether or not this file is a root directory of the file system. */ final boolean isRootDirectory() { // only root directories have their parent link pointing to themselves return isDirectory() && equals(((Directory) this).parent()); } /** * Returns the current count of links to this file. */ public synchronized final int links() { return links; } /** * Called when this file has been linked in a directory. The given entry is the new directory * entry that links to this file. */ void linked(DirectoryEntry entry) { checkNotNull(entry); } /** * Called when this file has been unlinked from a directory, either for a move or delete. */ void unlinked() {} /** * Increments the link count for this file. */ synchronized final void incrementLinkCount() { links++; } /** * Decrements the link count for this file. */ synchronized final void decrementLinkCount() { links--; } /** * Gets the creation time of the file. */ public synchronized final long getCreationTime() { return creationTime; } /** * Gets the last access time of the file. */ public synchronized final long getLastAccessTime() { return lastAccessTime; } /** * Gets the last modified time of the file. */ public synchronized final long getLastModifiedTime() { return lastModifiedTime; } /** * Sets the creation time of the file. */ synchronized final void setCreationTime(long creationTime) { this.creationTime = creationTime; } /** * Sets the last access time of the file. */ synchronized final void setLastAccessTime(long lastAccessTime) { this.lastAccessTime = lastAccessTime; } /** * Sets the last modified time of the file. */ synchronized final void setLastModifiedTime(long lastModifiedTime) { this.lastModifiedTime = lastModifiedTime; } /** * Sets the last access time of the file to the current time. */ final void updateAccessTime() { setLastAccessTime(System.currentTimeMillis()); } /** * Sets the last modified time of the file to the current time. */ final void updateModifiedTime() { setLastModifiedTime(System.currentTimeMillis()); } /** * Returns the names of the attributes contained in the given attribute view in the file's * attributes table. */ public synchronized final ImmutableSet getAttributeNames(String view) { if (attributes == null) { return ImmutableSet.of(); } return ImmutableSet.copyOf(attributes.row(view).keySet()); } /** * Returns the attribute keys contained in the attributes map for the file. */ @VisibleForTesting synchronized final ImmutableSet getAttributeKeys() { if (attributes == null) { return ImmutableSet.of(); } ImmutableSet.Builder builder = ImmutableSet.builder(); for (Table.Cell cell : attributes.cellSet()) { builder.add(cell.getRowKey() + ':' + cell.getColumnKey()); } return builder.build(); } /** * Gets the value of the given attribute in the given view. */ @Nullable public synchronized final Object getAttribute(String view, String attribute) { if (attributes == null) { return null; } return attributes.get(view, attribute); } /** * Sets the given attribute in the given view to the given value. */ public synchronized final void setAttribute(String view, String attribute, Object value) { if (attributes == null) { attributes = HashBasedTable.create(); } attributes.put(view, attribute, value); } /** * Deletes the given attribute from the given view. */ public synchronized final void deleteAttribute(String view, String attribute) { if (attributes != null) { attributes.remove(view, attribute); } } /** * Copies basic attributes (file times) from this file to the given file. */ synchronized final void copyBasicAttributes(File target) { target.setFileTimes(creationTime, lastModifiedTime, lastAccessTime); } private synchronized void setFileTimes( long creationTime, long lastModifiedTime, long lastAccessTime) { this.creationTime = creationTime; this.lastModifiedTime = lastModifiedTime; this.lastAccessTime = lastAccessTime; } /** * Copies the attributes from this file to the given file. */ synchronized final void copyAttributes(File target) { copyBasicAttributes(target); target.putAll(attributes); } private synchronized void putAll(@Nullable Table attributes) { if (attributes != null && this.attributes != attributes) { if (this.attributes == null) { this.attributes = HashBasedTable.create(); } this.attributes.putAll(attributes); } } @Override public final String toString() { return MoreObjects.toStringHelper(this) .add("id", id()) .toString(); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/FileFactory.java000066400000000000000000000070241265745405500257530ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Supplier; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; /** * Factory for creating new files and copying files. One piece of the file store implementation. * * @author Colin Decker */ final class FileFactory { private final AtomicInteger idGenerator = new AtomicInteger(); private final HeapDisk disk; /** * Creates a new file factory using the given disk for regular files. */ public FileFactory(HeapDisk disk) { this.disk = checkNotNull(disk); } private int nextFileId() { return idGenerator.getAndIncrement(); } /** * Creates a new directory. */ public Directory createDirectory() { return Directory.create(nextFileId()); } /** * Creates a new root directory with the given name. */ public Directory createRootDirectory(Name name) { return Directory.createRoot(nextFileId(), name); } /** * Creates a new regular file. */ @VisibleForTesting RegularFile createRegularFile() { return RegularFile.create(nextFileId(), disk); } /** * Creates a new symbolic link referencing the given target path. */ @VisibleForTesting SymbolicLink createSymbolicLink(JimfsPath target) { return SymbolicLink.create(nextFileId(), target); } /** * Creates and returns a copy of the given file. */ public File copyWithoutContent(File file) throws IOException { return file.copyWithoutContent(nextFileId()); } // suppliers to act as file creation callbacks private final Supplier directorySupplier = new DirectorySupplier(); private final Supplier regularFileSupplier = new RegularFileSupplier(); /** * Returns a supplier that creates directories. */ public Supplier directoryCreator() { return directorySupplier; } /** * Returns a supplier that creates regular files. */ public Supplier regularFileCreator() { return regularFileSupplier; } /** * Returns a supplier that creates a symbolic links to the given path. */ public Supplier symbolicLinkCreator(JimfsPath target) { return new SymbolicLinkSupplier(target); } private final class DirectorySupplier implements Supplier { @Override public Directory get() { return createDirectory(); } } private final class RegularFileSupplier implements Supplier { @Override public RegularFile get() { return createRegularFile(); } } private final class SymbolicLinkSupplier implements Supplier { private final JimfsPath target; protected SymbolicLinkSupplier(JimfsPath target) { this.target = checkNotNull(target); } @Override public SymbolicLink get() { return createSymbolicLink(target); } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/FileLookup.java000066400000000000000000000016421265745405500256150ustar00rootroot00000000000000/* * Copyright 2014 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 java.io.IOException; /** * Callback for looking up a file. * * @author Colin Decker */ public interface FileLookup { /** * Looks up the file. * * @throws IOException if the lookup fails for any reason, such as the file not existing */ File lookup() throws IOException; } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/FileSystemState.java000066400000000000000000000110471265745405500266310ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.base.Throwables; import com.google.common.collect.Sets; import java.io.Closeable; import java.io.IOException; import java.nio.file.ClosedFileSystemException; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** * Object that manages the open/closed state of a file system, ensuring that all open resources * are closed when the file system is closed and that file system methods throw an exception when * the file system has been closed. * * @author Colin Decker */ final class FileSystemState implements Closeable { private final Set resources = Sets.newConcurrentHashSet(); private final Runnable onClose; private final AtomicBoolean open = new AtomicBoolean(true); /** Count of resources currently in the process of being registered. */ private final AtomicInteger registering = new AtomicInteger(); FileSystemState(Runnable onClose) { this.onClose = checkNotNull(onClose); } /** * Returns whether or not the file system is open. */ public boolean isOpen() { return open.get(); } /** * Checks that the file system is open, throwing {@link ClosedFileSystemException} if it is not. */ public void checkOpen() { if (!open.get()) { throw new ClosedFileSystemException(); } } /** * Registers the given resource to be closed when the file system is closed. Should be called * when the resource is opened. */ public C register(C resource) { // Initial open check to avoid incrementing registering if we already know it's closed. // This is to prevent any possibility of a weird pathalogical situation where the do/while // loop in close() keeps looping as register() is called repeatedly from multiple threads. checkOpen(); registering.incrementAndGet(); try { // Need to check again after marking registration in progress to avoid a potential race. // (close() could have run completely between the first checkOpen() and // registering.incrementAndGet().) checkOpen(); resources.add(resource); return resource; } finally { registering.decrementAndGet(); } } /** * Unregisters the given resource. Should be called when the resource is closed. */ public void unregister(Closeable resource) { resources.remove(resource); } /** * Closes the file system, runs the {@code onClose} callback and closes all registered resources. */ @Override public void close() throws IOException { if (open.compareAndSet(true, false)) { onClose.run(); Throwable thrown = null; do { for (Closeable resource : resources) { try { resource.close(); } catch (Throwable e) { if (thrown == null) { thrown = e; } else { thrown.addSuppressed(e); } } finally { // ensure the resource is removed even if it doesn't remove itself when closed resources.remove(resource); } } // It's possible for a thread registering a resource to register that resource after open // has been set to false and even after we've looped through and closed all the resources. // Since registering must be incremented *before* checking the state of open, however, // when we reach this point in that situation either the register call is still in progress // (registering > 0) or the new resource has been successfully added (resources not empty). // In either case, we just need to repeat the loop until there are no more register calls // in progress (no new calls can start and no resources left to close. } while (registering.get() > 0 || !resources.isEmpty()); Throwables.propagateIfPossible(thrown, IOException.class); } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/FileSystemView.java000066400000000000000000000653541265745405500264750ustar00rootroot00000000000000/* * 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.checkNotNull; import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.CREATE_NEW; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.WRITE; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Lists; import java.io.IOException; import java.nio.file.CopyOption; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.DirectoryStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileSystemException; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.SecureDirectoryStream; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import javax.annotation.Nullable; /** * View of a file system with a specific working directory. As all file system operations need to * work when given either relative or absolute paths, this class contains the implementation of most * file system operations, with relative path operations resolving against the working directory. * *

A file system has one default view using the file system's working directory. Additional views * may be created for use in {@link SecureDirectoryStream} instances, which each have a different * working directory they use. * * @author Colin Decker */ final class FileSystemView { private final JimfsFileStore store; private final Directory workingDirectory; private final JimfsPath workingDirectoryPath; /** * Creates a new file system view. */ public FileSystemView( JimfsFileStore store, Directory workingDirectory, JimfsPath workingDirectoryPath) { this.store = checkNotNull(store); this.workingDirectory = checkNotNull(workingDirectory); this.workingDirectoryPath = checkNotNull(workingDirectoryPath); } /** * Returns whether or not this view and the given view belong to the same file system. */ private boolean isSameFileSystem(FileSystemView other) { return store == other.store; } /** * Returns the file system state. */ public FileSystemState state() { return store.state(); } /** * Returns the path of the working directory at the time this view was created. Does not reflect * changes to the path caused by the directory being moved. */ public JimfsPath getWorkingDirectoryPath() { return workingDirectoryPath; } /** * Attempt to look up the file at the given path. */ DirectoryEntry lookUpWithLock(JimfsPath path, Set options) throws IOException { store.readLock().lock(); try { return lookUp(path, options); } finally { store.readLock().unlock(); } } /** * Looks up the file at the given path without locking. */ private DirectoryEntry lookUp(JimfsPath path, Set options) throws IOException { return store.lookUp(workingDirectory, path, options); } /** * Creates a new directory stream for the directory located by the given path. The given * {@code basePathForStream} is that base path that the returned stream will use. This will be * the same as {@code dir} except for streams created relative to another secure stream. */ public DirectoryStream newDirectoryStream( JimfsPath dir, DirectoryStream.Filter filter, Set options, JimfsPath basePathForStream) throws IOException { Directory file = (Directory) lookUpWithLock(dir, options) .requireDirectory(dir) .file(); FileSystemView view = new FileSystemView(store, file, basePathForStream); JimfsSecureDirectoryStream stream = new JimfsSecureDirectoryStream(view, filter, state()); return store.supportsFeature(Feature.SECURE_DIRECTORY_STREAM) ? stream : new DowngradedDirectoryStream(stream); } /** * Snapshots the entries of the working directory of this view. */ public ImmutableSortedSet snapshotWorkingDirectoryEntries() { store.readLock().lock(); try { ImmutableSortedSet names = workingDirectory.snapshot(); workingDirectory.updateAccessTime(); return names; } finally { store.readLock().unlock(); } } /** * Returns a snapshot mapping the names of each file in the directory at the given path to the * last modified time of that file. */ public ImmutableMap snapshotModifiedTimes(JimfsPath path) throws IOException { ImmutableMap.Builder modifiedTimes = ImmutableMap.builder(); store.readLock().lock(); try { Directory dir = (Directory) lookUp(path, Options.FOLLOW_LINKS) .requireDirectory(path) .file(); // TODO(cgdecker): Investigate whether WatchServices should keep a reference to the actual // directory when SecureDirectoryStream is supported rather than looking up the directory // each time the WatchService polls for (DirectoryEntry entry : dir) { if (!entry.name().equals(Name.SELF) && !entry.name().equals(Name.PARENT)) { modifiedTimes.put(entry.name(), entry.file().getLastModifiedTime()); } } return modifiedTimes.build(); } finally { store.readLock().unlock(); } } /** * Returns whether or not the two given paths locate the same file. The second path is located * using the given view rather than this file view. */ public boolean isSameFile(JimfsPath path, FileSystemView view2, JimfsPath path2) throws IOException { if (!isSameFileSystem(view2)) { return false; } store.readLock().lock(); try { File file = lookUp(path, Options.FOLLOW_LINKS).fileOrNull(); File file2 = view2.lookUp(path2, Options.FOLLOW_LINKS).fileOrNull(); return file != null && Objects.equals(file, file2); } finally { store.readLock().unlock(); } } /** * Gets the {@linkplain Path#toRealPath(LinkOption...) real path} to the file located by the * given path. */ public JimfsPath toRealPath( JimfsPath path, PathService pathService, Set options) throws IOException { checkNotNull(path); checkNotNull(options); store.readLock().lock(); try { DirectoryEntry entry = lookUp(path, options).requireExists(path); List names = new ArrayList<>(); names.add(entry.name()); while (!entry.file().isRootDirectory()) { entry = entry.directory().entryInParent(); names.add(entry.name()); } // names are ordered last to first in the list, so get the reverse view List reversed = Lists.reverse(names); Name root = reversed.remove(0); return pathService.createPath(root, reversed); } finally { store.readLock().unlock(); } } /** * Creates a new directory at the given path. The given attributes will be set on the new file if * possible. */ public Directory createDirectory(JimfsPath path, FileAttribute... attrs) throws IOException { return (Directory) createFile(path, store.directoryCreator(), true, attrs); } /** * Creates a new symbolic link at the given path with the given target. The given attributes will * be set on the new file if possible. */ public SymbolicLink createSymbolicLink( JimfsPath path, JimfsPath target, FileAttribute... attrs) throws IOException { if (!store.supportsFeature(Feature.SYMBOLIC_LINKS)) { throw new UnsupportedOperationException(); } return (SymbolicLink) createFile(path, store.symbolicLinkCreator(target), true, attrs); } /** * Creates a new file at the given path if possible, using the given supplier to create the file. * Returns the new file. If {@code allowExisting} is {@code true} and a file already exists at * the given path, returns that file. Otherwise, throws {@link FileAlreadyExistsException}. */ private File createFile( JimfsPath path, Supplier fileCreator, boolean failIfExists, FileAttribute... attrs) throws IOException { checkNotNull(path); checkNotNull(fileCreator); store.writeLock().lock(); try { DirectoryEntry entry = lookUp(path, Options.NOFOLLOW_LINKS); if (entry.exists()) { if (failIfExists) { throw new FileAlreadyExistsException(path.toString()); } // currently can only happen if getOrCreateFile doesn't find the file with the read lock // and then the file is created between when it releases the read lock and when it // acquires the write lock; so, very unlikely return entry.file(); } Directory parent = entry.directory(); File newFile = fileCreator.get(); store.setInitialAttributes(newFile, attrs); parent.link(path.name(), newFile); parent.updateModifiedTime(); return newFile; } finally { store.writeLock().unlock(); } } /** * Gets the regular file at the given path, creating it if it doesn't exist and the given options * specify that it should be created. */ public RegularFile getOrCreateRegularFile( JimfsPath path, Set options, FileAttribute... attrs) throws IOException { checkNotNull(path); if (!options.contains(CREATE_NEW)) { // assume file exists unless we're explicitly trying to create a new file RegularFile file = lookUpRegularFile(path, options); if (file != null) { return file; } } if (options.contains(CREATE) || options.contains(CREATE_NEW)) { return getOrCreateRegularFileWithWriteLock(path, options, attrs); } else { throw new NoSuchFileException(path.toString()); } } /** * Looks up the regular file at the given path, throwing an exception if the file isn't a regular * file. Returns null if the file did not exist. */ @Nullable private RegularFile lookUpRegularFile(JimfsPath path, Set options) throws IOException { store.readLock().lock(); try { DirectoryEntry entry = lookUp(path, options); if (entry.exists()) { File file = entry.file(); if (!file.isRegularFile()) { throw new FileSystemException(path.toString(), null, "not a regular file"); } return open((RegularFile) file, options); } else { return null; } } finally { store.readLock().unlock(); } } /** * Gets or creates a new regular file with a write lock (assuming the file does not exist). */ private RegularFile getOrCreateRegularFileWithWriteLock( JimfsPath path, Set options, FileAttribute[] attrs) throws IOException { store.writeLock().lock(); try { File file = createFile(path, store.regularFileCreator(), options.contains(CREATE_NEW), attrs); // the file already existed but was not a regular file if (!file.isRegularFile()) { throw new FileSystemException(path.toString(), null, "not a regular file"); } return open((RegularFile) file, options); } finally { store.writeLock().unlock(); } } /** * Opens the given regular file with the given options, truncating it if necessary and * incrementing its open count. Returns the given file. */ private static RegularFile open(RegularFile file, Set options) { if (options.contains(TRUNCATE_EXISTING) && options.contains(WRITE)) { file.writeLock().lock(); try { file.truncate(0); } finally { file.writeLock().unlock(); } } // must be opened while holding a file store lock to ensure no race between opening and // deleting the file file.opened(); return file; } /** * Returns the target of the symbolic link at the given path. */ public JimfsPath readSymbolicLink(JimfsPath path) throws IOException { if (!store.supportsFeature(Feature.SYMBOLIC_LINKS)) { throw new UnsupportedOperationException(); } SymbolicLink symbolicLink = (SymbolicLink) lookUpWithLock(path, Options.NOFOLLOW_LINKS) .requireSymbolicLink(path) .file(); return symbolicLink.target(); } /** * Checks access to the file at the given path for the given modes. Since access controls are not * implemented for this file system, this just checks that the file exists. */ public void checkAccess(JimfsPath path) throws IOException { // just check that the file exists lookUpWithLock(path, Options.FOLLOW_LINKS).requireExists(path); } /** * Creates a hard link at the given link path to the regular file at the given path. The existing * file must exist and must be a regular file. The given file system view must belong to the same * file system as this view. */ public void link(JimfsPath link, FileSystemView existingView, JimfsPath existing) throws IOException { checkNotNull(link); checkNotNull(existingView); checkNotNull(existing); if (!store.supportsFeature(Feature.LINKS)) { throw new UnsupportedOperationException(); } if (!isSameFileSystem(existingView)) { throw new FileSystemException( link.toString(), existing.toString(), "can't link: source and target are in different file system instances"); } Name linkName = link.name(); // existingView is in the same file system, so just one lock is needed store.writeLock().lock(); try { // we do want to follow links when finding the existing file File existingFile = existingView .lookUp(existing, Options.FOLLOW_LINKS) .requireExists(existing) .file(); if (!existingFile.isRegularFile()) { throw new FileSystemException( link.toString(), existing.toString(), "can't link: not a regular file"); } Directory linkParent = lookUp(link, Options.NOFOLLOW_LINKS).requireDoesNotExist(link).directory(); linkParent.link(linkName, existingFile); linkParent.updateModifiedTime(); } finally { store.writeLock().unlock(); } } /** * Deletes the file at the given absolute path. */ public void deleteFile(JimfsPath path, DeleteMode deleteMode) throws IOException { store.writeLock().lock(); try { DirectoryEntry entry = lookUp(path, Options.NOFOLLOW_LINKS).requireExists(path); delete(entry, deleteMode, path); } finally { store.writeLock().unlock(); } } /** * Deletes the given directory entry from its parent directory. */ private void delete(DirectoryEntry entry, DeleteMode deleteMode, JimfsPath pathForException) throws IOException { Directory parent = entry.directory(); File file = entry.file(); checkDeletable(file, deleteMode, pathForException); parent.unlink(entry.name()); parent.updateModifiedTime(); file.deleted(); } /** * Mode for deleting. Determines what types of files can be deleted. */ public enum DeleteMode { /** * Delete any file. */ ANY, /** * Only delete non-directory files. */ NON_DIRECTORY_ONLY, /** * Only delete directory files. */ DIRECTORY_ONLY } /** * Checks that the given file can be deleted, throwing an exception if it can't. */ private void checkDeletable(File file, DeleteMode mode, Path path) throws IOException { if (file.isRootDirectory()) { throw new FileSystemException(path.toString(), null, "can't delete root directory"); } if (file.isDirectory()) { if (mode == DeleteMode.NON_DIRECTORY_ONLY) { throw new FileSystemException(path.toString(), null, "can't delete: is a directory"); } checkEmpty(((Directory) file), path); } else if (mode == DeleteMode.DIRECTORY_ONLY) { throw new FileSystemException(path.toString(), null, "can't delete: is not a directory"); } if (file == workingDirectory && !path.isAbsolute()) { // this is weird, but on Unix at least, the file system seems to be happy to delete the // working directory if you give the absolute path to it but fail if you use a relative path // that resolves to the working directory (e.g. "" or ".") throw new FileSystemException(path.toString(), null, "invalid argument"); } } /** * Checks that given directory is empty, throwing {@link DirectoryNotEmptyException} if not. */ private void checkEmpty(Directory dir, Path pathForException) throws FileSystemException { if (!dir.isEmpty()) { throw new DirectoryNotEmptyException(pathForException.toString()); } } /** * Copies or moves the file at the given source path to the given dest path. */ public void copy( JimfsPath source, FileSystemView destView, JimfsPath dest, Set options, boolean move) throws IOException { checkNotNull(source); checkNotNull(destView); checkNotNull(dest); checkNotNull(options); boolean sameFileSystem = isSameFileSystem(destView); File sourceFile; File copyFile = null; // non-null after block completes iff source file was copied lockBoth(store.writeLock(), destView.store.writeLock()); try { DirectoryEntry sourceEntry = lookUp(source, options).requireExists(source); DirectoryEntry destEntry = destView.lookUp(dest, Options.NOFOLLOW_LINKS); Directory sourceParent = sourceEntry.directory(); sourceFile = sourceEntry.file(); Directory destParent = destEntry.directory(); if (move && sourceFile.isDirectory()) { if (sameFileSystem) { checkMovable(sourceFile, source); checkNotAncestor(sourceFile, destParent, destView); } else { // move to another file system is accomplished by copy-then-delete, so the source file // must be deletable to be moved checkDeletable(sourceFile, DeleteMode.ANY, source); } } if (destEntry.exists()) { if (destEntry.file().equals(sourceFile)) { return; } else if (options.contains(REPLACE_EXISTING)) { destView.delete(destEntry, DeleteMode.ANY, dest); } else { throw new FileAlreadyExistsException(dest.toString()); } } if (move && sameFileSystem) { // Real move on the same file system. sourceParent.unlink(source.name()); sourceParent.updateModifiedTime(); destParent.link(dest.name(), sourceFile); destParent.updateModifiedTime(); } else { // Doing a copy OR a move to a different file system, which must be implemented by copy and // delete. // By default, don't copy attributes. AttributeCopyOption attributeCopyOption = AttributeCopyOption.NONE; if (move) { // Copy only the basic attributes of the file to the other file system, as it may not // support all the attribute views that this file system does. This also matches the // behavior of moving a file to a foreign file system with a different // FileSystemProvider. attributeCopyOption = AttributeCopyOption.BASIC; } else if (options.contains(COPY_ATTRIBUTES)) { // As with move, if we're copying the file to a different file system, only copy its // basic attributes. attributeCopyOption = sameFileSystem ? AttributeCopyOption.ALL : AttributeCopyOption.BASIC; } // Copy the file, but don't copy its content while we're holding the file store locks. copyFile = destView.store.copyWithoutContent(sourceFile, attributeCopyOption); destParent.link(dest.name(), copyFile); destParent.updateModifiedTime(); // In order for the copy to be atomic (not strictly necessary, but seems preferable since // we can) lock both source and copy files before leaving the file store locks. This // ensures that users cannot observe the copy's content until the content has been copied. // This also marks the source file as opened, preventing its content from being deleted // until after it's copied if the source file itself is deleted in the next step. lockSourceAndCopy(sourceFile, copyFile); if (move) { // It should not be possible for delete to throw an exception here, because we already // checked that the file was deletable above. delete(sourceEntry, DeleteMode.ANY, source); } } } finally { destView.store.writeLock().unlock(); store.writeLock().unlock(); } if (copyFile != null) { // Copy the content. This is done outside the above block to minimize the time spent holding // file store locks, since copying the content of a regular file could take a (relatively) // long time. If done inside the above block, copying using Files.copy can be slower than // copying with an InputStream and an OutputStream if many files are being copied on // different threads. try { sourceFile.copyContentTo(copyFile); } finally { // Unlock the files, allowing the content of the copy to be observed by the user. This also // closes the source file, allowing its content to be deleted if it was deleted. unlockSourceAndCopy(sourceFile, copyFile); } } } private void checkMovable(File file, JimfsPath path) throws FileSystemException { if (file.isRootDirectory()) { throw new FileSystemException(path.toString(), null, "can't move root directory"); } } /** * Acquires both write locks in a way that attempts to avoid the possibility of deadlock. Note * that typically (when only one file system instance is involved), both locks will be the same * lock and there will be no issue at all. */ private static void lockBoth(Lock sourceWriteLock, Lock destWriteLock) { while (true) { sourceWriteLock.lock(); if (destWriteLock.tryLock()) { return; } else { sourceWriteLock.unlock(); } destWriteLock.lock(); if (sourceWriteLock.tryLock()) { return; } else { destWriteLock.unlock(); } } } /** * Checks that source is not an ancestor of dest, throwing an exception if it is. */ private void checkNotAncestor(File source, Directory destParent, FileSystemView destView) throws IOException { // if dest is not in the same file system, it couldn't be in source's subdirectories if (!isSameFileSystem(destView)) { return; } Directory current = destParent; while (true) { if (current.equals(source)) { throw new IOException( "invalid argument: can't move directory into a subdirectory of itself"); } if (current.isRootDirectory()) { return; } else { current = current.parent(); } } } /** * Locks source and copy files before copying content. Also marks the source file as opened so * that its content won't be deleted until after the copy if it is deleted. */ private void lockSourceAndCopy(File sourceFile, File copyFile) { sourceFile.opened(); ReadWriteLock sourceLock = sourceFile.contentLock(); if (sourceLock != null) { sourceLock.readLock().lock(); } ReadWriteLock copyLock = copyFile.contentLock(); if (copyLock != null) { copyLock.writeLock().lock(); } } /** * Unlocks source and copy files after copying content. Also closes the source file so its * content can be deleted if it was deleted. */ private void unlockSourceAndCopy(File sourceFile, File copyFile) { ReadWriteLock sourceLock = sourceFile.contentLock(); if (sourceLock != null) { sourceLock.readLock().unlock(); } ReadWriteLock copyLock = copyFile.contentLock(); if (copyLock != null) { copyLock.writeLock().unlock(); } sourceFile.closed(); } /** * Returns a file attribute view using the given lookup callback. */ @Nullable public V getFileAttributeView(FileLookup lookup, Class type) { return store.getFileAttributeView(lookup, type); } /** * Returns a file attribute view for the given path in this view. */ @Nullable public V getFileAttributeView( final JimfsPath path, Class type, final Set options) { return store.getFileAttributeView( new FileLookup() { @Override public File lookup() throws IOException { return lookUpWithLock(path, options) .requireExists(path) .file(); } }, type); } /** * Reads attributes of the file located by the given path in this view as an object. */ public A readAttributes( JimfsPath path, Class type, Set options) throws IOException { File file = lookUpWithLock(path, options) .requireExists(path) .file(); return store.readAttributes(file, type); } /** * Reads attributes of the file located by the given path in this view as a map. */ public ImmutableMap readAttributes( JimfsPath path, String attributes, Set options) throws IOException { File file = lookUpWithLock(path, options) .requireExists(path) .file(); return store.readAttributes(file, attributes); } /** * Sets the given attribute to the given value on the file located by the given path in this * view. */ public void setAttribute( JimfsPath path, String attribute, Object value, Set options) throws IOException { File file = lookUpWithLock(path, options) .requireExists(path) .file(); store.setAttribute(file, attribute, value); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/FileTree.java000066400000000000000000000165111265745405500252440ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import java.io.IOException; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * The tree of directories and files for the file system. Contains the file system root directories * and provides the ability to look up files by path. One piece of the file store implementation. * * @author Colin Decker */ final class FileTree { /** * Doesn't much matter, but this number comes from MIN_ELOOP_THRESHOLD * * here */ private static final int MAX_SYMBOLIC_LINK_DEPTH = 40; private static final ImmutableList EMPTY_PATH_NAMES = ImmutableList.of(Name.SELF); /** * Map of root names to root directories. */ private final ImmutableSortedMap roots; /** * Creates a new file tree with the given root directories. */ FileTree(Map roots) { this.roots = ImmutableSortedMap.copyOf(roots, Name.canonicalOrdering()); } /** * Returns the names of the root directories in this tree. */ public ImmutableSortedSet getRootDirectoryNames() { return roots.keySet(); } /** * Gets the directory entry for the root with the given name or {@code null} if no such root * exists. */ @Nullable public DirectoryEntry getRoot(Name name) { Directory dir = roots.get(name); return dir == null ? null : dir.entryInParent(); } /** * Returns the result of the file lookup for the given path. */ public DirectoryEntry lookUp( File workingDirectory, JimfsPath path, Set options) throws IOException { checkNotNull(path); checkNotNull(options); DirectoryEntry result = lookUp(workingDirectory, path, options, 0); if (result == null) { // an intermediate file in the path did not exist or was not a directory throw new NoSuchFileException(path.toString()); } return result; } @Nullable private DirectoryEntry lookUp( File dir, JimfsPath path, Set options, int linkDepth) throws IOException { ImmutableList names = path.names(); if (path.isAbsolute()) { // look up the root directory DirectoryEntry entry = getRoot(path.root()); if (entry == null) { // root not found; always return null as no real parent directory exists // this prevents new roots from being created in file systems supporting multiple roots return null; } else if (names.isEmpty()) { // root found, no more names to look up return entry; } else { // root found, more names to look up; set dir to the root directory for the path dir = entry.file(); } } else if (isEmpty(names)) { // set names to the canonical list of names for an empty path (singleton list of ".") names = EMPTY_PATH_NAMES; } return lookUp(dir, names, options, linkDepth); } /** * Looks up the given names against the given base file. If the file is not a directory, the * lookup fails. */ @Nullable private DirectoryEntry lookUp( File dir, Iterable names, Set options, int linkDepth) throws IOException { Iterator nameIterator = names.iterator(); Name name = nameIterator.next(); while (nameIterator.hasNext()) { Directory directory = toDirectory(dir); if (directory == null) { return null; } DirectoryEntry entry = directory.get(name); if (entry == null) { return null; } File file = entry.file(); if (file.isSymbolicLink()) { DirectoryEntry linkResult = followSymbolicLink(dir, (SymbolicLink) file, linkDepth); if (linkResult == null) { return null; } dir = linkResult.fileOrNull(); } else { dir = file; } name = nameIterator.next(); } return lookUpLast(dir, name, options, linkDepth); } /** * Looks up the last element of a path. */ @Nullable private DirectoryEntry lookUpLast( @Nullable File dir, Name name, Set options, int linkDepth) throws IOException { Directory directory = toDirectory(dir); if (directory == null) { return null; } DirectoryEntry entry = directory.get(name); if (entry == null) { return new DirectoryEntry(directory, name, null); } File file = entry.file(); if (!options.contains(LinkOption.NOFOLLOW_LINKS) && file.isSymbolicLink()) { return followSymbolicLink(dir, (SymbolicLink) file, linkDepth); } return getRealEntry(entry); } /** * Returns the directory entry located by the target path of the given symbolic link, resolved * relative to the given directory. */ @Nullable private DirectoryEntry followSymbolicLink(File dir, SymbolicLink link, int linkDepth) throws IOException { if (linkDepth >= MAX_SYMBOLIC_LINK_DEPTH) { throw new IOException("too many levels of symbolic links"); } return lookUp(dir, link.target(), Options.FOLLOW_LINKS, linkDepth + 1); } /** * Returns the entry for the file in its parent directory. This will be the given entry unless the * name for the entry is "." or "..", in which the directory linking to the file is not the file's * parent directory. In that case, we know the file must be a directory ("." and ".." can only * link to directories), so we can just get the entry in the directory's parent directory that * links to it. So, for example, if we have a directory "foo" that contains a directory "bar" and * we find an entry [bar -> "." -> bar], we instead return the entry for bar in its parent, * [foo -> "bar" -> bar]. */ @Nullable private DirectoryEntry getRealEntry(DirectoryEntry entry) { Name name = entry.name(); if (name.equals(Name.SELF) || name.equals(Name.PARENT)) { Directory dir = toDirectory(entry.file()); assert dir != null; return dir.entryInParent(); } else { return entry; } } @Nullable private Directory toDirectory(@Nullable File file) { return file == null || !file.isDirectory() ? null : (Directory) file; } private static boolean isEmpty(ImmutableList names) { // the empty path (created by FileSystem.getPath("")), has no root and a single name, "" return names.isEmpty() || names.size() == 1 && names.get(0).toString().isEmpty(); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/GlobToRegex.java000066400000000000000000000271741265745405500257350ustar00rootroot00000000000000/* * 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.checkNotNull; import java.util.ArrayDeque; import java.util.Deque; import java.util.regex.PatternSyntaxException; /** * Translates globs to regex patterns. * * @author Colin Decker */ final class GlobToRegex { /** * Converts the given glob to a regular expression pattern. The given separators determine what * characters the resulting expression breaks on for glob expressions such as * which should not * cross directory boundaries. * *

Basic conversions (assuming / as only separator): * *

{@code
   * ?        = [^/]
   * *        = [^/]*
   * **       = .*
   * [a-z]    = [[^/]&&[a-z]]
   * [!a-z]   = [[^/]&&[^a-z]]
   * {a,b,c}  = (a|b|c)
   * }
*/ public static String toRegex(String glob, String separators) { return new GlobToRegex(glob, separators).convert(); } private static final InternalCharMatcher REGEX_RESERVED = InternalCharMatcher.anyOf("^$.?+*\\[]{}()"); private final String glob; private final String separators; private final InternalCharMatcher separatorMatcher; private final StringBuilder builder = new StringBuilder(); private final Deque states = new ArrayDeque<>(); private int index; private GlobToRegex(String glob, String separators) { this.glob = checkNotNull(glob); this.separators = separators; this.separatorMatcher = InternalCharMatcher.anyOf(separators); } /** * Converts the glob to a regex one character at a time. A state stack (states) is maintained, * with the state at the top of the stack being the current state at any given time. The current * state is always used to process the next character. When a state processes a character, it may * pop the current state or push a new state as the current state. The resulting regex is written * to {@code builder}. */ private String convert() { pushState(NORMAL); for (index = 0; index < glob.length(); index++) { currentState().process(this, glob.charAt(index)); } currentState().finish(this); return builder.toString(); } /** * Enters the given state. The current state becomes the previous state. */ private void pushState(State state) { states.push(state); } /** * Returns to the previous state. */ private void popState() { states.pop(); } /** * Returns the current state. */ private State currentState() { return states.peek(); } /** * Throws a {@link PatternSyntaxException}. */ private PatternSyntaxException syntaxError(String desc) { throw new PatternSyntaxException(desc, glob, index); } /** * Appends the given character as-is to the regex. */ private void appendExact(char c) { builder.append(c); } /** * Appends the regex form of the given normal character or separator from the glob. */ private void append(char c) { if (separatorMatcher.matches(c)) { appendSeparator(); } else { appendNormal(c); } } /** * Appends the regex form of the given normal character from the glob. */ private void appendNormal(char c) { if (REGEX_RESERVED.matches(c)) { builder.append('\\'); } builder.append(c); } /** * Appends the regex form matching the separators for the path type. */ private void appendSeparator() { if (separators.length() == 1) { appendNormal(separators.charAt(0)); } else { builder.append('['); for (int i = 0; i < separators.length(); i++) { appendInBracket(separators.charAt(i)); } builder.append("]"); } } /** * Appends the regex form that matches anything except the separators for the path type. */ private void appendNonSeparator() { builder.append("[^"); for (int i = 0; i < separators.length(); i++) { appendInBracket(separators.charAt(i)); } builder.append(']'); } /** * Appends the regex form of the glob ? character. */ private void appendQuestionMark() { appendNonSeparator(); } /** * Appends the regex form of the glob * character. */ private void appendStar() { appendNonSeparator(); builder.append('*'); } /** * Appends the regex form of the glob ** pattern. */ private void appendStarStar() { builder.append(".*"); } /** * Appends the regex form of the start of a glob [] section. */ private void appendBracketStart() { builder.append('['); appendNonSeparator(); builder.append("&&["); } /** * Appends the regex form of the end of a glob [] section. */ private void appendBracketEnd() { builder.append("]]"); } /** * Appends the regex form of the given character within a glob [] section. */ private void appendInBracket(char c) { // escape \ in regex character class if (c == '\\') { builder.append('\\'); } builder.append(c); } /** * Appends the regex form of the start of a glob {} section. */ private void appendCurlyBraceStart() { builder.append('('); } /** * Appends the regex form of the separator (,) within a glob {} section. */ private void appendSubpatternSeparator() { builder.append('|'); } /** * Appends the regex form of the end of a glob {} section. */ private void appendCurlyBraceEnd() { builder.append(')'); } /** * Converter state. */ private abstract static class State { /** * Process the next character with the current state, transitioning the converter to a new * state if necessary. */ abstract void process(GlobToRegex converter, char c); /** * Called after all characters have been read. */ void finish(GlobToRegex converter) {} } /** * Normal state. */ private static final State NORMAL = new State() { @Override void process(GlobToRegex converter, char c) { switch (c) { case '?': converter.appendQuestionMark(); return; case '[': converter.appendBracketStart(); converter.pushState(BRACKET_FIRST_CHAR); return; case '{': converter.appendCurlyBraceStart(); converter.pushState(CURLY_BRACE); return; case '*': converter.pushState(STAR); return; case '\\': converter.pushState(ESCAPE); return; default: converter.append(c); } } @Override public String toString() { return "NORMAL"; } }; /** * State following the reading of a single \. */ private static final State ESCAPE = new State() { @Override void process(GlobToRegex converter, char c) { converter.append(c); converter.popState(); } @Override void finish(GlobToRegex converter) { throw converter.syntaxError("Hanging escape (\\) at end of pattern"); } @Override public String toString() { return "ESCAPE"; } }; /** * State following the reading of a single *. */ private static final State STAR = new State() { @Override void process(GlobToRegex converter, char c) { if (c == '*') { converter.appendStarStar(); converter.popState(); } else { converter.appendStar(); converter.popState(); converter.currentState().process(converter, c); } } @Override void finish(GlobToRegex converter) { converter.appendStar(); } @Override public String toString() { return "STAR"; } }; /** * State immediately following the reading of a [. */ private static final State BRACKET_FIRST_CHAR = new State() { @Override void process(GlobToRegex converter, char c) { if (c == ']') { // A glob like "[]]" or "[]q]" is apparently fine in Unix (when used with ls for example) // but doesn't work for the default java.nio.file implementations. In the cases of "[]]" it // produces: // java.util.regex.PatternSyntaxException: Unclosed character class near index 13 // ^[[^/]&&[]]\]$ // ^ // The error here is slightly different, but trying to make this work would require some // kind of lookahead and break the simplicity of char-by-char conversion here. Also, if // someone wants to include a ']' inside a character class, they should escape it. throw converter.syntaxError("Empty []"); } if (c == '!') { converter.appendExact('^'); } else if (c == '-') { converter.appendExact(c); } else { converter.appendInBracket(c); } converter.popState(); converter.pushState(BRACKET); } @Override void finish(GlobToRegex converter) { throw converter.syntaxError("Unclosed ["); } @Override public String toString() { return "BRACKET_FIRST_CHAR"; } }; /** * State inside [brackets], but not at the first character inside the brackets. */ private static final State BRACKET = new State() { @Override void process(GlobToRegex converter, char c) { if (c == ']') { converter.appendBracketEnd(); converter.popState(); } else { converter.appendInBracket(c); } } @Override void finish(GlobToRegex converter) { throw converter.syntaxError("Unclosed ["); } @Override public String toString() { return "BRACKET"; } }; /** * State inside {curly braces}. */ private static final State CURLY_BRACE = new State() { @Override void process(GlobToRegex converter, char c) { switch (c) { case '?': converter.appendQuestionMark(); break; case '[': converter.appendBracketStart(); converter.pushState(BRACKET_FIRST_CHAR); break; case '{': throw converter.syntaxError("{ not allowed in subpattern group"); case '*': converter.pushState(STAR); break; case '\\': converter.pushState(ESCAPE); break; case '}': converter.appendCurlyBraceEnd(); converter.popState(); break; case ',': converter.appendSubpatternSeparator(); break; default: converter.append(c); } } @Override void finish(GlobToRegex converter) { throw converter.syntaxError("Unclosed {"); } @Override public String toString() { return "CURLY_BRACE"; } }; } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/Handler.java000066400000000000000000000055161265745405500251250ustar00rootroot00000000000000/* * Copyright 2015 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 java.io.IOException; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; /** * {@link URLStreamHandler} implementation for jimfs. Named {@code Handler} so that the class can * be found by Java as described in the documentation for * {@link URL#URL(String, String, int, String) URL}. * *

This class is only public because it is necessary for Java to find it. It is not intended * to be used directly. * * @author Colin Decker * @since 1.1 */ public final class Handler extends URLStreamHandler { private static final String JAVA_PROTOCOL_HANDLER_PACKAGES = "java.protocol.handler.pkgs"; /** * Registers this handler by adding the package {@code com.google.common} to the system property * {@code "java.protocol.handler.pkgs"}. Java will then look for this class in the {@code jimfs} * (the name of the protocol) package of {@code com.google.common}. * * @throws SecurityException if the system property that needs to be set to register this handler * can't be read or written. */ static void register() { register(Handler.class); } /** * Generic method that would allow registration of any properly placed {@code Handler} class. */ static void register(Class handlerClass) { checkArgument("Handler".equals(handlerClass.getSimpleName())); String pkg = handlerClass.getPackage().getName(); int lastDot = pkg.lastIndexOf('.'); checkArgument(lastDot > 0, "package for Handler (%s) must have a parent package", pkg); String parentPackage = pkg.substring(0, lastDot); String packages = System.getProperty(JAVA_PROTOCOL_HANDLER_PACKAGES); if (packages == null) { packages = parentPackage; } else { packages += "|" + parentPackage; } System.setProperty(JAVA_PROTOCOL_HANDLER_PACKAGES, packages); } /** * @deprecated Not intended to be called directly; this class is only for use by Java itself. */ @Deprecated public Handler() {} // a public, no-arg constructor is required @Override protected URLConnection openConnection(URL url) throws IOException { return new PathURLConnection(url); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/HeapDisk.java000066400000000000000000000126361265745405500252410ustar00rootroot00000000000000/* * 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 com.google.common.annotations.VisibleForTesting; import com.google.common.math.LongMath; import java.io.IOException; import java.math.RoundingMode; /** * A resizable pseudo-disk acting as a shared space for storing file data. A disk allocates fixed * size blocks of bytes to files as needed and may cache blocks that have been freed for reuse. A * memory disk has a fixed maximum number of blocks it will allocate at a time (which sets the * total "size" of the disk) and a maximum number of unused blocks it will cache for reuse at a * time (which sets the minimum amount of space the disk will use once * * @author Colin Decker */ final class HeapDisk { /** Fixed size of each block for this disk. */ private final int blockSize; /** Maximum total number of blocks that the disk may contain at any time. */ private final int maxBlockCount; /** Maximum total number of unused blocks that may be cached for reuse at any time. */ private final int maxCachedBlockCount; /** * Cache of free blocks to be allocated to files. While this is stored as a file, it isn't used * like a normal file: only the methods for accessing its blocks are used. */ @VisibleForTesting final RegularFile blockCache; /** The current total number of blocks that are currently allocated to files. */ private int allocatedBlockCount; /** * Creates a new disk using settings from the given configuration. */ public HeapDisk(Configuration config) { this.blockSize = config.blockSize; this.maxBlockCount = toBlockCount(config.maxSize, blockSize); this.maxCachedBlockCount = config.maxCacheSize == -1 ? maxBlockCount : toBlockCount(config.maxCacheSize, blockSize); this.blockCache = createBlockCache(maxCachedBlockCount); } /** Returns the nearest multiple of {@code blockSize} that is <= {@code size}. */ private static int toBlockCount(long size, int blockSize) { return (int) LongMath.divide(size, blockSize, RoundingMode.FLOOR); } /** * Creates a new disk with the given {@code blockSize}, {@code maxBlockCount} and * {@code maxCachedBlockCount}. */ public HeapDisk(int blockSize, int maxBlockCount, int maxCachedBlockCount) { checkArgument(blockSize > 0, "blockSize (%s) must be positive", blockSize); checkArgument(maxBlockCount > 0, "maxBlockCount (%s) must be positive", maxBlockCount); checkArgument( maxCachedBlockCount >= 0, "maxCachedBlockCount must be non-negative", maxCachedBlockCount); this.blockSize = blockSize; this.maxBlockCount = maxBlockCount; this.maxCachedBlockCount = maxCachedBlockCount; this.blockCache = createBlockCache(maxCachedBlockCount); } private RegularFile createBlockCache(int maxCachedBlockCount) { return new RegularFile(-1, this, new byte[Math.min(maxCachedBlockCount, 8192)][], 0, 0); } /** * Returns the size of blocks created by this disk. */ public int blockSize() { return blockSize; } /** * Returns the total size of this disk. This is the maximum size of the disk and does not reflect * the amount of data currently allocated or cached. */ public synchronized long getTotalSpace() { return maxBlockCount * (long) blockSize; } /** * Returns the current number of unallocated bytes on this disk. This is the maximum number of * additional bytes that could be allocated and does not reflect the number of bytes currently * actually cached in the disk. */ public synchronized long getUnallocatedSpace() { return (maxBlockCount - allocatedBlockCount) * (long) blockSize; } /** * Allocates the given number of blocks and adds them to the given file. */ public synchronized void allocate(RegularFile file, int count) throws IOException { int newAllocatedBlockCount = allocatedBlockCount + count; if (newAllocatedBlockCount > maxBlockCount) { throw new IOException("out of disk space"); } int newBlocksNeeded = Math.max(count - blockCache.blockCount(), 0); for (int i = 0; i < newBlocksNeeded; i++) { file.addBlock(new byte[blockSize]); } if (newBlocksNeeded != count) { blockCache.transferBlocksTo(file, count - newBlocksNeeded); } allocatedBlockCount = newAllocatedBlockCount; } /** * Frees all blocks in the given file. */ public void free(RegularFile file) { free(file, file.blockCount()); } /** * Frees the last {@code count} blocks from the given file. */ public synchronized void free(RegularFile file, int count) { int remainingCacheSpace = maxCachedBlockCount - blockCache.blockCount(); if (remainingCacheSpace > 0) { file.copyBlocksTo(blockCache, Math.min(count, remainingCacheSpace)); } file.truncateBlocks(file.blockCount() - count); allocatedBlockCount -= count; } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/InternalCharMatcher.java000066400000000000000000000021651265745405500274230ustar00rootroot00000000000000/* * 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 java.util.Arrays; /** * Simple replacement for the real CharMatcher until it's out of @Beta. * * @author Colin Decker */ final class InternalCharMatcher { public static InternalCharMatcher anyOf(String chars) { return new InternalCharMatcher(chars); } private final char[] chars; private InternalCharMatcher(String chars) { this.chars = chars.toCharArray(); Arrays.sort(this.chars); } public boolean matches(char c) { return Arrays.binarySearch(chars, c) >= 0; } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/Jimfs.java000066400000000000000000000151561265745405500246210ustar00rootroot00000000000000/* * 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.jimfs.SystemJimfsFileSystemProvider.FILE_SYSTEM_KEY; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.util.UUID; /** * Static factory methods for creating new Jimfs file systems. File systems may either be created * with a basic configuration matching the current operating system or by providing a specific * {@link Configuration}. Basic {@linkplain Configuration#unix() UNIX}, * {@linkplain Configuration#osX() Mac OS X} and {@linkplain Configuration#windows() Windows} * configurations are provided. * *

Examples: * *

 *   // A file system with a configuration similar to the current OS
 *   FileSystem fileSystem = Jimfs.newFileSystem();
 *
 *   // A file system with paths and behavior generally matching that of Windows
 *   FileSystem windows = Jimfs.newFileSystem(Configuration.windows());  
* *

Additionally, various behavior of the file system can be customized by creating a custom * {@link Configuration}. A modified version of one of the existing default configurations can be * created using {@link Configuration#toBuilder()} or a new configuration can be created from * scratch with {@link Configuration#builder(PathType)}. See {@link Configuration.Builder} for what * can be configured. * *

Examples: * *

 *   // Modify the default UNIX configuration
 *   FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix()
 *       .toBuilder()
 *       .setAttributeViews("basic", "owner", "posix", "unix")
 *       .setWorkingDirectory("/home/user")
 *       .setBlockSize(4096)
 *       .build());
 *
 *   // Create a custom configuration
 *   Configuration config = Configuration.builder(PathType.windows())
 *       .setRoots("C:\\", "D:\\", "E:\\")
 *       // ...
 *       .build();  
* * @author Colin Decker */ public final class Jimfs { /** * The URI scheme for the Jimfs file system ("jimfs"). */ public static final String URI_SCHEME = "jimfs"; private Jimfs() {} /** * Creates a new in-memory file system with a * {@linkplain Configuration#forCurrentPlatform() default configuration} appropriate to the * current operating system. * *

More specifically, if the operating system is Windows, {@link Configuration#windows()} is * used; if the operating system is Mac OS X, {@link Configuration#osX()} is used; otherwise, * {@link Configuration#unix()} is used. */ public static FileSystem newFileSystem() { return newFileSystem(newRandomFileSystemName()); } /** * Creates a new in-memory file system with a * {@linkplain Configuration#forCurrentPlatform() default configuration} appropriate to the * current operating system. * *

More specifically, if the operating system is Windows, {@link Configuration#windows()} is * used; if the operating system is Mac OS X, {@link Configuration#osX()} is used; otherwise, * {@link Configuration#unix()} is used. * *

The returned file system uses the given name as the host part of its URI and the URIs of * paths in the file system. For example, given the name {@code my-file-system}, the file * system's URI will be {@code jimfs://my-file-system} and the URI of the path {@code /foo/bar} * will be {@code jimfs://my-file-system/foo/bar}. */ public static FileSystem newFileSystem(String name) { return newFileSystem(name, Configuration.forCurrentPlatform()); } /** * Creates a new in-memory file system with the given configuration. */ public static FileSystem newFileSystem(Configuration configuration) { return newFileSystem(newRandomFileSystemName(), configuration); } /** * Creates a new in-memory file system with the given configuration. * *

The returned file system uses the given name as the host part of its URI and the URIs of * paths in the file system. For example, given the name {@code my-file-system}, the file * system's URI will be {@code jimfs://my-file-system} and the URI of the path {@code /foo/bar} * will be {@code jimfs://my-file-system/foo/bar}. */ public static FileSystem newFileSystem(String name, Configuration configuration) { try { URI uri = new URI(URI_SCHEME, name, null, null); return newFileSystem(uri, configuration); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } @VisibleForTesting static FileSystem newFileSystem(URI uri, Configuration config) { checkArgument( URI_SCHEME.equals(uri.getScheme()), "uri (%s) must have scheme %s", uri, URI_SCHEME); try { // Create the FileSystem. It uses JimfsFileSystemProvider as its provider, as that is // the provider that actually implements the operations needed for Files methods to work. JimfsFileSystem fileSystem = JimfsFileSystems.newFileSystem(JimfsFileSystemProvider.instance(), uri, config); // Now, call FileSystems.newFileSystem, passing it the FileSystem we just created. This // allows the system-loaded SystemJimfsFileSystemProvider instance to cache the FileSystem // so that methods like Paths.get(URI) work. // We do it in this awkward way to avoid issues when the classes in the API (this class // and Configuration, for example) are loaded by a different classloader than the one that // loads SystemJimfsFileSystemProvider using ServiceLoader. See // https://github.com/google/jimfs/issues/18 for gory details. ImmutableMap env = ImmutableMap.of(FILE_SYSTEM_KEY, fileSystem); FileSystems.newFileSystem(uri, env, SystemJimfsFileSystemProvider.class.getClassLoader()); return fileSystem; } catch (IOException e) { throw new AssertionError(e); } } private static String newRandomFileSystemName() { return UUID.randomUUID().toString(); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/JimfsAsynchronousFileChannel.java000066400000000000000000000154561265745405500313310ustar00rootroot00000000000000/* * 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 com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.ClosedChannelException; import java.nio.channels.CompletionHandler; import java.nio.channels.FileLock; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import javax.annotation.Nullable; /** * {@link AsynchronousFileChannel} implementation that delegates to a {@link JimfsFileChannel}. * * @author Colin Decker */ final class JimfsAsynchronousFileChannel extends AsynchronousFileChannel { private final JimfsFileChannel channel; private final ListeningExecutorService executor; public JimfsAsynchronousFileChannel(JimfsFileChannel channel, ExecutorService executor) { this.channel = checkNotNull(channel); this.executor = MoreExecutors.listeningDecorator(executor); } @Override public long size() throws IOException { return channel.size(); } private void addCallback( ListenableFuture future, CompletionHandler handler, @Nullable A attachment) { future.addListener(new CompletionHandlerCallback<>(future, handler, attachment), executor); } @Override public AsynchronousFileChannel truncate(long size) throws IOException { channel.truncate(size); return this; } @Override public void force(boolean metaData) throws IOException { channel.force(metaData); } @Override public void lock( long position, long size, boolean shared, @Nullable A attachment, CompletionHandler handler) { checkNotNull(handler); addCallback(lock(position, size, shared), handler, attachment); } @Override public ListenableFuture lock( final long position, final long size, final boolean shared) { Util.checkNotNegative(position, "position"); Util.checkNotNegative(size, "size"); if (!isOpen()) { return closedChannelFuture(); } if (shared) { channel.checkReadable(); } else { channel.checkWritable(); } return executor.submit( new Callable() { @Override public FileLock call() throws IOException { return tryLock(position, size, shared); } }); } @Override public FileLock tryLock(long position, long size, boolean shared) throws IOException { Util.checkNotNegative(position, "position"); Util.checkNotNegative(size, "size"); channel.checkOpen(); if (shared) { channel.checkReadable(); } else { channel.checkWritable(); } return new JimfsFileChannel.FakeFileLock(this, position, size, shared); } @Override public void read( ByteBuffer dst, long position, @Nullable A attachment, CompletionHandler handler) { addCallback(read(dst, position), handler, attachment); } @Override public ListenableFuture read(final ByteBuffer dst, final long position) { checkArgument(!dst.isReadOnly(), "dst may not be read-only"); Util.checkNotNegative(position, "position"); if (!isOpen()) { return closedChannelFuture(); } channel.checkReadable(); return executor.submit( new Callable() { @Override public Integer call() throws IOException { return channel.read(dst, position); } }); } @Override public void write( ByteBuffer src, long position, @Nullable A attachment, CompletionHandler handler) { addCallback(write(src, position), handler, attachment); } @Override public ListenableFuture write(final ByteBuffer src, final long position) { Util.checkNotNegative(position, "position"); if (!isOpen()) { return closedChannelFuture(); } channel.checkWritable(); return executor.submit( new Callable() { @Override public Integer call() throws IOException { return channel.write(src, position); } }); } @Override public boolean isOpen() { return channel.isOpen(); } @Override public void close() throws IOException { channel.close(); } /** * Immediate future indicating that the channel is closed. */ private static ListenableFuture closedChannelFuture() { SettableFuture future = SettableFuture.create(); future.setException(new ClosedChannelException()); return future; } /** * Runnable callback that wraps a {@link CompletionHandler} and an attachment. */ private static final class CompletionHandlerCallback implements Runnable { private final ListenableFuture future; private final CompletionHandler completionHandler; @Nullable private final A attachment; private CompletionHandlerCallback( ListenableFuture future, CompletionHandler completionHandler, @Nullable A attachment) { this.future = checkNotNull(future); this.completionHandler = checkNotNull(completionHandler); this.attachment = attachment; } @Override public void run() { R result; try { result = future.get(); } catch (ExecutionException e) { onFailure(e.getCause()); return; } catch (InterruptedException | RuntimeException | Error e) { // get() shouldn't be interrupted since this should only be called when the result is // ready, but just handle it anyway to be sure and to satisfy the compiler onFailure(e); return; } onSuccess(result); } private void onSuccess(R result) { completionHandler.completed(result, attachment); } private void onFailure(Throwable t) { completionHandler.failed(t, attachment); } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/JimfsFileChannel.java000066400000000000000000000457611265745405500267170ustar00rootroot00000000000000/* * 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.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; import static java.nio.file.StandardOpenOption.APPEND; import static java.nio.file.StandardOpenOption.READ; import static java.nio.file.StandardOpenOption.WRITE; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.AsynchronousCloseException; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.FileLockInterruptionException; import java.nio.channels.NonReadableChannelException; import java.nio.channels.NonWritableChannelException; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.file.OpenOption; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; /** * A {@link FileChannel} implementation that reads and writes to a {@link RegularFile} object. The * read and write methods and other methods that read or change the position of the channel are * locked because the {@link ReadableByteChannel} and {@link WritableByteChannel} interfaces specify * that the read and write methods block when another thread is currently doing a read or write * operation. * * @author Colin Decker */ final class JimfsFileChannel extends FileChannel { /** * Set of threads that are currently doing an interruptible blocking operation; that is, doing * something that requires acquiring the file's lock. These threads must be interrupted if the * channel is closed by another thread. */ @GuardedBy("blockingThreads") private final Set blockingThreads = new HashSet(); private final RegularFile file; private final FileSystemState fileSystemState; private final boolean read; private final boolean write; private final boolean append; @GuardedBy("this") private long position; public JimfsFileChannel( RegularFile file, Set options, FileSystemState fileSystemState) { this.file = file; this.fileSystemState = fileSystemState; this.read = options.contains(READ); this.write = options.contains(WRITE); this.append = options.contains(APPEND); fileSystemState.register(this); } /** * Returns an {@link AsynchronousFileChannel} view of this channel using the given executor for * asynchronous operations. */ public AsynchronousFileChannel asAsynchronousFileChannel(ExecutorService executor) { return new JimfsAsynchronousFileChannel(this, executor); } void checkReadable() { if (!read) { throw new NonReadableChannelException(); } } void checkWritable() { if (!write) { throw new NonWritableChannelException(); } } void checkOpen() throws ClosedChannelException { if (!isOpen()) { throw new ClosedChannelException(); } } /** * Begins a blocking operation, making the operation interruptible. Returns {@code true} if the * channel was open and the thread was added as a blocking thread; returns {@code false} if the * channel was closed. */ private boolean beginBlocking() { begin(); synchronized (blockingThreads) { if (isOpen()) { blockingThreads.add(Thread.currentThread()); return true; } return false; } } /** * Ends a blocking operation, throwing an exception if the thread was interrupted while blocking * or if the channel was closed from another thread. */ private void endBlocking(boolean completed) throws AsynchronousCloseException { synchronized (blockingThreads) { blockingThreads.remove(Thread.currentThread()); } end(completed); } @Override public int read(ByteBuffer dst) throws IOException { checkNotNull(dst); checkOpen(); checkReadable(); int read = 0; // will definitely either be assigned or an exception will be thrown synchronized (this) { boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.readLock().lockInterruptibly(); try { read = file.read(position, dst); if (read != -1) { position += read; } file.updateAccessTime(); completed = true; } finally { file.readLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } } return read; } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { checkPositionIndexes(offset, offset + length, dsts.length); List buffers = Arrays.asList(dsts).subList(offset, offset + length); Util.checkNoneNull(buffers); checkOpen(); checkReadable(); long read = 0; // will definitely either be assigned or an exception will be thrown synchronized (this) { boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.readLock().lockInterruptibly(); try { read = file.read(position, buffers); if (read != -1) { position += read; } file.updateAccessTime(); completed = true; } finally { file.readLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } } return read; } @Override public int write(ByteBuffer src) throws IOException { checkNotNull(src); checkOpen(); checkWritable(); int written = 0; // will definitely either be assigned or an exception will be thrown synchronized (this) { boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.writeLock().lockInterruptibly(); try { if (append) { position = file.size(); } written = file.write(position, src); position += written; file.updateModifiedTime(); completed = true; } finally { file.writeLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } } return written; } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { checkPositionIndexes(offset, offset + length, srcs.length); List buffers = Arrays.asList(srcs).subList(offset, offset + length); Util.checkNoneNull(buffers); checkOpen(); checkWritable(); long written = 0; // will definitely either be assigned or an exception will be thrown synchronized (this) { boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.writeLock().lockInterruptibly(); try { if (append) { position = file.size(); } written = file.write(position, buffers); position += written; file.updateModifiedTime(); completed = true; } finally { file.writeLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } } return written; } @Override public long position() throws IOException { checkOpen(); long pos; synchronized (this) { boolean completed = false; try { begin(); // don't call beginBlocking() because this method doesn't block if (!isOpen()) { return 0; // AsynchronousCloseException will be thrown } pos = this.position; completed = true; } finally { end(completed); } } return pos; } @Override public FileChannel position(long newPosition) throws IOException { Util.checkNotNegative(newPosition, "newPosition"); checkOpen(); synchronized (this) { boolean completed = false; try { begin(); // don't call beginBlocking() because this method doesn't block if (!isOpen()) { return this; // AsynchronousCloseException will be thrown } this.position = newPosition; completed = true; } finally { end(completed); } } return this; } @Override public long size() throws IOException { checkOpen(); long size = 0; // will definitely either be assigned or an exception will be thrown boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.readLock().lockInterruptibly(); try { size = file.sizeWithoutLocking(); completed = true; } finally { file.readLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } return size; } @Override public FileChannel truncate(long size) throws IOException { Util.checkNotNegative(size, "size"); checkOpen(); checkWritable(); synchronized (this) { boolean completed = false; try { if (!beginBlocking()) { return this; // AsynchronousCloseException will be thrown } file.writeLock().lockInterruptibly(); try { file.truncate(size); if (position > size) { position = size; } file.updateModifiedTime(); completed = true; } finally { file.writeLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } } return this; } @Override public void force(boolean metaData) throws IOException { checkOpen(); // nothing to do since writes are all direct to the storage // however, we should handle the thread being interrupted anyway boolean completed = false; try { begin(); completed = true; } finally { end(completed); } } @Override public long transferTo(long position, long count, WritableByteChannel target) throws IOException { checkNotNull(target); Util.checkNotNegative(position, "position"); Util.checkNotNegative(count, "count"); checkOpen(); checkReadable(); long transferred = 0; // will definitely either be assigned or an exception will be thrown // no need to synchronize here; this method does not make use of the channel's position boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.readLock().lockInterruptibly(); try { transferred = file.transferTo(position, count, target); file.updateAccessTime(); completed = true; } finally { file.readLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } return transferred; } @Override public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { checkNotNull(src); Util.checkNotNegative(position, "position"); Util.checkNotNegative(count, "count"); checkOpen(); checkWritable(); long transferred = 0; // will definitely either be assigned or an exception will be thrown if (append) { // synchronize because appending does update the channel's position synchronized (this) { boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.writeLock().lockInterruptibly(); try { position = file.sizeWithoutLocking(); transferred = file.transferFrom(src, position, count); this.position = position + transferred; file.updateModifiedTime(); completed = true; } finally { file.writeLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } } } else { // don't synchronize because the channel's position is not involved boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.writeLock().lockInterruptibly(); try { transferred = file.transferFrom(src, position, count); file.updateModifiedTime(); completed = true; } finally { file.writeLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } } return transferred; } @Override public int read(ByteBuffer dst, long position) throws IOException { checkNotNull(dst); Util.checkNotNegative(position, "position"); checkOpen(); checkReadable(); int read = 0; // will definitely either be assigned or an exception will be thrown // no need to synchronize here; this method does not make use of the channel's position boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.readLock().lockInterruptibly(); try { read = file.read(position, dst); file.updateAccessTime(); completed = true; } finally { file.readLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } return read; } @Override public int write(ByteBuffer src, long position) throws IOException { checkNotNull(src); Util.checkNotNegative(position, "position"); checkOpen(); checkWritable(); int written = 0; // will definitely either be assigned or an exception will be thrown if (append) { // synchronize because appending does update the channel's position synchronized (this) { boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.writeLock().lockInterruptibly(); try { position = file.sizeWithoutLocking(); written = file.write(position, src); this.position = position + written; file.updateModifiedTime(); completed = true; } finally { file.writeLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } } } else { // don't synchronize because the channel's position is not involved boolean completed = false; try { if (!beginBlocking()) { return 0; // AsynchronousCloseException will be thrown } file.writeLock().lockInterruptibly(); try { written = file.write(position, src); file.updateModifiedTime(); completed = true; } finally { file.writeLock().unlock(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endBlocking(completed); } } return written; } @Override public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { // would like this to pretend to work, but can't create an implementation of MappedByteBuffer // well, a direct buffer could be cast to MappedByteBuffer, but it couldn't work in general throw new UnsupportedOperationException(); } @Override public FileLock lock(long position, long size, boolean shared) throws IOException { checkLockArguments(position, size, shared); // lock is interruptible boolean completed = false; try { begin(); completed = true; return new FakeFileLock(this, position, size, shared); } finally { try { end(completed); } catch (ClosedByInterruptException e) { throw new FileLockInterruptionException(); } } } @Override public FileLock tryLock(long position, long size, boolean shared) throws IOException { checkLockArguments(position, size, shared); // tryLock is not interruptible return new FakeFileLock(this, position, size, shared); } private void checkLockArguments(long position, long size, boolean shared) throws IOException { Util.checkNotNegative(position, "position"); Util.checkNotNegative(size, "size"); checkOpen(); if (shared) { checkReadable(); } else { checkWritable(); } } @Override protected void implCloseChannel() { // interrupt the current blocking threads, if any, causing them to throw // ClosedByInterruptException try { synchronized (blockingThreads) { for (Thread thread : blockingThreads) { thread.interrupt(); } } } finally { fileSystemState.unregister(this); file.closed(); } } /** * A file lock that does nothing, since only one JVM process has access to this file system. */ static final class FakeFileLock extends FileLock { private final AtomicBoolean valid = new AtomicBoolean(true); public FakeFileLock(FileChannel channel, long position, long size, boolean shared) { super(channel, position, size, shared); } public FakeFileLock(AsynchronousFileChannel channel, long position, long size, boolean shared) { super(channel, position, size, shared); } @Override public boolean isValid() { return valid.get(); } @Override public void release() throws IOException { valid.set(false); } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/JimfsFileStore.java000066400000000000000000000206321265745405500264310ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import java.io.IOException; import java.nio.file.FileStore; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileStoreAttributeView; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.annotation.Nullable; /** * {@link FileStore} implementation which provides methods for file creation, lookup and attribute * handling. * *

Most of these methods are actually implemented in another class: {@link FileTree} for lookup, * {@link FileFactory} for creating and copying files and {@link AttributeService} for attribute * handling. This class merely provides a single API through which to access the functionality of * those classes. * * @author Colin Decker */ final class JimfsFileStore extends FileStore { private final FileTree tree; private final HeapDisk disk; private final AttributeService attributes; private final FileFactory factory; private final ImmutableSet supportedFeatures; private final FileSystemState state; private final Lock readLock; private final Lock writeLock; public JimfsFileStore( FileTree tree, FileFactory factory, HeapDisk disk, AttributeService attributes, ImmutableSet supportedFeatures, FileSystemState state) { this.tree = checkNotNull(tree); this.factory = checkNotNull(factory); this.disk = checkNotNull(disk); this.attributes = checkNotNull(attributes); this.supportedFeatures = checkNotNull(supportedFeatures); this.state = checkNotNull(state); ReadWriteLock lock = new ReentrantReadWriteLock(); this.readLock = lock.readLock(); this.writeLock = lock.writeLock(); } // internal use methods /** * Returns the file system state object. */ FileSystemState state() { return state; } /** * Returns the read lock for this store. */ Lock readLock() { return readLock; } /** * Returns the write lock for this store. */ Lock writeLock() { return writeLock; } /** * Returns the names of the root directories in this store. */ ImmutableSortedSet getRootDirectoryNames() { state.checkOpen(); return tree.getRootDirectoryNames(); } /** * Returns the root directory with the given name or {@code null} if no such directory exists. */ @Nullable Directory getRoot(Name name) { DirectoryEntry entry = tree.getRoot(name); return entry == null ? null : (Directory) entry.file(); } /** * Returns whether or not the given feature is supported by this file store. */ boolean supportsFeature(Feature feature) { return supportedFeatures.contains(feature); } /** * Looks up the file at the given path using the given link options. If the path is relative, the * lookup is relative to the given working directory. * * @throws NoSuchFileException if an element of the path other than the final element does not * resolve to a directory or symbolic link (e.g. it doesn't exist or is a regular file) * @throws IOException if a symbolic link cycle is detected or the depth of symbolic link * recursion otherwise exceeds a threshold */ DirectoryEntry lookUp(File workingDirectory, JimfsPath path, Set options) throws IOException { state.checkOpen(); return tree.lookUp(workingDirectory, path, options); } /** * Returns a supplier that creates a new regular file. */ Supplier regularFileCreator() { state.checkOpen(); return factory.regularFileCreator(); } /** * Returns a supplier that creates a new directory. */ Supplier directoryCreator() { state.checkOpen(); return factory.directoryCreator(); } /** * Returns a supplier that creates a new symbolic link with the given target. */ Supplier symbolicLinkCreator(JimfsPath target) { state.checkOpen(); return factory.symbolicLinkCreator(target); } /** * Creates a copy of the given file, copying its attributes as well according to the given * {@code attributeCopyOption}. */ File copyWithoutContent(File file, AttributeCopyOption attributeCopyOption) throws IOException { File copy = factory.copyWithoutContent(file); setInitialAttributes(copy); attributes.copyAttributes(file, copy, attributeCopyOption); return copy; } /** * Sets initial attributes on the given file. Sets default attributes first, then attempts to set * the given user-provided attributes. */ void setInitialAttributes(File file, FileAttribute... attrs) { state.checkOpen(); attributes.setInitialAttributes(file, attrs); } /** * Returns an attribute view of the given type for the given file lookup callback, or * {@code null} if the view type is not supported. */ @Nullable V getFileAttributeView(FileLookup lookup, Class type) { state.checkOpen(); return attributes.getFileAttributeView(lookup, type); } /** * Returns a map containing the attributes described by the given string mapped to their values. */ ImmutableMap readAttributes(File file, String attributes) { state.checkOpen(); return this.attributes.readAttributes(file, attributes); } /** * Returns attributes of the given file as an object of the given type. * * @throws UnsupportedOperationException if the given attributes type is not supported */ A readAttributes(File file, Class type) { state.checkOpen(); return attributes.readAttributes(file, type); } /** * Sets the given attribute to the given value for the given file. */ void setAttribute(File file, String attribute, Object value) { state.checkOpen(); // TODO(cgdecker): Change attribute stuff to avoid the sad boolean parameter attributes.setAttribute(file, attribute, value, false); } /** * Returns the file attribute views supported by this store. */ ImmutableSet supportedFileAttributeViews() { state.checkOpen(); return attributes.supportedFileAttributeViews(); } // methods implementing the FileStore API @Override public String name() { return "jimfs"; } @Override public String type() { return "jimfs"; } @Override public boolean isReadOnly() { return false; } @Override public long getTotalSpace() throws IOException { state.checkOpen(); return disk.getTotalSpace(); } @Override public long getUsableSpace() throws IOException { state.checkOpen(); return getUnallocatedSpace(); } @Override public long getUnallocatedSpace() throws IOException { state.checkOpen(); return disk.getUnallocatedSpace(); } @Override public boolean supportsFileAttributeView(Class type) { state.checkOpen(); return attributes.supportsFileAttributeView(type); } @Override public boolean supportsFileAttributeView(String name) { state.checkOpen(); return attributes.supportedFileAttributeViews().contains(name); } @Override public V getFileStoreAttributeView(Class type) { state.checkOpen(); return null; // no supported views } @Override public Object getAttribute(String attribute) throws IOException { throw new UnsupportedOperationException(); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystem.java000066400000000000000000000327561265745405500266330ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.Closeable; import java.io.IOException; import java.net.URI; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.WatchService; import java.nio.file.attribute.UserPrincipalLookupService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.annotation.Nullable; /** * {@link FileSystem} implementation for Jimfs. Most behavior for the file system is implemented * by its {@linkplain #getDefaultView() default file system view}. * *

Overview of file system design

* * {@link com.google.common.jimfs.JimfsFileSystem JimfsFileSystem} instances are created by * {@link com.google.common.jimfs.JimfsFileSystems JimfsFileSystems} using a user-provided * {@link com.google.common.jimfs.Configuration Configuration}. The configuration is used to create * the various classes that implement the file system with the correct settings and to create the * file system root directories and working directory. The file system is then used to create the * {@code Path} objects that all file system operations use. * *

Once created, the primary entry points to the file system are * {@link com.google.common.jimfs.JimfsFileSystemProvider JimfsFileSystemProvider}, which handles * calls to methods in {@link java.nio.file.Files}, and * {@link com.google.common.jimfs.JimfsSecureDirectoryStream JimfsSecureDirectoryStream}, which * provides methods that are similar to those of the file system provider but which treat relative * paths as relative to the stream's directory rather than the file system's working directory. * *

The implementation of the methods on both of those classes is handled by the * {@link com.google.common.jimfs.FileSystemView FileSystemView} class, which acts as a view of * the file system with a specific working directory. The file system provider uses the file * system's default view, while each secure directory stream uses a view specific to that stream. * *

File system views make use of the file system's singleton * {@link com.google.common.jimfs.JimfsFileStore JimfsFileStore} which handles file creation, * storage and attributes. The file store delegates to several other classes to handle each of * these: * *

    *
  • {@link com.google.common.jimfs.FileFactory FileFactory} handles creation of new file * objects.
  • *
  • {@link com.google.common.jimfs.HeapDisk HeapDisk} handles allocation of blocks to * {@link RegularFile RegularFile} instances.
  • *
  • {@link com.google.common.jimfs.FileTree FileTree} stores the root of the file hierarchy * and handles file lookup.
  • *
  • {@link com.google.common.jimfs.AttributeService AttributeService} handles file * attributes, using a set of * {@link com.google.common.jimfs.AttributeProvider AttributeProvider} implementations to * handle each supported file attribute view.
  • *
* *

Paths

* * The implementation of {@link java.nio.file.Path} for the file system is * {@link com.google.common.jimfs.JimfsPath JimfsPath}. Paths are created by a * {@link com.google.common.jimfs.PathService PathService} with help from the file system's * configured {@link com.google.common.jimfs.PathType PathType}. * *

Paths are made up of {@link com.google.common.jimfs.Name Name} objects, which also serve as * the file names in directories. A name has two forms: * *

    *
  • The display form is used in {@code Path} for {@code toString()}. It is also used for * determining the equality and sort order of {@code Path} objects for most file systems.
  • *
  • The canonical form is used for equality of two {@code Name} objects. This affects * the notion of name equality in the file system itself for file lookup. A file system may be * configured to use the canonical form of the name for path equality (a Windows-like file system * configuration does this, as the real Windows file system implementation uses case-insensitive * equality for its path objects.
  • *
* *

The canonical form of a name is created by applying a series of * {@linkplain PathNormalization normalizations} to the original string. These * normalization may be either a Unicode normalization (e.g. NFD) or case folding normalization for * case-insensitivity. Normalizations may also be applied to the display form of a name, but this * is currently only done for a Mac OS X type configuration. * *

Files

* * All files in the file system are an instance of {@link com.google.common.jimfs.File File}. A * file object contains both the file's attributes and content. * *

There are three types of files: * *

    *
  • {@link Directory Directory} - contains a table linking file names * to {@linkplain com.google.common.jimfs.DirectoryEntry directory entries}. *
  • {@link RegularFile RegularFile} - an in-memory store for raw bytes. *
  • {@link com.google.common.jimfs.SymbolicLink SymbolicLink} - contains a path. *
* *

{@link com.google.common.jimfs.JimfsFileChannel JimfsFileChannel}, * {@link com.google.common.jimfs.JimfsInputStream JimfsInputStream} and * {@link com.google.common.jimfs.JimfsOutputStream JimfsOutputStream} implement the standard * channel/stream APIs for regular files. * *

{@link com.google.common.jimfs.JimfsSecureDirectoryStream JimfsSecureDirectoryStream} handles * reading the entries of a directory. The secure directory stream additionally contains a * {@code FileSystemView} with its directory as the working directory, allowing for operations * relative to the actual directory file rather than just the path to the file. This allows the * operations to continue to work as expected even if the directory is moved. * *

A directory can be watched for changes using the {@link java.nio.file.WatchService} * implementation, {@link com.google.common.jimfs.PollingWatchService PollingWatchService}. * *

Regular files

* * {@link RegularFile RegularFile} makes use of a singleton * {@link com.google.common.jimfs.HeapDisk HeapDisk}. A disk is a resizable factory and cache for * fixed size blocks of memory. These blocks are allocated to files as needed and returned to the * disk when a file is deleted or truncated. When cached free blocks are available, those blocks * are allocated to files first. If more blocks are needed, they are created. * *

Linking

* * When a file is mapped to a file name in a directory table, it is linked. Each type of * file has different rules governing how it is linked. * *
    *
  • Directory - A directory has two or more links to it. The first is the link from * its parent directory to it. This link is the name of the directory. The second is the * self link (".") which links the directory to itself. The directory may also have any * number of additional parent links ("..") from child directories back to it.
  • *
  • Regular file - A regular file has one link from its parent directory by default. However, * regular files are also allowed to have any number of additional user-created hard links, from * the same directory with different names and/or from other directories with any names.
  • *
  • Symbolic link - A symbolic link can only have one link, from its parent directory.
  • *
* *

Thread safety

* * All file system operations should be safe in a multithreaded environment. The file hierarchy * itself is protected by a file system level read-write lock. This ensures safety of all * modifications to directory tables as well as atomicity of operations like file moves. Regular * files are each protected by a read-write lock which is obtained for each read or write operation. * File attributes are protected by synchronization on the file object itself. * * @author Colin Decker */ final class JimfsFileSystem extends FileSystem { private final JimfsFileSystemProvider provider; private final URI uri; private final JimfsFileStore fileStore; private final PathService pathService; private final UserPrincipalLookupService userLookupService = new UserLookupService(true); private final FileSystemView defaultView; private final WatchServiceConfiguration watchServiceConfig; JimfsFileSystem( JimfsFileSystemProvider provider, URI uri, JimfsFileStore fileStore, PathService pathService, FileSystemView defaultView, WatchServiceConfiguration watchServiceConfig) { this.provider = checkNotNull(provider); this.uri = checkNotNull(uri); this.fileStore = checkNotNull(fileStore); this.pathService = checkNotNull(pathService); this.defaultView = checkNotNull(defaultView); this.watchServiceConfig = checkNotNull(watchServiceConfig); } @Override public JimfsFileSystemProvider provider() { return provider; } /** * Returns the URI for this file system. */ public URI getUri() { return uri; } /** * Returns the default view for this file system. */ public FileSystemView getDefaultView() { return defaultView; } @Override public String getSeparator() { return pathService.getSeparator(); } @SuppressWarnings("unchecked") // safe cast of immutable set @Override public ImmutableSortedSet getRootDirectories() { ImmutableSortedSet.Builder builder = ImmutableSortedSet.orderedBy(pathService); for (Name name : fileStore.getRootDirectoryNames()) { builder.add(pathService.createRoot(name)); } return (ImmutableSortedSet) (ImmutableSortedSet) builder.build(); } /** * Returns the working directory path for this file system. */ public JimfsPath getWorkingDirectory() { return defaultView.getWorkingDirectoryPath(); } /** * Returns the path service for this file system. */ @VisibleForTesting PathService getPathService() { return pathService; } /** * Returns the file store for this file system. */ public JimfsFileStore getFileStore() { return fileStore; } @Override public ImmutableSet getFileStores() { fileStore.state().checkOpen(); return ImmutableSet.of(fileStore); } @Override public ImmutableSet supportedFileAttributeViews() { return fileStore.supportedFileAttributeViews(); } @Override public JimfsPath getPath(String first, String... more) { fileStore.state().checkOpen(); return pathService.parsePath(first, more); } /** * Gets the URI of the given path in this file system. */ public URI toUri(JimfsPath path) { fileStore.state().checkOpen(); return pathService.toUri(uri, path.toAbsolutePath()); } /** * Converts the given URI into a path in this file system. */ public JimfsPath toPath(URI uri) { fileStore.state().checkOpen(); return pathService.fromUri(uri); } @Override public PathMatcher getPathMatcher(String syntaxAndPattern) { fileStore.state().checkOpen(); return pathService.createPathMatcher(syntaxAndPattern); } @Override public UserPrincipalLookupService getUserPrincipalLookupService() { fileStore.state().checkOpen(); return userLookupService; } @Override public WatchService newWatchService() throws IOException { return watchServiceConfig.newWatchService(defaultView, pathService); } @Nullable private ExecutorService defaultThreadPool; /** * Returns a default thread pool to use for asynchronous file channels when users do not provide * an executor themselves. (This is required by the spec of newAsynchronousFileChannel in * FileSystemProvider.) */ public synchronized ExecutorService getDefaultThreadPool() { if (defaultThreadPool == null) { defaultThreadPool = Executors.newCachedThreadPool( new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("JimfsFileSystem-" + uri.getHost() + "-defaultThreadPool-%s") .build()); // ensure thread pool is closed when file system is closed fileStore .state() .register( new Closeable() { @Override public void close() { defaultThreadPool.shutdown(); } }); } return defaultThreadPool; } /** * Returns {@code false}; currently, cannot create a read-only file system. * * @return {@code false}, always */ @Override public boolean isReadOnly() { return false; } @Override public boolean isOpen() { return fileStore.state().isOpen(); } @Override public void close() throws IOException { fileStore.state().close(); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java000066400000000000000000000316251265745405500303400ustar00rootroot00000000000000/* * 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.Jimfs.URI_SCHEME; import static java.nio.file.StandardOpenOption.APPEND; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; import java.nio.file.AccessMode; import java.nio.file.CopyOption; import java.nio.file.DirectoryStream; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.LinkOption; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.ProviderMismatchException; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.DosFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.nio.file.spi.FileSystemProvider; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import javax.annotation.Nullable; /** * {@link FileSystemProvider} implementation for Jimfs. This provider implements the actual file * system operations but does not handle creation, caching or lookup of file systems. See * {@link SystemJimfsFileSystemProvider}, which is the {@code META-INF/services/} entry for Jimfs, * for those operations. * * @author Colin Decker */ final class JimfsFileSystemProvider extends FileSystemProvider { private static final JimfsFileSystemProvider INSTANCE = new JimfsFileSystemProvider(); static { // Register the URL stream handler implementation. try { Handler.register(); } catch (Throwable e) { // Couldn't set the system property needed to register the handler. Nothing we can do really. } } /** * Returns the singleton instance of this provider. */ static JimfsFileSystemProvider instance() { return INSTANCE; } @Override public String getScheme() { return URI_SCHEME; } @Override public FileSystem newFileSystem(URI uri, Map env) throws IOException { throw new UnsupportedOperationException( "This method should not be called directly;" + "use an overload of Jimfs.newFileSystem() to create a FileSystem."); } @Override public FileSystem getFileSystem(URI uri) { throw new UnsupportedOperationException( "This method should not be called directly; " + "use FileSystems.getFileSystem(URI) instead."); } @Override public FileSystem newFileSystem(Path path, Map env) throws IOException { JimfsPath checkedPath = checkPath(path); checkNotNull(env); URI pathUri = checkedPath.toUri(); URI jarUri = URI.create("jar:" + pathUri); try { // pass the new jar:jimfs://... URI to be handled by ZipFileSystemProvider return FileSystems.newFileSystem(jarUri, env); } catch (Exception e) { // if any exception occurred, assume the file wasn't a zip file and that we don't support // viewing it as a file system throw new UnsupportedOperationException(e); } } @Override public Path getPath(URI uri) { throw new UnsupportedOperationException( "This method should not be called directly; " + "use Paths.get(URI) instead."); } private static JimfsPath checkPath(Path path) { if (path instanceof JimfsPath) { return (JimfsPath) path; } throw new ProviderMismatchException( "path " + path + " is not associated with a Jimfs file system"); } /** * Gets the file system for the given path. */ private static JimfsFileSystem getFileSystem(Path path) { return (JimfsFileSystem) checkPath(path).getFileSystem(); } /** * Returns the default file system view for the given path. */ private static FileSystemView getDefaultView(JimfsPath path) { return getFileSystem(path).getDefaultView(); } @Override public FileChannel newFileChannel( Path path, Set options, FileAttribute... attrs) throws IOException { JimfsPath checkedPath = checkPath(path); if (!checkedPath.getJimfsFileSystem().getFileStore().supportsFeature(FILE_CHANNEL)) { throw new UnsupportedOperationException(); } return newJimfsFileChannel(checkedPath, options, attrs); } private JimfsFileChannel newJimfsFileChannel( JimfsPath path, Set options, FileAttribute... attrs) throws IOException { ImmutableSet opts = Options.getOptionsForChannel(options); FileSystemView view = getDefaultView(path); RegularFile file = view.getOrCreateRegularFile(path, opts, attrs); return new JimfsFileChannel(file, opts, view.state()); } @Override public SeekableByteChannel newByteChannel( Path path, Set options, FileAttribute... attrs) throws IOException { JimfsPath checkedPath = checkPath(path); JimfsFileChannel channel = newJimfsFileChannel(checkedPath, options, attrs); return checkedPath.getJimfsFileSystem().getFileStore().supportsFeature(FILE_CHANNEL) ? channel : new DowngradedSeekableByteChannel(channel); } @Override public AsynchronousFileChannel newAsynchronousFileChannel( Path path, Set options, @Nullable ExecutorService executor, FileAttribute... attrs) throws IOException { // call newFileChannel and cast so that FileChannel support is checked there JimfsFileChannel channel = (JimfsFileChannel) newFileChannel(path, options, attrs); if (executor == null) { JimfsFileSystem fileSystem = (JimfsFileSystem) path.getFileSystem(); executor = fileSystem.getDefaultThreadPool(); } return channel.asAsynchronousFileChannel(executor); } @Override public InputStream newInputStream(Path path, OpenOption... options) throws IOException { JimfsPath checkedPath = checkPath(path); ImmutableSet opts = Options.getOptionsForInputStream(options); FileSystemView view = getDefaultView(checkedPath); RegularFile file = view.getOrCreateRegularFile(checkedPath, opts, NO_ATTRS); return new JimfsInputStream(file, view.state()); } private static final FileAttribute[] NO_ATTRS = {}; @Override public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException { JimfsPath checkedPath = checkPath(path); ImmutableSet opts = Options.getOptionsForOutputStream(options); FileSystemView view = getDefaultView(checkedPath); RegularFile file = view.getOrCreateRegularFile(checkedPath, opts, NO_ATTRS); return new JimfsOutputStream(file, opts.contains(APPEND), view.state()); } @Override public DirectoryStream newDirectoryStream( Path dir, DirectoryStream.Filter filter) throws IOException { JimfsPath checkedPath = checkPath(dir); return getDefaultView(checkedPath) .newDirectoryStream(checkedPath, filter, Options.FOLLOW_LINKS, checkedPath); } @Override public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { JimfsPath checkedPath = checkPath(dir); FileSystemView view = getDefaultView(checkedPath); view.createDirectory(checkedPath, attrs); } @Override public void createLink(Path link, Path existing) throws IOException { JimfsPath linkPath = checkPath(link); JimfsPath existingPath = checkPath(existing); checkArgument( linkPath.getFileSystem().equals(existingPath.getFileSystem()), "link and existing paths must belong to the same file system instance"); FileSystemView view = getDefaultView(linkPath); view.link(linkPath, getDefaultView(existingPath), existingPath); } @Override public void createSymbolicLink(Path link, Path target, FileAttribute... attrs) throws IOException { JimfsPath linkPath = checkPath(link); JimfsPath targetPath = checkPath(target); checkArgument( linkPath.getFileSystem().equals(targetPath.getFileSystem()), "link and target paths must belong to the same file system instance"); FileSystemView view = getDefaultView(linkPath); view.createSymbolicLink(linkPath, targetPath, attrs); } @Override public Path readSymbolicLink(Path link) throws IOException { JimfsPath checkedPath = checkPath(link); return getDefaultView(checkedPath).readSymbolicLink(checkedPath); } @Override public void delete(Path path) throws IOException { JimfsPath checkedPath = checkPath(path); FileSystemView view = getDefaultView(checkedPath); view.deleteFile(checkedPath, FileSystemView.DeleteMode.ANY); } @Override public void copy(Path source, Path target, CopyOption... options) throws IOException { copy(source, target, Options.getCopyOptions(options), false); } @Override public void move(Path source, Path target, CopyOption... options) throws IOException { copy(source, target, Options.getMoveOptions(options), true); } private void copy(Path source, Path target, ImmutableSet options, boolean move) throws IOException { JimfsPath sourcePath = checkPath(source); JimfsPath targetPath = checkPath(target); FileSystemView sourceView = getDefaultView(sourcePath); FileSystemView targetView = getDefaultView(targetPath); sourceView.copy(sourcePath, targetView, targetPath, options, move); } @Override public boolean isSameFile(Path path, Path path2) throws IOException { if (path.equals(path2)) { return true; } if (!(path instanceof JimfsPath && path2 instanceof JimfsPath)) { return false; } JimfsPath checkedPath = (JimfsPath) path; JimfsPath checkedPath2 = (JimfsPath) path2; FileSystemView view = getDefaultView(checkedPath); FileSystemView view2 = getDefaultView(checkedPath2); return view.isSameFile(checkedPath, view2, checkedPath2); } @Override public boolean isHidden(Path path) throws IOException { // TODO(cgdecker): This should probably be configurable, but this seems fine for now /* * If the DOS view is supported, use the Windows isHidden method (check the dos:hidden * attribute). Otherwise, use the Unix isHidden method (just check if the file name starts with * "."). */ JimfsPath checkedPath = checkPath(path); FileSystemView view = getDefaultView(checkedPath); if (getFileStore(path).supportsFileAttributeView("dos")) { return view .readAttributes(checkedPath, DosFileAttributes.class, Options.NOFOLLOW_LINKS) .isHidden(); } return path.getNameCount() > 0 && path.getFileName().toString().startsWith("."); } @Override public FileStore getFileStore(Path path) throws IOException { return getFileSystem(path).getFileStore(); } @Override public void checkAccess(Path path, AccessMode... modes) throws IOException { JimfsPath checkedPath = checkPath(path); getDefaultView(checkedPath).checkAccess(checkedPath); } @Nullable @Override public V getFileAttributeView( Path path, Class type, LinkOption... options) { JimfsPath checkedPath = checkPath(path); return getDefaultView(checkedPath) .getFileAttributeView(checkedPath, type, Options.getLinkOptions(options)); } @Override public
A readAttributes( Path path, Class type, LinkOption... options) throws IOException { JimfsPath checkedPath = checkPath(path); return getDefaultView(checkedPath) .readAttributes(checkedPath, type, Options.getLinkOptions(options)); } @Override public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { JimfsPath checkedPath = checkPath(path); return getDefaultView(checkedPath) .readAttributes(checkedPath, attributes, Options.getLinkOptions(options)); } @Override public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { JimfsPath checkedPath = checkPath(path); getDefaultView(checkedPath) .setAttribute(checkedPath, attribute, value, Options.getLinkOptions(options)); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystems.java000066400000000000000000000124461265745405500270100ustar00rootroot00000000000000/* * 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.jimfs.Jimfs.URI_SCHEME; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.nio.file.spi.FileSystemProvider; import java.util.HashMap; import java.util.Map; /** * Initializes and configures new file system instances. * * @author Colin Decker */ final class JimfsFileSystems { private JimfsFileSystems() {} /** * The system-loaded {@code JimfsFileSystemProvider} that caches {@code JimfsFileSystem} * instances. */ private static final FileSystemProvider systemJimfsProvider = getSystemJimfsProvider(); private static FileSystemProvider getSystemJimfsProvider() { for (FileSystemProvider provider : FileSystemProvider.installedProviders()) { if (provider.getScheme().equals(URI_SCHEME)) { return provider; } } return null; } private static final Runnable DO_NOTHING = new Runnable() { @Override public void run() {} }; /** * Returns a {@code Runnable} that will remove the file system with the given {@code URI} from * the system provider's cache when called. */ private static Runnable removeFileSystemRunnable(URI uri) { if (systemJimfsProvider == null) { // TODO(cgdecker): Use Runnables.doNothing() when it's out of @Beta return DO_NOTHING; } // We have to invoke the SystemJimfsFileSystemProvider.removeFileSystemRunnable(URI) // method reflectively since the system-loaded instance of it may be a different class // than the one we'd get if we tried to cast it and call it like normal here. try { Method method = systemJimfsProvider.getClass().getDeclaredMethod("removeFileSystemRunnable", URI.class); return (Runnable) method.invoke(systemJimfsProvider, uri); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { throw new RuntimeException( "Unable to get Runnable for removing the FileSystem from the cache when it is closed", e); } } /** * Initialize and configure a new file system with the given provider and URI, using the given * configuration. */ public static JimfsFileSystem newFileSystem( JimfsFileSystemProvider provider, URI uri, Configuration config) throws IOException { PathService pathService = new PathService(config); FileSystemState state = new FileSystemState(removeFileSystemRunnable(uri)); JimfsFileStore fileStore = createFileStore(config, pathService, state); FileSystemView defaultView = createDefaultView(config, fileStore, pathService); WatchServiceConfiguration watchServiceConfig = config.watchServiceConfig; JimfsFileSystem fileSystem = new JimfsFileSystem(provider, uri, fileStore, pathService, defaultView, watchServiceConfig); pathService.setFileSystem(fileSystem); return fileSystem; } /** * Creates the file store for the file system. */ private static JimfsFileStore createFileStore( Configuration config, PathService pathService, FileSystemState state) { AttributeService attributeService = new AttributeService(config); // TODO(cgdecker): Make disk values configurable HeapDisk disk = new HeapDisk(config); FileFactory fileFactory = new FileFactory(disk); Map roots = new HashMap<>(); // create roots for (String root : config.roots) { JimfsPath path = pathService.parsePath(root); if (!path.isAbsolute() && path.getNameCount() == 0) { throw new IllegalArgumentException("Invalid root path: " + root); } Name rootName = path.root(); Directory rootDir = fileFactory.createRootDirectory(rootName); attributeService.setInitialAttributes(rootDir); roots.put(rootName, rootDir); } return new JimfsFileStore( new FileTree(roots), fileFactory, disk, attributeService, config.supportedFeatures, state); } /** * Creates the default view of the file system using the given working directory. */ private static FileSystemView createDefaultView( Configuration config, JimfsFileStore fileStore, PathService pathService) throws IOException { JimfsPath workingDirPath = pathService.parsePath(config.workingDirectory); Directory dir = fileStore.getRoot(workingDirPath.root()); if (dir == null) { throw new IllegalArgumentException("Invalid working dir path: " + workingDirPath); } for (Name name : workingDirPath.names()) { Directory newDir = fileStore.directoryCreator().get(); fileStore.setInitialAttributes(newDir); dir.link(name, newDir); dir = newDir; } return new FileSystemView(fileStore, dir, workingDirPath); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/JimfsInputStream.java000066400000000000000000000072541265745405500270150ustar00rootroot00000000000000/* * 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.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.Ints; import java.io.IOException; import java.io.InputStream; import javax.annotation.concurrent.GuardedBy; /** * {@link InputStream} for reading from a file's {@link RegularFile}. * * @author Colin Decker */ final class JimfsInputStream extends InputStream { @GuardedBy("this") @VisibleForTesting RegularFile file; @GuardedBy("this") private long pos; @GuardedBy("this") private boolean finished; private final FileSystemState fileSystemState; public JimfsInputStream(RegularFile file, FileSystemState fileSystemState) { this.file = checkNotNull(file); this.fileSystemState = fileSystemState; fileSystemState.register(this); } @Override public synchronized int read() throws IOException { checkNotClosed(); if (finished) { return -1; } file.readLock().lock(); try { int b = file.read(pos++); // it's ok for pos to go beyond size() if (b == -1) { finished = true; } else { file.updateAccessTime(); } return b; } finally { file.readLock().unlock(); } } @Override public int read(byte[] b) throws IOException { return readInternal(b, 0, b.length); } @Override public int read(byte[] b, int off, int len) throws IOException { checkPositionIndexes(off, off + len, b.length); return readInternal(b, off, len); } private synchronized int readInternal(byte[] b, int off, int len) throws IOException { checkNotClosed(); if (finished) { return -1; } file.readLock().lock(); try { int read = file.read(pos, b, off, len); if (read == -1) { finished = true; } else { pos += read; } file.updateAccessTime(); return read; } finally { file.readLock().unlock(); } } @Override public long skip(long n) throws IOException { if (n <= 0) { return 0; } synchronized (this) { checkNotClosed(); if (finished) { return 0; } // available() must be an int, so the min must be also int skip = (int) Math.min(Math.max(file.size() - pos, 0), n); pos += skip; return skip; } } @Override public synchronized int available() throws IOException { checkNotClosed(); if (finished) { return 0; } long available = Math.max(file.size() - pos, 0); return Ints.saturatedCast(available); } @GuardedBy("this") private void checkNotClosed() throws IOException { if (file == null) { throw new IOException("stream is closed"); } } @Override public synchronized void close() throws IOException { if (isOpen()) { fileSystemState.unregister(this); file.closed(); // file is set to null here and only here file = null; } } @GuardedBy("this") private boolean isOpen() { return file != null; } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/JimfsOutputStream.java000066400000000000000000000055711265745405500272160ustar00rootroot00000000000000/* * 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.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.io.OutputStream; import javax.annotation.concurrent.GuardedBy; /** * {@link OutputStream} for writing to a {@link RegularFile}. * * @author Colin Decker */ final class JimfsOutputStream extends OutputStream { @GuardedBy("this") @VisibleForTesting RegularFile file; @GuardedBy("this") private long pos; private final boolean append; private final FileSystemState fileSystemState; JimfsOutputStream(RegularFile file, boolean append, FileSystemState fileSystemState) { this.file = checkNotNull(file); this.append = append; this.fileSystemState = fileSystemState; fileSystemState.register(this); } @Override public synchronized void write(int b) throws IOException { checkNotClosed(); file.writeLock().lock(); try { if (append) { pos = file.sizeWithoutLocking(); } file.write(pos++, (byte) b); file.updateModifiedTime(); } finally { file.writeLock().unlock(); } } @Override public void write(byte[] b) throws IOException { writeInternal(b, 0, b.length); } @Override public void write(byte[] b, int off, int len) throws IOException { checkPositionIndexes(off, off + len, b.length); writeInternal(b, off, len); } private synchronized void writeInternal(byte[] b, int off, int len) throws IOException { checkNotClosed(); file.writeLock().lock(); try { if (append) { pos = file.sizeWithoutLocking(); } pos += file.write(pos, b, off, len); file.updateModifiedTime(); } finally { file.writeLock().unlock(); } } @GuardedBy("this") private void checkNotClosed() throws IOException { if (file == null) { throw new IOException("stream is closed"); } } @Override public synchronized void close() throws IOException { if (isOpen()) { fileSystemState.unregister(this); file.closed(); // file is set to null here and only here file = null; } } @GuardedBy("this") private boolean isOpen() { return file != null; } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java000066400000000000000000000306201265745405500254270ustar00rootroot00000000000000/* * 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 com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.ProviderMismatchException; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.AbstractList; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.Iterator; import java.util.List; import java.util.Objects; import javax.annotation.Nullable; /** * Jimfs implementation of {@link Path}. Creation of new {@code Path} objects is delegated to the * file system's {@link PathService}. * * @author Colin Decker */ final class JimfsPath implements Path { @Nullable private final Name root; private final ImmutableList names; private final PathService pathService; public JimfsPath(PathService pathService, @Nullable Name root, Iterable names) { this.pathService = checkNotNull(pathService); this.root = root; this.names = ImmutableList.copyOf(names); } /** * Returns the root name, or null if there is no root. */ @Nullable public Name root() { return root; } /** * Returns the list of name elements. */ public ImmutableList names() { return names; } /** * Returns the file name of this path. Unlike {@link #getFileName()}, this may return the name of * the root if this is a root path. */ @Nullable public Name name() { if (!names.isEmpty()) { return Iterables.getLast(names); } return root; } /** * Returns whether or not this is the empty path, with no root and a single, empty string, name. */ public boolean isEmptyPath() { return root == null && names.size() == 1 && names.get(0).toString().isEmpty(); } @Override public FileSystem getFileSystem() { return pathService.getFileSystem(); } /** * Equivalent to {@link #getFileSystem()} but with a return type of {@code JimfsFileSystem}. * {@code getFileSystem()}'s return type is left as {@code FileSystem} to make testing paths * easier (as long as methods that access the file system in some way are not called, the file * system can be a fake file system instance). */ public JimfsFileSystem getJimfsFileSystem() { return (JimfsFileSystem) pathService.getFileSystem(); } @Override public boolean isAbsolute() { return root != null; } @Override public JimfsPath getRoot() { if (root == null) { return null; } return pathService.createRoot(root); } @Override public JimfsPath getFileName() { return names.isEmpty() ? null : getName(names.size() - 1); } @Override public JimfsPath getParent() { if (names.isEmpty() || names.size() == 1 && root == null) { return null; } return pathService.createPath(root, names.subList(0, names.size() - 1)); } @Override public int getNameCount() { return names.size(); } @Override public JimfsPath getName(int index) { checkArgument( index >= 0 && index < names.size(), "index (%s) must be >= 0 and < name count (%s)", index, names.size()); return pathService.createFileName(names.get(index)); } @Override public JimfsPath subpath(int beginIndex, int endIndex) { checkArgument( beginIndex >= 0 && endIndex <= names.size() && endIndex > beginIndex, "beginIndex (%s) must be >= 0; endIndex (%s) must be <= name count (%s) and > beginIndex", beginIndex, endIndex, names.size()); return pathService.createRelativePath(names.subList(beginIndex, endIndex)); } /** * Returns true if list starts with all elements of other in the same order. */ private static boolean startsWith(List list, List other) { return list.size() >= other.size() && list.subList(0, other.size()).equals(other); } @Override public boolean startsWith(Path other) { JimfsPath otherPath = checkPath(other); return otherPath != null && getFileSystem().equals(otherPath.getFileSystem()) && Objects.equals(root, otherPath.root) && startsWith(names, otherPath.names); } @Override public boolean startsWith(String other) { return startsWith(pathService.parsePath(other)); } @Override public boolean endsWith(Path other) { JimfsPath otherPath = checkPath(other); if (otherPath == null) { return false; } if (otherPath.isAbsolute()) { return compareTo(otherPath) == 0; } return startsWith(names.reverse(), otherPath.names.reverse()); } @Override public boolean endsWith(String other) { return endsWith(pathService.parsePath(other)); } @Override public JimfsPath normalize() { if (isNormal()) { return this; } Deque newNames = new ArrayDeque<>(); for (Name name : names) { if (name.equals(Name.PARENT)) { Name lastName = newNames.peekLast(); if (lastName != null && !lastName.equals(Name.PARENT)) { newNames.removeLast(); } else if (!isAbsolute()) { // if there's a root and we have an extra ".." that would go up above the root, ignore it newNames.add(name); } } else if (!name.equals(Name.SELF)) { newNames.add(name); } } return newNames.equals(names) ? this : pathService.createPath(root, newNames); } /** * Returns whether or not this path is in a normalized form. It's normal if it both contains no * "." names and contains no ".." names in a location other than the start of the path. */ private boolean isNormal() { if (getNameCount() == 0 || getNameCount() == 1 && !isAbsolute()) { return true; } boolean foundNonParentName = isAbsolute(); // if there's a root, the path doesn't start with .. boolean normal = true; for (Name name : names) { if (name.equals(Name.PARENT)) { if (foundNonParentName) { normal = false; break; } } else { if (name.equals(Name.SELF)) { normal = false; break; } foundNonParentName = true; } } return normal; } /** * Resolves the given name against this path. The name is assumed not to be a root name. */ JimfsPath resolve(Name name) { if (name.toString().isEmpty()) { return this; } return pathService.createPathInternal( root, ImmutableList.builder() .addAll(names) .add(name) .build()); } @Override public JimfsPath resolve(Path other) { JimfsPath otherPath = checkPath(other); if (otherPath == null) { throw new ProviderMismatchException(other.toString()); } if (isEmptyPath() || otherPath.isAbsolute()) { return otherPath; } if (otherPath.isEmptyPath()) { return this; } return pathService.createPath( root, ImmutableList.builder() .addAll(names) .addAll(otherPath.names) .build()); } @Override public JimfsPath resolve(String other) { return resolve(pathService.parsePath(other)); } @Override public JimfsPath resolveSibling(Path other) { JimfsPath otherPath = checkPath(other); if (otherPath == null) { throw new ProviderMismatchException(other.toString()); } if (otherPath.isAbsolute()) { return otherPath; } JimfsPath parent = getParent(); if (parent == null) { return otherPath; } return parent.resolve(other); } @Override public JimfsPath resolveSibling(String other) { return resolveSibling(pathService.parsePath(other)); } @Override public JimfsPath relativize(Path other) { JimfsPath otherPath = checkPath(other); if (otherPath == null) { throw new ProviderMismatchException(other.toString()); } checkArgument( Objects.equals(root, otherPath.root), "Paths have different roots: %s, %s", this, other); if (equals(other)) { return pathService.emptyPath(); } if (isEmptyPath()) { return otherPath; } ImmutableList otherNames = otherPath.names; int sharedSubsequenceLength = 0; for (int i = 0; i < Math.min(getNameCount(), otherNames.size()); i++) { if (names.get(i).equals(otherNames.get(i))) { sharedSubsequenceLength++; } else { break; } } int extraNamesInThis = Math.max(0, getNameCount() - sharedSubsequenceLength); ImmutableList extraNamesInOther = (otherNames.size() <= sharedSubsequenceLength) ? ImmutableList.of() : otherNames.subList(sharedSubsequenceLength, otherNames.size()); List parts = new ArrayList<>(extraNamesInThis + extraNamesInOther.size()); // add .. for each extra name in this path parts.addAll(Collections.nCopies(extraNamesInThis, Name.PARENT)); // add each extra name in the other path parts.addAll(extraNamesInOther); return pathService.createRelativePath(parts); } @Override public JimfsPath toAbsolutePath() { return isAbsolute() ? this : getJimfsFileSystem().getWorkingDirectory().resolve(this); } @Override public JimfsPath toRealPath(LinkOption... options) throws IOException { return getJimfsFileSystem() .getDefaultView() .toRealPath(this, pathService, Options.getLinkOptions(options)); } @Override public WatchKey register( WatchService watcher, WatchEvent.Kind[] events, WatchEvent.Modifier... modifiers) throws IOException { checkNotNull(modifiers); return register(watcher, events); } @Override public WatchKey register(WatchService watcher, WatchEvent.Kind... events) throws IOException { checkNotNull(watcher); checkNotNull(events); if (!(watcher instanceof AbstractWatchService)) { throw new IllegalArgumentException( "watcher (" + watcher + ") is not associated with this file system"); } AbstractWatchService service = (AbstractWatchService) watcher; return service.register(this, Arrays.asList(events)); } @Override public URI toUri() { return getJimfsFileSystem().toUri(this); } @Override public File toFile() { // documented as unsupported for anything but the default file system throw new UnsupportedOperationException(); } @Override public Iterator iterator() { return asList().iterator(); } private List asList() { return new AbstractList() { @Override public Path get(int index) { return getName(index); } @Override public int size() { return getNameCount(); } }; } @Override public int compareTo(Path other) { // documented to throw CCE if other is associated with a different FileSystemProvider JimfsPath otherPath = (JimfsPath) other; return ComparisonChain.start() .compare(getJimfsFileSystem().getUri(), ((JimfsPath) other).getJimfsFileSystem().getUri()) .compare(this, otherPath, pathService) .result(); } @Override public boolean equals(@Nullable Object obj) { return obj instanceof JimfsPath && compareTo((JimfsPath) obj) == 0; } @Override public int hashCode() { return pathService.hash(this); } @Override public String toString() { return pathService.toString(this); } @Nullable private JimfsPath checkPath(Path other) { if (checkNotNull(other) instanceof JimfsPath && other.getFileSystem().equals(getFileSystem())) { return (JimfsPath) other; } return null; } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/JimfsSecureDirectoryStream.java000066400000000000000000000154071265745405500310300ustar00rootroot00000000000000/* * 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.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.channels.SeekableByteChannel; import java.nio.file.ClosedDirectoryStreamException; import java.nio.file.CopyOption; import java.nio.file.DirectoryIteratorException; import java.nio.file.LinkOption; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.ProviderMismatchException; import java.nio.file.SecureDirectoryStream; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.util.Iterator; import java.util.Set; import javax.annotation.Nullable; /** * Secure directory stream implementation that uses a {@link FileSystemView} with the stream's * directory as its working directory. * * @author Colin Decker */ final class JimfsSecureDirectoryStream implements SecureDirectoryStream { private final FileSystemView view; private final Filter filter; private final FileSystemState fileSystemState; private boolean open = true; private Iterator iterator = new DirectoryIterator(); public JimfsSecureDirectoryStream( FileSystemView view, Filter filter, FileSystemState fileSystemState) { this.view = checkNotNull(view); this.filter = checkNotNull(filter); this.fileSystemState = fileSystemState; fileSystemState.register(this); } private JimfsPath path() { return view.getWorkingDirectoryPath(); } @Override public synchronized Iterator iterator() { checkOpen(); Iterator result = iterator; checkState(result != null, "iterator() has already been called once"); iterator = null; return result; } @Override public synchronized void close() { open = false; fileSystemState.unregister(this); } protected synchronized void checkOpen() { if (!open) { throw new ClosedDirectoryStreamException(); } } private final class DirectoryIterator extends AbstractIterator { @Nullable private Iterator fileNames; @Override protected synchronized Path computeNext() { checkOpen(); try { if (fileNames == null) { fileNames = view.snapshotWorkingDirectoryEntries().iterator(); } while (fileNames.hasNext()) { Name name = fileNames.next(); Path path = view.getWorkingDirectoryPath().resolve(name); if (filter.accept(path)) { return path; } } return endOfData(); } catch (IOException e) { throw new DirectoryIteratorException(e); } } } /** * A stream filter that always returns true. */ public static final Filter ALWAYS_TRUE_FILTER = new Filter() { @Override public boolean accept(Object entry) throws IOException { return true; } }; @Override public SecureDirectoryStream newDirectoryStream(Path path, LinkOption... options) throws IOException { checkOpen(); JimfsPath checkedPath = checkPath(path); // safe cast because a file system that supports SecureDirectoryStream always creates // SecureDirectoryStreams return (SecureDirectoryStream) view.newDirectoryStream( checkedPath, ALWAYS_TRUE_FILTER, Options.getLinkOptions(options), path().resolve(checkedPath)); } @Override public SeekableByteChannel newByteChannel( Path path, Set options, FileAttribute... attrs) throws IOException { checkOpen(); JimfsPath checkedPath = checkPath(path); ImmutableSet opts = Options.getOptionsForChannel(options); return new JimfsFileChannel( view.getOrCreateRegularFile(checkedPath, opts), opts, fileSystemState); } @Override public void deleteFile(Path path) throws IOException { checkOpen(); JimfsPath checkedPath = checkPath(path); view.deleteFile(checkedPath, FileSystemView.DeleteMode.NON_DIRECTORY_ONLY); } @Override public void deleteDirectory(Path path) throws IOException { checkOpen(); JimfsPath checkedPath = checkPath(path); view.deleteFile(checkedPath, FileSystemView.DeleteMode.DIRECTORY_ONLY); } @Override public void move(Path srcPath, SecureDirectoryStream targetDir, Path targetPath) throws IOException { checkOpen(); JimfsPath checkedSrcPath = checkPath(srcPath); JimfsPath checkedTargetPath = checkPath(targetPath); if (!(targetDir instanceof JimfsSecureDirectoryStream)) { throw new ProviderMismatchException( "targetDir isn't a secure directory stream associated with this file system"); } JimfsSecureDirectoryStream checkedTargetDir = (JimfsSecureDirectoryStream) targetDir; view.copy( checkedSrcPath, checkedTargetDir.view, checkedTargetPath, ImmutableSet.of(), true); } @Override public V getFileAttributeView(Class type) { return getFileAttributeView(path().getFileSystem().getPath("."), type); } @Override public V getFileAttributeView( Path path, Class type, LinkOption... options) { checkOpen(); final JimfsPath checkedPath = checkPath(path); final ImmutableSet optionsSet = Options.getLinkOptions(options); return view.getFileAttributeView( new FileLookup() { @Override public File lookup() throws IOException { checkOpen(); // per the spec, must check that the stream is open for each view operation return view .lookUpWithLock(checkedPath, optionsSet) .requireExists(checkedPath) .file(); } }, type); } private static JimfsPath checkPath(Path path) { if (path instanceof JimfsPath) { return (JimfsPath) path; } throw new ProviderMismatchException( "path " + path + " is not associated with a Jimfs file system"); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/Name.java000066400000000000000000000076121265745405500244270ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.collect.Ordering; import javax.annotation.Nullable; /** * Immutable representation of a file name. Used both for the name components of paths and as the * keys for directory entries. * *

A name has both a display string (used in the {@code toString()} form of a {@code Path} as * well as for {@code Path} equality and sort ordering) and a canonical string, which is used for * determining equality of the name during file lookup. * *

Note: all factory methods return a constant name instance when given the original string "." * or "..", ensuring that those names can be accessed statically elsewhere in the code while still * being equal to any names created for those values, regardless of normalization settings. * * @author Colin Decker */ final class Name { /** The empty name. */ static final Name EMPTY = new Name("", ""); /** The name to use for a link from a directory to itself. */ public static final Name SELF = new Name(".", "."); /** The name to use for a link from a directory to its parent directory. */ public static final Name PARENT = new Name("..", ".."); /** * Creates a new name with no normalization done on the given string. */ @VisibleForTesting static Name simple(String name) { switch (name) { case ".": return SELF; case "..": return PARENT; default: return new Name(name, name); } } /** * Creates a name with the given display representation and the given canonical representation. */ public static Name create(String display, String canonical) { return new Name(display, canonical); } private final String display; private final String canonical; private Name(String display, String canonical) { this.display = checkNotNull(display); this.canonical = checkNotNull(canonical); } @Override public boolean equals(@Nullable Object obj) { if (obj instanceof Name) { Name other = (Name) obj; return canonical.equals(other.canonical); } return false; } @Override public int hashCode() { return Util.smearHash(canonical.hashCode()); } @Override public String toString() { return display; } /** * Returns an ordering that orders names by their display representation. */ public static Ordering displayOrdering() { return DISPLAY_ORDERING; } /** * Returns an ordering that orders names by their canonical representation. */ public static Ordering canonicalOrdering() { return CANONICAL_ORDERING; } private static final Ordering DISPLAY_ORDERING = Ordering.natural() .onResultOf( new Function() { @Override public String apply(Name name) { return name.display; } }); private static final Ordering CANONICAL_ORDERING = Ordering.natural() .onResultOf( new Function() { @Override public String apply(Name name) { return name.canonical; } }); } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/Options.java000066400000000000000000000122101265745405500251700ustar00rootroot00000000000000/* * 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.checkNotNull; import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; import static java.nio.file.StandardOpenOption.APPEND; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.READ; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.WRITE; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.nio.file.CopyOption; import java.nio.file.LinkOption; import java.nio.file.OpenOption; import java.util.Set; /** * Utility methods for normalizing user-provided options arrays and sets to canonical immutable * sets of options. * * @author Colin Decker */ final class Options { private Options() {} /** * Immutable set containing LinkOption.NOFOLLOW_LINKS. */ public static final ImmutableSet NOFOLLOW_LINKS = ImmutableSet.of(LinkOption.NOFOLLOW_LINKS); /** * Immutable empty LinkOption set. */ public static final ImmutableSet FOLLOW_LINKS = ImmutableSet.of(); private static final ImmutableSet DEFAULT_READ = ImmutableSet.of(READ); private static final ImmutableSet DEFAULT_READ_NOFOLLOW_LINKS = ImmutableSet.of(READ, LinkOption.NOFOLLOW_LINKS); private static final ImmutableSet DEFAULT_WRITE = ImmutableSet.of(WRITE, CREATE, TRUNCATE_EXISTING); /** * Returns an immutable set of link options. */ public static ImmutableSet getLinkOptions(LinkOption... options) { return options.length == 0 ? FOLLOW_LINKS : NOFOLLOW_LINKS; } /** * Returns an immutable set of open options for opening a new file channel. */ public static ImmutableSet getOptionsForChannel(Set options) { if (options.isEmpty()) { return DEFAULT_READ; } boolean append = options.contains(APPEND); boolean write = append || options.contains(WRITE); boolean read = !write || options.contains(READ); if (read) { if (append) { throw new UnsupportedOperationException("'READ' + 'APPEND' not allowed"); } if (!write) { // ignore all write related options return options.contains(LinkOption.NOFOLLOW_LINKS) ? DEFAULT_READ_NOFOLLOW_LINKS : DEFAULT_READ; } } // options contains write or append and may also contain read // it does not contain both read and append if (options.contains(WRITE)) { return ImmutableSet.copyOf(options); } else { return new ImmutableSet.Builder() .add(WRITE) .addAll(options) .build(); } } /** * Returns an immutable set of open options for opening a new input stream. */ @SuppressWarnings("unchecked") // safe covariant cast public static ImmutableSet getOptionsForInputStream(OpenOption... options) { boolean nofollowLinks = false; for (OpenOption option : options) { if (checkNotNull(option) != READ) { if (option == LinkOption.NOFOLLOW_LINKS) { nofollowLinks = true; } else { throw new UnsupportedOperationException("'" + option + "' not allowed"); } } } // just return the link options for finding the file, nothing else is needed return (ImmutableSet) (ImmutableSet) (nofollowLinks ? NOFOLLOW_LINKS : FOLLOW_LINKS); } /** * Returns an immutable set of open options for opening a new output stream. */ public static ImmutableSet getOptionsForOutputStream(OpenOption... options) { if (options.length == 0) { return DEFAULT_WRITE; } ImmutableSet result = ImmutableSet.copyOf(options); if (result.contains(READ)) { throw new UnsupportedOperationException("'READ' not allowed"); } return result; } /** * Returns an immutable set of the given options for a move. */ public static ImmutableSet getMoveOptions(CopyOption... options) { return ImmutableSet.copyOf(Lists.asList(LinkOption.NOFOLLOW_LINKS, options)); } /** * Returns an immutable set of the given options for a copy. */ public static ImmutableSet getCopyOptions(CopyOption... options) { ImmutableSet result = ImmutableSet.copyOf(options); if (result.contains(ATOMIC_MOVE)) { throw new UnsupportedOperationException("'ATOMIC_MOVE' not allowed"); } return result; } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/OwnerAttributeProvider.java000066400000000000000000000072461265745405500302430ustar00rootroot00000000000000/* * 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.checkNotNull; import static com.google.common.jimfs.UserLookupService.createUserPrincipal; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileOwnerAttributeView; import java.nio.file.attribute.UserPrincipal; import java.util.Map; import javax.annotation.Nullable; /** * Attribute provider that provides the {@link FileOwnerAttributeView} ("owner"). * * @author Colin Decker */ final class OwnerAttributeProvider extends AttributeProvider { private static final ImmutableSet ATTRIBUTES = ImmutableSet.of("owner"); private static final UserPrincipal DEFAULT_OWNER = createUserPrincipal("user"); @Override public String name() { return "owner"; } @Override public ImmutableSet fixedAttributes() { return ATTRIBUTES; } @Override public ImmutableMap defaultValues(Map userProvidedDefaults) { Object userProvidedOwner = userProvidedDefaults.get("owner:owner"); UserPrincipal owner = DEFAULT_OWNER; if (userProvidedOwner != null) { if (userProvidedOwner instanceof String) { owner = createUserPrincipal((String) userProvidedOwner); } else { throw invalidType("owner", "owner", userProvidedOwner, String.class, UserPrincipal.class); } } return ImmutableMap.of("owner:owner", owner); } @Nullable @Override public Object get(File file, String attribute) { if (attribute.equals("owner")) { return file.getAttribute("owner", "owner"); } return null; } @Override public void set(File file, String view, String attribute, Object value, boolean create) { if (attribute.equals("owner")) { UserPrincipal user = checkType(view, attribute, value, UserPrincipal.class); // TODO(cgdecker): Do we really need to do this? Any reason not to allow any UserPrincipal? if (!(user instanceof UserLookupService.JimfsUserPrincipal)) { user = createUserPrincipal(user.getName()); } file.setAttribute("owner", "owner", user); } } @Override public Class viewType() { return FileOwnerAttributeView.class; } @Override public FileOwnerAttributeView view( FileLookup lookup, ImmutableMap inheritedViews) { return new View(lookup); } /** * Implementation of {@link FileOwnerAttributeView}. */ private static final class View extends AbstractAttributeView implements FileOwnerAttributeView { public View(FileLookup lookup) { super(lookup); } @Override public String name() { return "owner"; } @Override public UserPrincipal getOwner() throws IOException { return (UserPrincipal) lookupFile().getAttribute("owner", "owner"); } @Override public void setOwner(UserPrincipal owner) throws IOException { lookupFile().setAttribute("owner", "owner", checkNotNull(owner)); } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/PathMatchers.java000066400000000000000000000065271265745405500261360ustar00rootroot00000000000000/* * 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 com.google.common.annotations.VisibleForTesting; import com.google.common.base.Ascii; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; import java.nio.file.FileSystem; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.util.regex.Pattern; /** * {@link PathMatcher} factory for any file system. * * @author Colin Decker */ final class PathMatchers { private PathMatchers() {} /** * Gets a {@link PathMatcher} for the given syntax and pattern as specified by * {@link FileSystem#getPathMatcher}. The {@code separators} string contains the path name * element separators (one character each) recognized by the file system. For a glob-syntax path * matcher, any of the given separators will be recognized as a separator in the pattern, and any * of them will be matched as a separator when checking a path. */ // TODO(cgdecker): Should I be just canonicalizing separators rather than matching any separator? // Perhaps so, assuming Path always canonicalizes its separators public static PathMatcher getPathMatcher( String syntaxAndPattern, String separators, ImmutableSet normalizations) { int syntaxSeparator = syntaxAndPattern.indexOf(':'); checkArgument( syntaxSeparator > 0, "Must be of the form 'syntax:pattern': %s", syntaxAndPattern); String syntax = Ascii.toLowerCase(syntaxAndPattern.substring(0, syntaxSeparator)); String pattern = syntaxAndPattern.substring(syntaxSeparator + 1); switch (syntax) { case "glob": pattern = GlobToRegex.toRegex(pattern, separators); // fall through case "regex": return fromRegex(pattern, normalizations); default: throw new UnsupportedOperationException("Invalid syntax: " + syntaxAndPattern); } } private static PathMatcher fromRegex(String regex, Iterable normalizations) { return new RegexPathMatcher(PathNormalization.compilePattern(regex, normalizations)); } /** * {@code PathMatcher} that matches the {@code toString()} form of a {@code Path} against a regex * {@code Pattern}. */ @VisibleForTesting static final class RegexPathMatcher implements PathMatcher { private final Pattern pattern; private RegexPathMatcher(Pattern pattern) { this.pattern = checkNotNull(pattern); } @Override public boolean matches(Path path) { return pattern.matcher(path.toString()).matches(); } @Override public String toString() { return MoreObjects.toStringHelper(this).addValue(pattern).toString(); } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/PathNormalization.java000066400000000000000000000107471265745405500272150ustar00rootroot00000000000000/* * 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 com.google.common.base.Ascii; import com.google.common.base.Function; import com.ibm.icu.lang.UCharacter; import java.text.Normalizer; import java.util.regex.Pattern; /** * Normalizations that can be applied to names in paths. Includes Unicode normalizations and * normalizations for case insensitive paths. These normalizations can be set in * {@code Configuration.Builder} when creating a Jimfs file system instance and are automatically * applied to paths in the file system. * * @author Colin Decker */ public enum PathNormalization implements Function { /** * No normalization. */ NONE(0) { @Override public String apply(String string) { return string; } }, /** * Unicode composed normalization (form {@linkplain java.text.Normalizer.Form#NFC NFC}). */ NFC(Pattern.CANON_EQ) { @Override public String apply(String string) { return Normalizer.normalize(string, Normalizer.Form.NFC); } }, /** * Unicode decomposed normalization (form {@linkplain java.text.Normalizer.Form#NFD NFD}). */ NFD(Pattern.CANON_EQ) { @Override public String apply(String string) { return Normalizer.normalize(string, Normalizer.Form.NFD); } }, /* * Some notes on case folding/case insensitivity of file systems: * * In general (I don't have any counterexamples) case-insensitive file systems handle * their case insensitivity in a locale-independent way. NTFS, for example, writes a * special case mapping file ($UpCase) to the file system when it's first initialized, * and this is not affected by the locale of either the user or the copy of Windows * being used. This means that it will NOT handle i/I-variants in filenames as you'd * expect for Turkic languages, even for a Turkish user who has installed a Turkish * copy of Windows. */ /** * Unicode case folding for case insensitive paths. Requires ICU4J on the classpath. */ CASE_FOLD_UNICODE(Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) { @Override public String apply(String string) { try { return UCharacter.foldCase(string, true); } catch (NoClassDefFoundError e) { NoClassDefFoundError error = new NoClassDefFoundError( "PathNormalization.CASE_FOLD_UNICODE requires ICU4J. " + "Did you forget to include it on your classpath?"); error.initCause(e); throw error; } } }, /** * ASCII case folding for simple case insensitive paths. */ CASE_FOLD_ASCII(Pattern.CASE_INSENSITIVE) { @Override public String apply(String string) { return Ascii.toLowerCase(string); } }; private final int patternFlags; private PathNormalization(int patternFlags) { this.patternFlags = patternFlags; } /** * Applies this normalization to the given string, returning the normalized result. */ @Override public abstract String apply(String string); /** * Returns the flags that should be used when creating a regex {@link Pattern} in order to * approximate this normalization. */ public int patternFlags() { return patternFlags; } /** * Applies the given normalizations to the given string in order, returning the normalized * result. */ public static String normalize(String string, Iterable normalizations) { String result = string; for (PathNormalization normalization : normalizations) { result = normalization.apply(result); } return result; } /** * Compiles a regex pattern using flags based on the given normalizations. */ public static Pattern compilePattern(String regex, Iterable normalizations) { int flags = 0; for (PathNormalization normalization : normalizations) { flags |= normalization.patternFlags(); } return Pattern.compile(regex, flags); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/PathService.java000066400000000000000000000226201265745405500257600ustar00rootroot00000000000000/* * 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.base.Preconditions.checkState; import static com.google.common.jimfs.PathType.ParseResult; import static java.nio.file.LinkOption.NOFOLLOW_LINKS; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Functions; import com.google.common.base.Predicate; import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.PathMatcher; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import javax.annotation.Nullable; /** * Service for creating {@link JimfsPath} instances and handling other path-related operations. * * @author Colin Decker */ final class PathService implements Comparator { private static final Ordering DISPLAY_ROOT_ORDERING = Name.displayOrdering().nullsLast(); private static final Ordering> DISPLAY_NAMES_ORDERING = Name.displayOrdering().lexicographical(); private static final Ordering CANONICAL_ROOT_ORDERING = Name.canonicalOrdering().nullsLast(); private static final Ordering> CANONICAL_NAMES_ORDERING = Name.canonicalOrdering().lexicographical(); private final PathType type; private final ImmutableSet displayNormalizations; private final ImmutableSet canonicalNormalizations; private final boolean equalityUsesCanonicalForm; private final Ordering rootOrdering; private final Ordering> namesOrdering; private volatile FileSystem fileSystem; private volatile JimfsPath emptyPath; PathService(Configuration config) { this( config.pathType, config.nameDisplayNormalization, config.nameCanonicalNormalization, config.pathEqualityUsesCanonicalForm); } PathService( PathType type, Iterable displayNormalizations, Iterable canonicalNormalizations, boolean equalityUsesCanonicalForm) { this.type = checkNotNull(type); this.displayNormalizations = ImmutableSet.copyOf(displayNormalizations); this.canonicalNormalizations = ImmutableSet.copyOf(canonicalNormalizations); this.equalityUsesCanonicalForm = equalityUsesCanonicalForm; this.rootOrdering = equalityUsesCanonicalForm ? CANONICAL_ROOT_ORDERING : DISPLAY_ROOT_ORDERING; this.namesOrdering = equalityUsesCanonicalForm ? CANONICAL_NAMES_ORDERING : DISPLAY_NAMES_ORDERING; } /** * Sets the file system to use for created paths. */ public void setFileSystem(FileSystem fileSystem) { // allowed to not be JimfsFileSystem for testing purposes only checkState(this.fileSystem == null, "may not set fileSystem twice"); this.fileSystem = checkNotNull(fileSystem); } /** * Returns the file system this service is for. */ public FileSystem getFileSystem() { return fileSystem; } /** * Returns the default path separator. */ public String getSeparator() { return type.getSeparator(); } /** * Returns an empty path which has a single name, the empty string. */ public JimfsPath emptyPath() { JimfsPath result = emptyPath; if (result == null) { // use createPathInternal to avoid recursive call from createPath() result = createPathInternal(null, ImmutableList.of(Name.EMPTY)); emptyPath = result; return result; } return result; } /** * Returns the {@link Name} form of the given string. */ public Name name(String name) { switch (name) { case "": return Name.EMPTY; case ".": return Name.SELF; case "..": return Name.PARENT; default: String display = PathNormalization.normalize(name, displayNormalizations); String canonical = PathNormalization.normalize(name, canonicalNormalizations); return Name.create(display, canonical); } } /** * Returns the {@link Name} forms of the given strings. */ @VisibleForTesting List names(Iterable names) { List result = new ArrayList<>(); for (String name : names) { result.add(name(name)); } return result; } /** * Returns a root path with the given name. */ public JimfsPath createRoot(Name root) { return createPath(checkNotNull(root), ImmutableList.of()); } /** * Returns a single filename path with the given name. */ public JimfsPath createFileName(Name name) { return createPath(null, ImmutableList.of(name)); } /** * Returns a relative path with the given names. */ public JimfsPath createRelativePath(Iterable names) { return createPath(null, ImmutableList.copyOf(names)); } /** * Returns a path with the given root (or no root, if null) and the given names. */ public JimfsPath createPath(@Nullable Name root, Iterable names) { ImmutableList nameList = ImmutableList.copyOf(Iterables.filter(names, NOT_EMPTY)); if (root == null && nameList.isEmpty()) { // ensure the canonical empty path (one empty string name) is used rather than a path with // no root and no names return emptyPath(); } return createPathInternal(root, nameList); } /** * Returns a path with the given root (or no root, if null) and the given names. */ protected final JimfsPath createPathInternal(@Nullable Name root, Iterable names) { return new JimfsPath(this, root, names); } /** * Parses the given strings as a path. */ public JimfsPath parsePath(String first, String... more) { String joined = type.joiner().join(Iterables.filter(Lists.asList(first, more), NOT_EMPTY)); return toPath(type.parsePath(joined)); } private JimfsPath toPath(ParseResult parsed) { Name root = parsed.root() == null ? null : name(parsed.root()); Iterable names = names(parsed.names()); return createPath(root, names); } /** * Returns the string form of the given path. */ public String toString(JimfsPath path) { Name root = path.root(); String rootString = root == null ? null : root.toString(); Iterable names = Iterables.transform(path.names(), Functions.toStringFunction()); return type.toString(rootString, names); } /** * Creates a hash code for the given path. */ public int hash(JimfsPath path) { int hash = 31; hash = 31 * hash + getFileSystem().hashCode(); final Name root = path.root(); final ImmutableList names = path.names(); if (equalityUsesCanonicalForm) { // use hash codes of names themselves, which are based on the canonical form hash = 31 * hash + (root == null ? 0 : root.hashCode()); for (Name name : names) { hash = 31 * hash + name.hashCode(); } } else { // use hash codes from toString() form of names hash = 31 * hash + (root == null ? 0 : root.toString().hashCode()); for (Name name : names) { hash = 31 * hash + name.toString().hashCode(); } } return hash; } @Override public int compare(JimfsPath a, JimfsPath b) { return ComparisonChain.start() .compare(a.root(), b.root(), rootOrdering) .compare(a.names(), b.names(), namesOrdering) .result(); } /** * Returns the URI for the given path. The given file system URI is the base against which the * path is resolved to create the returned URI. */ public URI toUri(URI fileSystemUri, JimfsPath path) { checkArgument(path.isAbsolute(), "path (%s) must be absolute", path); String root = String.valueOf(path.root()); Iterable names = Iterables.transform(path.names(), Functions.toStringFunction()); return type.toUri(fileSystemUri, root, names, Files.isDirectory(path, NOFOLLOW_LINKS)); } /** * Converts the path of the given URI into a path for this file system. */ public JimfsPath fromUri(URI uri) { return toPath(type.fromUri(uri)); } /** * Returns a {@link PathMatcher} for the given syntax and pattern as specified by * {@link FileSystem#getPathMatcher(String)}. */ public PathMatcher createPathMatcher(String syntaxAndPattern) { return PathMatchers.getPathMatcher( syntaxAndPattern, type.getSeparator() + type.getOtherSeparators(), displayNormalizations); } private static final Predicate NOT_EMPTY = new Predicate() { @Override public boolean apply(Object input) { return !input.toString().isEmpty(); } }; } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/PathType.java000066400000000000000000000176041265745405500253070ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.InvalidPathException; import java.util.Arrays; import javax.annotation.Nullable; /** * An object defining a specific type of path. Knows how to parse strings to a path and how to * render a path as a string as well as what the path separator is and what other separators are * recognized when parsing paths. * * @author Colin Decker */ public abstract class PathType { /** * Returns a Unix-style path type. "/" is both the root and the only separator. Any path starting * with "/" is considered absolute. The nul character ('\0') is disallowed in paths. */ public static PathType unix() { return UnixPathType.INSTANCE; } /** * Returns a Windows-style path type. The canonical separator character is "\". "/" is also * treated as a separator when parsing paths. * *

As much as possible, this implementation follows the information provided in * * this article. Paths with drive-letter roots (e.g. "C:\") and paths with UNC roots (e.g. * "\\host\share\") are supported. * *

Two Windows path features are not currently supported as they are too Windows-specific: * *

    *
  • Relative paths containing a drive-letter root, for example "C:" or "C:foo\bar". Such * paths have a root component and optionally have names, but are relative paths, * relative to the working directory of the drive identified by the root.
  • *
  • Absolute paths with no root, for example "\foo\bar". Such paths are absolute paths on * the current drive.
  • *
*/ public static PathType windows() { return WindowsPathType.INSTANCE; } private final boolean allowsMultipleRoots; private final String separator; private final String otherSeparators; private final Joiner joiner; private final Splitter splitter; protected PathType(boolean allowsMultipleRoots, char separator, char... otherSeparators) { this.separator = String.valueOf(separator); this.allowsMultipleRoots = allowsMultipleRoots; this.otherSeparators = String.valueOf(otherSeparators); this.joiner = Joiner.on(separator); this.splitter = createSplitter(separator, otherSeparators); } private static final char[] regexReservedChars = "^$.?+*\\[]{}()".toCharArray(); static { Arrays.sort(regexReservedChars); } private static boolean isRegexReserved(char c) { return Arrays.binarySearch(regexReservedChars, c) >= 0; } private static Splitter createSplitter(char separator, char... otherSeparators) { if (otherSeparators.length == 0) { return Splitter.on(separator).omitEmptyStrings(); } // TODO(cgdecker): When CharMatcher is out of @Beta, us Splitter.on(CharMatcher) StringBuilder patternBuilder = new StringBuilder(); patternBuilder.append("["); appendToRegex(separator, patternBuilder); for (char other : otherSeparators) { appendToRegex(other, patternBuilder); } patternBuilder.append("]"); return Splitter.onPattern(patternBuilder.toString()).omitEmptyStrings(); } private static void appendToRegex(char separator, StringBuilder patternBuilder) { if (isRegexReserved(separator)) { patternBuilder.append("\\"); } patternBuilder.append(separator); } /** * Returns whether or not this type of path allows multiple root directories. */ public final boolean allowsMultipleRoots() { return allowsMultipleRoots; } /** * Returns the canonical separator for this path type. The returned string always has a length of * one. */ public final String getSeparator() { return separator; } /** * Returns the other separators that are recognized when parsing a path. If no other separators * are recognized, the empty string is returned. */ public final String getOtherSeparators() { return otherSeparators; } /** * Returns the path joiner for this path type. */ public final Joiner joiner() { return joiner; } /** * Returns the path splitter for this path type. */ public final Splitter splitter() { return splitter; } /** * Returns an empty path. */ protected final ParseResult emptyPath() { return new ParseResult(null, ImmutableList.of("")); } /** * Parses the given strings as a path. * * @throws InvalidPathException if the path isn't valid for this path type */ public abstract ParseResult parsePath(String path); /** * Returns the string form of the given path. */ public abstract String toString(@Nullable String root, Iterable names); /** * Returns the string form of the given path for use in the path part of a URI. The root element * is not nullable as the path must be absolute. The elements of the returned path do not * need to be escaped. The {@code directory} boolean indicates whether the file the URI is for is * known to be a directory. */ protected abstract String toUriPath(String root, Iterable names, boolean directory); /** * Parses a path from the given URI path. * * @throws InvalidPathException if the given path isn't valid for this path type */ protected abstract ParseResult parseUriPath(String uriPath); /** * Creates a URI for the path with the given root and names in the file system with the given URI. */ public final URI toUri( URI fileSystemUri, String root, Iterable names, boolean directory) { String path = toUriPath(root, names, directory); try { // it should not suck this much to create a new URI that's the same except with a path set =( // need to do it this way for automatic path escaping return new URI( fileSystemUri.getScheme(), fileSystemUri.getUserInfo(), fileSystemUri.getHost(), fileSystemUri.getPort(), path, null, null); } catch (URISyntaxException e) { throw new AssertionError(e); } } /** * Parses a path from the given URI. */ public final ParseResult fromUri(URI uri) { return parseUriPath(uri.getPath()); } /** * Simple result of parsing a path. */ public static final class ParseResult { @Nullable private final String root; private final Iterable names; public ParseResult(@Nullable String root, Iterable names) { this.root = root; this.names = checkNotNull(names); } /** * Returns whether or not this result is an absolute path. */ public boolean isAbsolute() { return root != null; } /** * Returns whether or not this result represents a root path. */ public boolean isRoot() { return root != null && Iterables.isEmpty(names); } /** * Returns the parsed root element, or null if there was no root. */ @Nullable public String root() { return root; } /** * Returns the parsed name elements. */ public Iterable names() { return names; } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/PathURLConnection.java000066400000000000000000000107621265745405500270460ustar00rootroot00000000000000/* * Copyright 2015 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.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Ascii; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileTime; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; /** * {@code URLConnection} implementation. * * @author Colin Decker */ final class PathURLConnection extends URLConnection { /* * This implementation should be able to work for any proper file system implementation... it * might be useful to release it and make it usable by other file systems. */ private static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss \'GMT\'"; private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; private InputStream stream; private ImmutableListMultimap headers = ImmutableListMultimap.of(); PathURLConnection(URL url) { super(checkNotNull(url)); } @Override public void connect() throws IOException { if (stream != null) { return; } Path path = Paths.get(toUri(url)); long length; if (Files.isDirectory(path)) { // Match File URL behavior for directories by having the stream contain the filenames in // the directory separated by newlines. StringBuilder builder = new StringBuilder(); try (DirectoryStream files = Files.newDirectoryStream(path)) { for (Path file : files) { builder.append(file.getFileName()).append('\n'); } } byte[] bytes = builder.toString().getBytes(UTF_8); stream = new ByteArrayInputStream(bytes); length = bytes.length; } else { stream = Files.newInputStream(path); length = Files.size(path); } FileTime lastModified = Files.getLastModifiedTime(path); String contentType = MoreObjects.firstNonNull(Files.probeContentType(path), DEFAULT_CONTENT_TYPE); ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); builder.put("content-length", "" + length); builder.put("content-type", contentType); if (lastModified != null) { DateFormat format = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); format.setTimeZone(TimeZone.getTimeZone("GMT")); builder.put("last-modified", format.format(new Date(lastModified.toMillis()))); } headers = builder.build(); } private static URI toUri(URL url) throws IOException { try { return url.toURI(); } catch (URISyntaxException e) { throw new IOException("URL " + url + " cannot be converted to a URI", e); } } @Override public InputStream getInputStream() throws IOException { connect(); return stream; } @SuppressWarnings("unchecked") // safe by specification of ListMultimap.asMap() @Override public Map> getHeaderFields() { try { connect(); } catch (IOException e) { return ImmutableMap.of(); } return (ImmutableMap>) (ImmutableMap) headers.asMap(); } @Override public String getHeaderField(String name) { try { connect(); } catch (IOException e) { return null; } // no header should have more than one value return Iterables.getFirst(headers.get(Ascii.toLowerCase(name)), null); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/PollingWatchService.java000066400000000000000000000174431265745405500274660ustar00rootroot00000000000000/* * 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 java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.IOException; import java.nio.file.Path; import java.nio.file.WatchEvent; import java.nio.file.WatchService; import java.nio.file.Watchable; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** * Implementation of {@link WatchService} that polls for changes to directories at registered paths. * * @author Colin Decker */ final class PollingWatchService extends AbstractWatchService { /** * Thread factory for polling threads, which should be daemon threads so as not to keep the VM * running if the user doesn't close the watch service or the file system. */ private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder() .setNameFormat("com.google.common.jimfs.PollingWatchService-thread-%d") .setDaemon(true) .build(); private final ScheduledExecutorService pollingService = Executors.newSingleThreadScheduledExecutor(THREAD_FACTORY); /** * Map of keys to the most recent directory snapshot for each key. */ private final ConcurrentMap snapshots = new ConcurrentHashMap<>(); private final FileSystemView view; private final PathService pathService; private final FileSystemState fileSystemState; @VisibleForTesting final long interval; @VisibleForTesting final TimeUnit timeUnit; private ScheduledFuture pollingFuture; PollingWatchService( FileSystemView view, PathService pathService, FileSystemState fileSystemState, long interval, TimeUnit timeUnit) { this.view = checkNotNull(view); this.pathService = checkNotNull(pathService); this.fileSystemState = checkNotNull(fileSystemState); checkArgument(interval >= 0, "interval (%s) may not be negative", interval); this.interval = interval; this.timeUnit = checkNotNull(timeUnit); fileSystemState.register(this); } @Override public Key register(Watchable watchable, Iterable> eventTypes) throws IOException { JimfsPath path = checkWatchable(watchable); Key key = super.register(path, eventTypes); Snapshot snapshot = takeSnapshot(path); synchronized (this) { snapshots.put(key, snapshot); if (pollingFuture == null) { startPolling(); } } return key; } private JimfsPath checkWatchable(Watchable watchable) { if (!(watchable instanceof JimfsPath) || !isSameFileSystem((Path) watchable)) { throw new IllegalArgumentException( "watchable (" + watchable + ") must be a Path " + "associated with the same file system as this watch service"); } return (JimfsPath) watchable; } private boolean isSameFileSystem(Path path) { return ((JimfsFileSystem) path.getFileSystem()).getDefaultView() == view; } @VisibleForTesting synchronized boolean isPolling() { return pollingFuture != null; } @Override public synchronized void cancelled(Key key) { snapshots.remove(key); if (snapshots.isEmpty()) { stopPolling(); } } @Override public void close() { super.close(); synchronized (this) { // synchronize to ensure no new for (Key key : snapshots.keySet()) { key.cancel(); } pollingService.shutdown(); fileSystemState.unregister(this); } } private void startPolling() { pollingFuture = pollingService.scheduleAtFixedRate(pollingTask, interval, interval, timeUnit); } private void stopPolling() { pollingFuture.cancel(false); pollingFuture = null; } private final Runnable pollingTask = new Runnable() { @Override public void run() { synchronized (PollingWatchService.this) { for (Map.Entry entry : snapshots.entrySet()) { Key key = entry.getKey(); Snapshot previousSnapshot = entry.getValue(); JimfsPath path = (JimfsPath) key.watchable(); try { Snapshot newSnapshot = takeSnapshot(path); boolean posted = previousSnapshot.postChanges(newSnapshot, key); entry.setValue(newSnapshot); if (posted) { key.signal(); } } catch (IOException e) { // snapshot failed; assume file does not exist or isn't a directory // and cancel the key key.cancel(); } } } } }; private Snapshot takeSnapshot(JimfsPath path) throws IOException { return new Snapshot(view.snapshotModifiedTimes(path)); } /** * Snapshot of the state of a directory at a particular moment. */ private final class Snapshot { /** * Maps directory entry names to last modified times. */ private final ImmutableMap modifiedTimes; Snapshot(Map modifiedTimes) { this.modifiedTimes = ImmutableMap.copyOf(modifiedTimes); } /** * Posts events to the given key based on the kinds of events it subscribes to and what events * have occurred between this state and the given new state. */ boolean postChanges(Snapshot newState, Key key) { boolean changesPosted = false; if (key.subscribesTo(ENTRY_CREATE)) { Set created = Sets.difference(newState.modifiedTimes.keySet(), modifiedTimes.keySet()); for (Name name : created) { key.post(new Event<>(ENTRY_CREATE, 1, pathService.createFileName(name))); changesPosted = true; } } if (key.subscribesTo(ENTRY_DELETE)) { Set deleted = Sets.difference(modifiedTimes.keySet(), newState.modifiedTimes.keySet()); for (Name name : deleted) { key.post(new Event<>(ENTRY_DELETE, 1, pathService.createFileName(name))); changesPosted = true; } } if (key.subscribesTo(ENTRY_MODIFY)) { for (Map.Entry entry : modifiedTimes.entrySet()) { Name name = entry.getKey(); Long modifiedTime = entry.getValue(); Long newModifiedTime = newState.modifiedTimes.get(name); if (newModifiedTime != null && !modifiedTime.equals(newModifiedTime)) { key.post(new Event<>(ENTRY_MODIFY, 1, pathService.createFileName(name))); changesPosted = true; } } } return changesPosted; } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/PosixAttributeProvider.java000066400000000000000000000207641265745405500302530ustar00rootroot00000000000000/* * 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.checkNotNull; import static com.google.common.jimfs.UserLookupService.createGroupPrincipal; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.io.IOException; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileOwnerAttributeView; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.GroupPrincipal; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.nio.file.attribute.UserPrincipal; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Attribute provider that provides the {@link PosixFileAttributeView} ("posix") and allows reading * of {@link PosixFileAttributes}. * * @author Colin Decker */ final class PosixAttributeProvider extends AttributeProvider { private static final ImmutableSet ATTRIBUTES = ImmutableSet.of("group", "permissions"); private static final ImmutableSet INHERITED_VIEWS = ImmutableSet.of("basic", "owner"); private static final GroupPrincipal DEFAULT_GROUP = createGroupPrincipal("group"); private static final ImmutableSet DEFAULT_PERMISSIONS = Sets.immutableEnumSet(PosixFilePermissions.fromString("rw-r--r--")); @Override public String name() { return "posix"; } @Override public ImmutableSet inherits() { return INHERITED_VIEWS; } @Override public ImmutableSet fixedAttributes() { return ATTRIBUTES; } @SuppressWarnings("unchecked") @Override public ImmutableMap defaultValues(Map userProvidedDefaults) { Object userProvidedGroup = userProvidedDefaults.get("posix:group"); UserPrincipal group = DEFAULT_GROUP; if (userProvidedGroup != null) { if (userProvidedGroup instanceof String) { group = createGroupPrincipal((String) userProvidedGroup); } else { throw new IllegalArgumentException( "invalid type " + userProvidedGroup.getClass().getName() + " for attribute 'posix:group': should be one of " + String.class + " or " + GroupPrincipal.class); } } Object userProvidedPermissions = userProvidedDefaults.get("posix:permissions"); Set permissions = DEFAULT_PERMISSIONS; if (userProvidedPermissions != null) { if (userProvidedPermissions instanceof String) { permissions = Sets.immutableEnumSet( PosixFilePermissions.fromString((String) userProvidedPermissions)); } else if (userProvidedPermissions instanceof Set) { permissions = toPermissions((Set) userProvidedPermissions); } else { throw new IllegalArgumentException( "invalid type " + userProvidedPermissions.getClass().getName() + " for attribute 'posix:permissions': should be one of " + String.class + " or " + Set.class); } } return ImmutableMap.of( "posix:group", group, "posix:permissions", permissions); } @Nullable @Override public Object get(File file, String attribute) { switch (attribute) { case "group": return file.getAttribute("posix", "group"); case "permissions": return file.getAttribute("posix", "permissions"); default: return null; } } @Override public void set(File file, String view, String attribute, Object value, boolean create) { switch (attribute) { case "group": checkNotCreate(view, attribute, create); GroupPrincipal group = checkType(view, attribute, value, GroupPrincipal.class); if (!(group instanceof UserLookupService.JimfsGroupPrincipal)) { group = createGroupPrincipal(group.getName()); } file.setAttribute("posix", "group", group); break; case "permissions": file.setAttribute( "posix", "permissions", toPermissions(checkType(view, attribute, value, Set.class))); break; default: } } @SuppressWarnings("unchecked") // only cast after checking each element's type private static ImmutableSet toPermissions(Set set) { ImmutableSet copy = ImmutableSet.copyOf(set); for (Object obj : copy) { if (!(obj instanceof PosixFilePermission)) { throw new IllegalArgumentException( "invalid element for attribute 'posix:permissions': " + "should be Set, found element of type " + obj.getClass()); } } return Sets.immutableEnumSet((ImmutableSet) copy); } @Override public Class viewType() { return PosixFileAttributeView.class; } @Override public PosixFileAttributeView view( FileLookup lookup, ImmutableMap inheritedViews) { return new View( lookup, (BasicFileAttributeView) inheritedViews.get("basic"), (FileOwnerAttributeView) inheritedViews.get("owner")); } @Override public Class attributesType() { return PosixFileAttributes.class; } @Override public PosixFileAttributes readAttributes(File file) { return new Attributes(file); } /** * Implementation of {@link PosixFileAttributeView}. */ private static class View extends AbstractAttributeView implements PosixFileAttributeView { private final BasicFileAttributeView basicView; private final FileOwnerAttributeView ownerView; protected View( FileLookup lookup, BasicFileAttributeView basicView, FileOwnerAttributeView ownerView) { super(lookup); this.basicView = checkNotNull(basicView); this.ownerView = checkNotNull(ownerView); } @Override public String name() { return "posix"; } @Override public PosixFileAttributes readAttributes() throws IOException { return new Attributes(lookupFile()); } @Override public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException { basicView.setTimes(lastModifiedTime, lastAccessTime, createTime); } @Override public void setPermissions(Set perms) throws IOException { lookupFile().setAttribute("posix", "permissions", ImmutableSet.copyOf(perms)); } @Override public void setGroup(GroupPrincipal group) throws IOException { lookupFile().setAttribute("posix", "group", checkNotNull(group)); } @Override public UserPrincipal getOwner() throws IOException { return ownerView.getOwner(); } @Override public void setOwner(UserPrincipal owner) throws IOException { ownerView.setOwner(owner); } } /** * Implementation of {@link PosixFileAttributes}. */ static class Attributes extends BasicAttributeProvider.Attributes implements PosixFileAttributes { private final UserPrincipal owner; private final GroupPrincipal group; private final ImmutableSet permissions; @SuppressWarnings("unchecked") protected Attributes(File file) { super(file); this.owner = (UserPrincipal) file.getAttribute("owner", "owner"); this.group = (GroupPrincipal) file.getAttribute("posix", "group"); this.permissions = (ImmutableSet) file.getAttribute("posix", "permissions"); } @Override public UserPrincipal owner() { return owner; } @Override public GroupPrincipal group() { return group; } @Override public ImmutableSet permissions() { return permissions; } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/RegularFile.java000066400000000000000000000460661265745405500257560ustar00rootroot00000000000000/* * 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.Util.clear; import static com.google.common.jimfs.Util.nextPowerOf2; import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.UnsignedBytes; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.Arrays; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * A mutable, resizable store for bytes. Bytes are stored in fixed-sized byte arrays (blocks) * allocated by a {@link HeapDisk}. * * @author Colin Decker */ final class RegularFile extends File { private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final HeapDisk disk; /** Block list for the file. */ private byte[][] blocks; /** Block count for the the file, which also acts as the head of the block list. */ private int blockCount; private long size; /** * Creates a new regular file with the given ID and using the given disk. */ public static RegularFile create(int id, HeapDisk disk) { return new RegularFile(id, disk, new byte[32][], 0, 0); } RegularFile(int id, HeapDisk disk, byte[][] blocks, int blockCount, long size) { super(id); this.disk = checkNotNull(disk); this.blocks = checkNotNull(blocks); this.blockCount = blockCount; checkArgument(size >= 0); this.size = size; } private int openCount = 0; private boolean deleted = false; /** * Returns the read lock for this file. */ public Lock readLock() { return lock.readLock(); } /** * Returns the write lock for this file. */ public Lock writeLock() { return lock.writeLock(); } // lower-level methods dealing with the blocks array private void expandIfNecessary(int minBlockCount) { if (minBlockCount > blocks.length) { this.blocks = Arrays.copyOf(blocks, nextPowerOf2(minBlockCount)); } } /** * Returns the number of blocks this file contains. */ int blockCount() { return blockCount; } /** * Copies the last {@code count} blocks from this file to the end of the given target file. */ void copyBlocksTo(RegularFile target, int count) { int start = blockCount - count; int targetEnd = target.blockCount + count; target.expandIfNecessary(targetEnd); System.arraycopy(this.blocks, start, target.blocks, target.blockCount, count); target.blockCount = targetEnd; } /** * Transfers the last {@code count} blocks from this file to the end of the given target file. */ void transferBlocksTo(RegularFile target, int count) { copyBlocksTo(target, count); truncateBlocks(blockCount - count); } /** * Truncates the blocks of this file to the given block count. */ void truncateBlocks(int count) { clear(blocks, count, blockCount - count); blockCount = count; } /** * Adds the given block to the end of this file. */ void addBlock(byte[] block) { expandIfNecessary(blockCount + 1); blocks[blockCount++] = block; } /** * Gets the block at the given index in this file. */ @VisibleForTesting byte[] getBlock(int index) { return blocks[index]; } // end of lower-level methods dealing with the blocks array /** * Gets the current size of this file in bytes. Does not do locking, so should only be called * when holding a lock. */ public long sizeWithoutLocking() { return size; } // need to lock in these methods since they're defined by an interface @Override public long size() { readLock().lock(); try { return size; } finally { readLock().unlock(); } } @Override RegularFile copyWithoutContent(int id) { byte[][] copyBlocks = new byte[Math.max(blockCount * 2, 32)][]; return new RegularFile(id, disk, copyBlocks, 0, size); } @Override void copyContentTo(File file) throws IOException { RegularFile copy = (RegularFile) file; disk.allocate(copy, blockCount); for (int i = 0; i < blockCount; i++) { byte[] block = blocks[i]; byte[] copyBlock = copy.blocks[i]; System.arraycopy(block, 0, copyBlock, 0, block.length); } } @Override ReadWriteLock contentLock() { return lock; } // opened/closed/delete don't use the read/write lock... they only need to ensure that they are // synchronized among themselves @Override public synchronized void opened() { openCount++; } @Override public synchronized void closed() { if (--openCount == 0 && deleted) { deleteContents(); } } /** * Marks this file as deleted. If there are no streams or channels open to the file, its * contents are deleted if necessary. */ @Override public synchronized void deleted() { if (links() == 0) { deleted = true; if (openCount == 0) { deleteContents(); } } } /** * Deletes the contents of this file. Called when this file has been deleted and all open streams * and channels to it have been closed. */ private void deleteContents() { disk.free(this); size = 0; } /** * Truncates this file to the given {@code size}. If the given size is less than the current size * of this file, the size of the file is reduced to the given size and any bytes beyond that * size are lost. If the given size is greater than the current size of the file, this method * does nothing. Returns {@code true} if this file was modified by the call (its size changed) * and {@code false} otherwise. */ public boolean truncate(long size) { if (size >= this.size) { return false; } long lastPosition = size - 1; this.size = size; int newBlockCount = blockIndex(lastPosition) + 1; int blocksToRemove = blockCount - newBlockCount; if (blocksToRemove > 0) { disk.free(this, blocksToRemove); } return true; } /** * Prepares for a write of len bytes starting at position pos. */ private void prepareForWrite(long pos, long len) throws IOException { long end = pos + len; // allocate any additional blocks needed int lastBlockIndex = blockCount - 1; int endBlockIndex = blockIndex(end - 1); if (endBlockIndex > lastBlockIndex) { int additionalBlocksNeeded = endBlockIndex - lastBlockIndex; disk.allocate(this, additionalBlocksNeeded); } // zero bytes between current size and pos if (pos > size) { long remaining = pos - size; int blockIndex = blockIndex(size); byte[] block = blocks[blockIndex]; int off = offsetInBlock(size); remaining -= zero(block, off, length(off, remaining)); while (remaining > 0) { block = blocks[++blockIndex]; remaining -= zero(block, 0, length(remaining)); } size = pos; } } /** * Writes the given byte to this file at position {@code pos}. {@code pos} may be greater than * the current size of this file, in which case this file is resized and all bytes between the * current size and {@code pos} are set to 0. Returns the number of bytes written. * * @throws IOException if the file needs more blocks but the disk is full */ public int write(long pos, byte b) throws IOException { prepareForWrite(pos, 1); byte[] block = blocks[blockIndex(pos)]; int off = offsetInBlock(pos); block[off] = b; if (pos >= size) { size = pos + 1; } return 1; } /** * Writes {@code len} bytes starting at offset {@code off} in the given byte array to this file * starting at position {@code pos}. {@code pos} may be greater than the current size of this * file, in which case this file is resized and all bytes between the current size and {@code * pos} are set to 0. Returns the number of bytes written. * * @throws IOException if the file needs more blocks but the disk is full */ public int write(long pos, byte[] b, int off, int len) throws IOException { prepareForWrite(pos, len); if (len == 0) { return 0; } int remaining = len; int blockIndex = blockIndex(pos); byte[] block = blocks[blockIndex]; int offInBlock = offsetInBlock(pos); int written = put(block, offInBlock, b, off, length(offInBlock, remaining)); remaining -= written; off += written; while (remaining > 0) { block = blocks[++blockIndex]; written = put(block, 0, b, off, length(remaining)); remaining -= written; off += written; } long endPos = pos + len; if (endPos > size) { size = endPos; } return len; } /** * Writes all available bytes from buffer {@code buf} to this file starting at position {@code * pos}. {@code pos} may be greater than the current size of this file, in which case this file * is resized and all bytes between the current size and {@code pos} are set to 0. Returns the * number of bytes written. * * @throws IOException if the file needs more blocks but the disk is full */ public int write(long pos, ByteBuffer buf) throws IOException { int len = buf.remaining(); prepareForWrite(pos, len); if (len == 0) { return 0; } int blockIndex = blockIndex(pos); byte[] block = blocks[blockIndex]; int off = offsetInBlock(pos); put(block, off, buf); while (buf.hasRemaining()) { block = blocks[++blockIndex]; put(block, 0, buf); } long endPos = pos + len; if (endPos > size) { size = endPos; } return len; } /** * Writes all available bytes from each buffer in {@code bufs}, in order, to this file starting * at position {@code pos}. {@code pos} may be greater than the current size of this file, in * which case this file is resized and all bytes between the current size and {@code pos} are set * to 0. Returns the number of bytes written. * * @throws IOException if the file needs more blocks but the disk is full */ public long write(long pos, Iterable bufs) throws IOException { long start = pos; for (ByteBuffer buf : bufs) { pos += write(pos, buf); } return pos - start; } /** * Transfers up to {@code count} bytes from the given channel to this file starting at position * {@code pos}. Returns the number of bytes transferred. If {@code pos} is greater than the * current size of this file, the file is truncated up to size {@code pos} before writing. * * @throws IOException if the file needs more blocks but the disk is full or if reading from src * throws an exception */ public long transferFrom(ReadableByteChannel src, long pos, long count) throws IOException { prepareForWrite(pos, 0); // don't assume the full count bytes will be written if (count == 0) { return 0; } long remaining = count; int blockIndex = blockIndex(pos); byte[] block = blockForWrite(blockIndex); int off = offsetInBlock(pos); ByteBuffer buf = ByteBuffer.wrap(block, off, length(off, remaining)); long currentPos = pos; int read = 0; while (buf.hasRemaining()) { read = src.read(buf); if (read == -1) { break; } currentPos += read; remaining -= read; } // update size before trying to get next block in case the disk is out of space if (currentPos > size) { size = currentPos; } if (read != -1) { outer: while (remaining > 0) { block = blockForWrite(++blockIndex); buf = ByteBuffer.wrap(block, 0, length(remaining)); while (buf.hasRemaining()) { read = src.read(buf); if (read == -1) { break outer; } currentPos += read; remaining -= read; } if (currentPos > size) { size = currentPos; } } } if (currentPos > size) { size = currentPos; } return currentPos - pos; } /** * Reads the byte at position {@code pos} in this file as an unsigned integer in the range 0-255. * If {@code pos} is greater than or equal to the size of this file, returns -1 instead. */ public int read(long pos) { if (pos >= size) { return -1; } byte[] block = blocks[blockIndex(pos)]; int off = offsetInBlock(pos); return UnsignedBytes.toInt(block[off]); } /** * Reads up to {@code len} bytes starting at position {@code pos} in this file to the given byte * array starting at offset {@code off}. Returns the number of bytes actually read or -1 if {@code * pos} is greater than or equal to the size of this file. */ public int read(long pos, byte[] b, int off, int len) { // since max is len (an int), result is guaranteed to be an int int bytesToRead = (int) bytesToRead(pos, len); if (bytesToRead > 0) { int remaining = bytesToRead; int blockIndex = blockIndex(pos); byte[] block = blocks[blockIndex]; int offsetInBlock = offsetInBlock(pos); int read = get(block, offsetInBlock, b, off, length(offsetInBlock, remaining)); remaining -= read; off += read; while (remaining > 0) { int index = ++blockIndex; block = blocks[index]; read = get(block, 0, b, off, length(remaining)); remaining -= read; off += read; } } return bytesToRead; } /** * Reads up to {@code buf.remaining()} bytes starting at position {@code pos} in this file to the * given buffer. Returns the number of bytes read or -1 if {@code pos} is greater than or equal to * the size of this file. */ public int read(long pos, ByteBuffer buf) { // since max is buf.remaining() (an int), result is guaranteed to be an int int bytesToRead = (int) bytesToRead(pos, buf.remaining()); if (bytesToRead > 0) { int remaining = bytesToRead; int blockIndex = blockIndex(pos); byte[] block = blocks[blockIndex]; int off = offsetInBlock(pos); remaining -= get(block, off, buf, length(off, remaining)); while (remaining > 0) { int index = ++blockIndex; block = blocks[index]; remaining -= get(block, 0, buf, length(remaining)); } } return bytesToRead; } /** * Reads up to the total {@code remaining()} number of bytes in each of {@code bufs} starting at * position {@code pos} in this file to the given buffers, in order. Returns the number of bytes * read or -1 if {@code pos} is greater than or equal to the size of this file. */ public long read(long pos, Iterable bufs) { if (pos >= size()) { return -1; } long start = pos; for (ByteBuffer buf : bufs) { int read = read(pos, buf); if (read == -1) { break; } else { pos += read; } } return pos - start; } /** * Transfers up to {@code count} bytes to the given channel starting at position {@code pos} in * this file. Returns the number of bytes transferred, possibly 0. Note that unlike all other * read methods in this class, this method does not return -1 if {@code pos} is greater than or * equal to the current size. This for consistency with {@link FileChannel#transferTo}, which * this method is primarily intended as an implementation of. */ public long transferTo(long pos, long count, WritableByteChannel dest) throws IOException { long bytesToRead = bytesToRead(pos, count); if (bytesToRead > 0) { long remaining = bytesToRead; int blockIndex = blockIndex(pos); byte[] block = blocks[blockIndex]; int off = offsetInBlock(pos); ByteBuffer buf = ByteBuffer.wrap(block, off, length(off, remaining)); while (buf.hasRemaining()) { remaining -= dest.write(buf); } buf.clear(); while (remaining > 0) { int index = ++blockIndex; block = blocks[index]; buf = ByteBuffer.wrap(block, 0, length(remaining)); while (buf.hasRemaining()) { remaining -= dest.write(buf); } buf.clear(); } } return Math.max(bytesToRead, 0); // don't return -1 for this method } /** * Gets the block at the given index, expanding to create the block if necessary. */ private byte[] blockForWrite(int index) throws IOException { if (index >= blockCount) { int additionalBlocksNeeded = index - blockCount + 1; disk.allocate(this, additionalBlocksNeeded); } return blocks[index]; } private int blockIndex(long position) { return (int) (position / disk.blockSize()); } private int offsetInBlock(long position) { return (int) (position % disk.blockSize()); } private int length(long max) { return (int) Math.min(disk.blockSize(), max); } private int length(int off, long max) { return (int) Math.min(disk.blockSize() - off, max); } /** * Returns the number of bytes that can be read starting at position {@code pos} (up to a maximum * of {@code max}) or -1 if {@code pos} is greater than or equal to the current size. */ private long bytesToRead(long pos, long max) { long available = size - pos; if (available <= 0) { return -1; } return Math.min(available, max); } /** * Zeroes len bytes in the given block starting at the given offset. Returns len. */ private static int zero(byte[] block, int offset, int len) { Util.zero(block, offset, len); return len; } /** * Puts the given slice of the given array at the given offset in the given block. */ private static int put(byte[] block, int offset, byte[] b, int off, int len) { System.arraycopy(b, off, block, offset, len); return len; } /** * Puts the contents of the given byte buffer at the given offset in the given block. */ private static int put(byte[] block, int offset, ByteBuffer buf) { int len = Math.min(block.length - offset, buf.remaining()); buf.get(block, offset, len); return len; } /** * Reads len bytes starting at the given offset in the given block into the given slice of the * given byte array. */ private static int get(byte[] block, int offset, byte[] b, int off, int len) { System.arraycopy(block, offset, b, off, len); return len; } /** * Reads len bytes starting at the given offset in the given block into the given byte buffer. */ private static int get(byte[] block, int offset, ByteBuffer buf, int len) { buf.put(block, offset, len); return len; } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/StandardAttributeProviders.java000066400000000000000000000037231265745405500310700ustar00rootroot00000000000000/* * 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 com.google.common.collect.ImmutableMap; import javax.annotation.Nullable; /** * Static registry of {@link AttributeProvider} implementations for the standard set of file * attribute views Jimfs supports. * * @author Colin Decker */ final class StandardAttributeProviders { private StandardAttributeProviders() {} private static final ImmutableMap PROVIDERS = new ImmutableMap.Builder() .put("basic", new BasicAttributeProvider()) .put("owner", new OwnerAttributeProvider()) .put("posix", new PosixAttributeProvider()) .put("dos", new DosAttributeProvider()) .put("acl", new AclAttributeProvider()) .put("user", new UserDefinedAttributeProvider()) .build(); /** * Returns the attribute provider for the given view, or {@code null} if the given view is not * one of the attribute views this supports. */ @Nullable public static AttributeProvider get(String view) { AttributeProvider provider = PROVIDERS.get(view); if (provider == null && view.equals("unix")) { // create a new UnixAttributeProvider per file system, as it does some caching that should be // cleaned up when the file system is garbage collected return new UnixAttributeProvider(); } return provider; } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/SymbolicLink.java000066400000000000000000000025431265745405500261440ustar00rootroot00000000000000/* * Copyright 2014 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.checkNotNull; /** * A symbolic link file, containing a {@linkplain JimfsPath path}. * * @author Colin Decker */ final class SymbolicLink extends File { private final JimfsPath target; /** * Creates a new symbolic link with the given ID and target. */ public static SymbolicLink create(int id, JimfsPath target) { return new SymbolicLink(id, target); } private SymbolicLink(int id, JimfsPath target) { super(id); this.target = checkNotNull(target); } /** * Returns the target path of this symbolic link. */ JimfsPath target() { return target; } @Override File copyWithoutContent(int id) { return SymbolicLink.create(id, target); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/SystemJimfsFileSystemProvider.java000066400000000000000000000233721265745405500315450ustar00rootroot00000000000000/* * 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.Strings.isNullOrEmpty; import static com.google.common.jimfs.Jimfs.URI_SCHEME; import com.google.auto.service.AutoService; import com.google.common.collect.MapMaker; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.nio.channels.SeekableByteChannel; import java.nio.file.AccessMode; import java.nio.file.CopyOption; import java.nio.file.DirectoryStream; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.FileSystemAlreadyExistsException; import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; import java.nio.file.LinkOption; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.nio.file.spi.FileSystemProvider; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; /** * {@link FileSystemProvider} implementation for Jimfs that is loaded by the system as a service. * This implementation only serves as a cache for file system instances and does not implement * actual file system operations. * *

While this class is public, it should not be used directly. To create a new file system * instance, see {@link Jimfs}. For other operations, use the public APIs in {@code java.nio.file}. * * @author Colin Decker * @since 1.1 */ @AutoService(FileSystemProvider.class) public final class SystemJimfsFileSystemProvider extends FileSystemProvider { /** * Env map key that maps to the already-created {@code FileSystem} instance in * {@code newFileSystem}. */ static final String FILE_SYSTEM_KEY = "fileSystem"; /** * Cache of file systems that have been created but not closed. * *

This cache is static to ensure that even when this provider isn't loaded by the system class * loader, meaning that a new instance of it must be created each time one of the methods on * {@link FileSystems} or {@link Paths#get(URI)} is called, cached file system instances are still * available. * *

The cache uses weak values so that it doesn't prevent file systems that are created but not * closed from being garbage collected if no references to them are held elsewhere. This is a * compromise between ensuring that any file URI continues to work as long as the file system * hasn't been closed (which is technically the correct thing to do but unlikely to be something * that most users care about) and ensuring that users don't get unexpected leaks of large amounts * of memory because they're creating many file systems in tests but forgetting to close them * (which seems likely to happen sometimes). Users that want to ensure that a file system won't be * garbage collected just need to ensure they hold a reference to it somewhere for as long as they * need it to stick around. */ private static final ConcurrentMap fileSystems = new MapMaker().weakValues().makeMap(); /** * @deprecated Not intended to be called directly; this class is only for use by Java itself. */ @Deprecated public SystemJimfsFileSystemProvider() {} // a public, no-arg constructor is required @Override public String getScheme() { return URI_SCHEME; } @Override public FileSystem newFileSystem(URI uri, Map env) throws IOException { checkArgument( uri.getScheme().equalsIgnoreCase(URI_SCHEME), "uri (%s) scheme must be '%s'", uri, URI_SCHEME); checkArgument( isValidFileSystemUri(uri), "uri (%s) may not have a path, query or fragment", uri); checkArgument( env.get(FILE_SYSTEM_KEY) instanceof FileSystem, "env map (%s) must contain key '%s' mapped to an instance of %s", env, FILE_SYSTEM_KEY, FileSystem.class); FileSystem fileSystem = (FileSystem) env.get(FILE_SYSTEM_KEY); if (fileSystems.putIfAbsent(uri, fileSystem) != null) { throw new FileSystemAlreadyExistsException(uri.toString()); } return fileSystem; } @Override public FileSystem getFileSystem(URI uri) { FileSystem fileSystem = fileSystems.get(uri); if (fileSystem == null) { throw new FileSystemNotFoundException(uri.toString()); } return fileSystem; } @Override public Path getPath(URI uri) { checkArgument( URI_SCHEME.equalsIgnoreCase(uri.getScheme()), "uri scheme does not match this provider: %s", uri); String path = uri.getPath(); checkArgument(!isNullOrEmpty(path), "uri must have a path: %s", uri); return toPath(getFileSystem(toFileSystemUri(uri)), uri); } /** * Returns whether or not the given URI is valid as a base file system URI. It must not have a * path, query or fragment. */ private static boolean isValidFileSystemUri(URI uri) { // would like to just check null, but fragment appears to be the empty string when not present return isNullOrEmpty(uri.getPath()) && isNullOrEmpty(uri.getQuery()) && isNullOrEmpty(uri.getFragment()); } /** * Returns the given URI with any path, query or fragment stripped off. */ private static URI toFileSystemUri(URI uri) { try { return new URI( uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null); } catch (URISyntaxException e) { throw new AssertionError(e); } } /** * Invokes the {@code toPath(URI)} method on the given {@code FileSystem}. */ private static Path toPath(FileSystem fileSystem, URI uri) { // We have to invoke this method by reflection because while the file system should be // an instance of JimfsFileSystem, it may be loaded by a different class loader and as // such appear to be a totally different class. try { Method toPath = fileSystem.getClass().getDeclaredMethod("toPath", URI.class); return (Path) toPath.invoke(fileSystem, uri); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("invalid file system: " + fileSystem); } catch (InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } } @Override public FileSystem newFileSystem(Path path, Map env) throws IOException { FileSystemProvider realProvider = path.getFileSystem().provider(); return realProvider.newFileSystem(path, env); } /** * Returns a runnable that, when run, removes the file system with the given URI from this * provider. */ @SuppressWarnings("unused") // called via reflection public static Runnable removeFileSystemRunnable(final URI uri) { return new Runnable() { @Override public void run() { fileSystems.remove(uri); } }; } @Override public SeekableByteChannel newByteChannel( Path path, Set options, FileAttribute... attrs) throws IOException { throw new UnsupportedOperationException(); } @Override public DirectoryStream newDirectoryStream( Path dir, DirectoryStream.Filter filter) throws IOException { throw new UnsupportedOperationException(); } @Override public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { throw new UnsupportedOperationException(); } @Override public void delete(Path path) throws IOException { throw new UnsupportedOperationException(); } @Override public void copy(Path source, Path target, CopyOption... options) throws IOException { throw new UnsupportedOperationException(); } @Override public void move(Path source, Path target, CopyOption... options) throws IOException { throw new UnsupportedOperationException(); } @Override public boolean isSameFile(Path path, Path path2) throws IOException { throw new UnsupportedOperationException(); } @Override public boolean isHidden(Path path) throws IOException { throw new UnsupportedOperationException(); } @Override public FileStore getFileStore(Path path) throws IOException { throw new UnsupportedOperationException(); } @Override public void checkAccess(Path path, AccessMode... modes) throws IOException { throw new UnsupportedOperationException(); } @Override public V getFileAttributeView( Path path, Class type, LinkOption... options) { throw new UnsupportedOperationException(); } @Override public A readAttributes( Path path, Class type, LinkOption... options) throws IOException { throw new UnsupportedOperationException(); } @Override public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { throw new UnsupportedOperationException(); } @Override public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { throw new UnsupportedOperationException(); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/UnixAttributeProvider.java000066400000000000000000000123721265745405500300700ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.GroupPrincipal; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.UserPrincipal; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; /** * Attribute provider that provides the "unix" attribute view. * * @author Colin Decker */ final class UnixAttributeProvider extends AttributeProvider { private static final ImmutableSet ATTRIBUTES = ImmutableSet.of("uid", "ino", "dev", "nlink", "rdev", "ctime", "mode", "gid"); private static final ImmutableSet INHERITED_VIEWS = ImmutableSet.of("basic", "owner", "posix"); private final AtomicInteger uidGenerator = new AtomicInteger(); private final ConcurrentMap idCache = new ConcurrentHashMap<>(); @Override public String name() { return "unix"; } @Override public ImmutableSet inherits() { return INHERITED_VIEWS; } @Override public ImmutableSet fixedAttributes() { return ATTRIBUTES; } @Override public Class viewType() { return UnixFileAttributeView.class; } @Override public UnixFileAttributeView view( FileLookup lookup, ImmutableMap inheritedViews) { // This method should not be called... and it cannot be called through the public APIs in // java.nio.file since there is no public UnixFileAttributeView type. throw new UnsupportedOperationException(); } // TODO(cgdecker): Since we can now guarantee that the owner/group for an file are our own // implementation of UserPrincipal/GroupPrincipal, it would be nice to have them store a unique // ID themselves and just get that rather than doing caching here. Then this could be a singleton // like the rest of the AttributeProviders. However, that would require a way for the owner/posix // providers to create their default principals using the lookup service for the specific file // system. /** * Returns an ID that is guaranteed to be the same for any invocation with equal objects. */ private Integer getUniqueId(Object object) { Integer id = idCache.get(object); if (id == null) { id = uidGenerator.incrementAndGet(); Integer existing = idCache.putIfAbsent(object, id); if (existing != null) { return existing; } } return id; } @SuppressWarnings("unchecked") @Override public Object get(File file, String attribute) { switch (attribute) { case "uid": UserPrincipal user = (UserPrincipal) file.getAttribute("owner", "owner"); return getUniqueId(user); case "gid": GroupPrincipal group = (GroupPrincipal) file.getAttribute("posix", "group"); return getUniqueId(group); case "mode": Set permissions = (Set) file.getAttribute("posix", "permissions"); return toMode(permissions); case "ctime": return FileTime.fromMillis(file.getCreationTime()); case "rdev": return 0L; case "dev": return 1L; case "ino": return file.id(); case "nlink": return file.links(); default: return null; } } @Override public void set(File file, String view, String attribute, Object value, boolean create) { throw unsettable(view, attribute); } @SuppressWarnings("OctalInteger") private static int toMode(Set permissions) { int result = 0; for (PosixFilePermission permission : permissions) { checkNotNull(permission); switch (permission) { case OWNER_READ: result |= 0400; break; case OWNER_WRITE: result |= 0200; break; case OWNER_EXECUTE: result |= 0100; break; case GROUP_READ: result |= 0040; break; case GROUP_WRITE: result |= 0020; break; case GROUP_EXECUTE: result |= 0010; break; case OTHERS_READ: result |= 0004; break; case OTHERS_WRITE: result |= 0002; break; case OTHERS_EXECUTE: result |= 0001; break; default: throw new AssertionError(); // no other possible values } } return result; } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/UnixFileAttributeView.java000066400000000000000000000015421265745405500300050ustar00rootroot00000000000000/* * 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 java.nio.file.attribute.FileAttributeView; /** * Dummy view interface for the "unix" view, which doesn't have a public view interface. * * @author Colin Decker */ interface UnixFileAttributeView extends FileAttributeView {} jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/UnixPathType.java000066400000000000000000000043531265745405500261500ustar00rootroot00000000000000/* * 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 java.nio.file.InvalidPathException; import javax.annotation.Nullable; /** * Unix-style path type. * * @author Colin Decker */ final class UnixPathType extends PathType { /** * Unix path type. */ static final PathType INSTANCE = new UnixPathType(); private UnixPathType() { super(false, '/'); } @Override public ParseResult parsePath(String path) { if (path.isEmpty()) { return emptyPath(); } checkValid(path); String root = path.startsWith("/") ? "/" : null; return new ParseResult(root, splitter().split(path)); } private static void checkValid(String path) { int nulIndex = path.indexOf('\0'); if (nulIndex != -1) { throw new InvalidPathException(path, "nul character not allowed", nulIndex); } } @Override public String toString(@Nullable String root, Iterable names) { StringBuilder builder = new StringBuilder(); if (root != null) { builder.append(root); } joiner().appendTo(builder, names); return builder.toString(); } @Override public String toUriPath(String root, Iterable names, boolean directory) { StringBuilder builder = new StringBuilder(); for (String name : names) { builder.append('/').append(name); } if (directory || builder.length() == 0) { builder.append('/'); } return builder.toString(); } @Override public ParseResult parseUriPath(String uriPath) { checkArgument(uriPath.startsWith("/"), "uriPath (%s) must start with /", uriPath); return parsePath(uriPath); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/UserDefinedAttributeProvider.java000066400000000000000000000111661265745405500313420ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.UserDefinedFileAttributeView; import java.util.List; /** * Attribute provider that provides the {@link UserDefinedFileAttributeView} ("user"). Unlike most * other attribute providers, this one has no pre-defined set of attributes. Rather, it allows * arbitrary user defined attributes to be set (as {@code ByteBuffer} or {@code byte[]}) and read * (as {@code byte[]}). * * @author Colin Decker */ final class UserDefinedAttributeProvider extends AttributeProvider { UserDefinedAttributeProvider() {} @Override public String name() { return "user"; } @Override public ImmutableSet fixedAttributes() { // no fixed set of attributes for this view return ImmutableSet.of(); } @Override public boolean supports(String attribute) { // any attribute name is supported return true; } @Override public ImmutableSet attributes(File file) { return userDefinedAttributes(file); } private static ImmutableSet userDefinedAttributes(File file) { ImmutableSet.Builder builder = ImmutableSet.builder(); for (String attribute : file.getAttributeNames("user")) { builder.add(attribute); } return builder.build(); } @Override public Object get(File file, String attribute) { Object value = file.getAttribute("user", attribute); if (value instanceof byte[]) { byte[] bytes = (byte[]) value; return bytes.clone(); } return null; } @Override public void set(File file, String view, String attribute, Object value, boolean create) { checkNotNull(value); checkNotCreate(view, attribute, create); byte[] bytes; if (value instanceof byte[]) { bytes = ((byte[]) value).clone(); } else if (value instanceof ByteBuffer) { // value instanceof ByteBuffer ByteBuffer buffer = (ByteBuffer) value; bytes = new byte[buffer.remaining()]; buffer.get(bytes); } else { throw invalidType(view, attribute, value, byte[].class, ByteBuffer.class); } file.setAttribute("user", attribute, bytes); } @Override public Class viewType() { return UserDefinedFileAttributeView.class; } @Override public UserDefinedFileAttributeView view( FileLookup lookup, ImmutableMap inheritedViews) { return new View(lookup); } /** * Implementation of {@link UserDefinedFileAttributeView}. */ private static class View extends AbstractAttributeView implements UserDefinedFileAttributeView { public View(FileLookup lookup) { super(lookup); } @Override public String name() { return "user"; } @Override public List list() throws IOException { return userDefinedAttributes(lookupFile()).asList(); } private byte[] getStoredBytes(String name) throws IOException { byte[] bytes = (byte[]) lookupFile().getAttribute(name(), name); if (bytes == null) { throw new IllegalArgumentException("attribute '" + name() + ":" + name + "' is not set"); } return bytes; } @Override public int size(String name) throws IOException { return getStoredBytes(name).length; } @Override public int read(String name, ByteBuffer dst) throws IOException { byte[] bytes = getStoredBytes(name); dst.put(bytes); return bytes.length; } @Override public int write(String name, ByteBuffer src) throws IOException { byte[] bytes = new byte[src.remaining()]; src.get(bytes); lookupFile().setAttribute(name(), name, bytes); return bytes.length; } @Override public void delete(String name) throws IOException { lookupFile().deleteAttribute(name(), name); } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/UserLookupService.java000066400000000000000000000062751265745405500272040ustar00rootroot00000000000000/* * 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.checkNotNull; import java.io.IOException; import java.nio.file.attribute.GroupPrincipal; import java.nio.file.attribute.UserPrincipal; import java.nio.file.attribute.UserPrincipalLookupService; import java.nio.file.attribute.UserPrincipalNotFoundException; /** * {@link UserPrincipalLookupService} implementation. * * @author Colin Decker */ final class UserLookupService extends UserPrincipalLookupService { private final boolean supportsGroups; public UserLookupService(boolean supportsGroups) { this.supportsGroups = supportsGroups; } @Override public UserPrincipal lookupPrincipalByName(String name) { return createUserPrincipal(name); } @Override public GroupPrincipal lookupPrincipalByGroupName(String group) throws IOException { if (!supportsGroups) { throw new UserPrincipalNotFoundException(group); // required by spec } return createGroupPrincipal(group); } /** * Creates a {@link UserPrincipal} for the given user name. */ static UserPrincipal createUserPrincipal(String name) { return new JimfsUserPrincipal(name); } /** * Creates a {@link GroupPrincipal} for the given group name. */ static GroupPrincipal createGroupPrincipal(String name) { return new JimfsGroupPrincipal(name); } /** * Base class for {@link UserPrincipal} and {@link GroupPrincipal} implementations. */ private abstract static class NamedPrincipal implements UserPrincipal { protected final String name; private NamedPrincipal(String name) { this.name = checkNotNull(name); } @Override public final String getName() { return name; } @Override public final int hashCode() { return name.hashCode(); } @Override public final String toString() { return name; } } /** * {@link UserPrincipal} implementation. */ static final class JimfsUserPrincipal extends NamedPrincipal { private JimfsUserPrincipal(String name) { super(name); } @Override public boolean equals(Object obj) { return obj instanceof JimfsUserPrincipal && getName().equals(((JimfsUserPrincipal) obj).getName()); } } /** * {@link GroupPrincipal} implementation. */ static final class JimfsGroupPrincipal extends NamedPrincipal implements GroupPrincipal { private JimfsGroupPrincipal(String name) { super(name); } @Override public boolean equals(Object obj) { return obj instanceof JimfsGroupPrincipal && ((JimfsGroupPrincipal) obj).name.equals(name); } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/Util.java000066400000000000000000000072521265745405500244640ustar00rootroot00000000000000/* * 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 com.google.common.collect.ImmutableCollection; /** * Miscellaneous static utility methods. * * @author Colin Decker * @author Austin Appleby */ final class Util { private Util() {} /** * Returns the next power of 2 >= n. */ public static int nextPowerOf2(int n) { if (n == 0) { return 1; } int b = Integer.highestOneBit(n); return b == n ? n : b << 1; } /** * Checks that the given number is not negative, throwing IAE if it is. The given description * describes the number in the exception message. */ static void checkNotNegative(long n, String description) { checkArgument(n >= 0, "%s must not be negative: %s", description, n); } /** * Checks that no element in the given iterable is null, throwing NPE if any is. */ static void checkNoneNull(Iterable objects) { if (!(objects instanceof ImmutableCollection)) { for (Object o : objects) { checkNotNull(o); } } } private static final int C1 = 0xcc9e2d51; private static final int C2 = 0x1b873593; /* * This method was rewritten in Java from an intermediate step of the Murmur hash function in * http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp, which contained the * following header: * * MurmurHash3 was written by Austin Appleby, and is placed in the public domain. The author * hereby disclaims copyright to this source code. */ static int smearHash(int hashCode) { return C2 * Integer.rotateLeft(hashCode * C1, 15); } private static final int ARRAY_LEN = 8192; private static final byte[] ZERO_ARRAY = new byte[ARRAY_LEN]; private static final byte[][] NULL_ARRAY = new byte[ARRAY_LEN][]; /** * Zeroes all bytes between off (inclusive) and off + len (exclusive) in the given array. */ static void zero(byte[] bytes, int off, int len) { // this is significantly faster than looping or Arrays.fill (which loops), particularly when // the length of the slice to be zeroed is <= to ARRAY_LEN (in that case, it's faster by a // factor of 2) int remaining = len; while (remaining > ARRAY_LEN) { System.arraycopy(ZERO_ARRAY, 0, bytes, off, ARRAY_LEN); off += ARRAY_LEN; remaining -= ARRAY_LEN; } System.arraycopy(ZERO_ARRAY, 0, bytes, off, remaining); } /** * Clears (sets to null) all blocks between off (inclusive) and off + len (exclusive) in the * given array. */ static void clear(byte[][] blocks, int off, int len) { // this is significantly faster than looping or Arrays.fill (which loops), particularly when // the length of the slice to be cleared is <= to ARRAY_LEN (in that case, it's faster by a // factor of 2) int remaining = len; while (remaining > ARRAY_LEN) { System.arraycopy(NULL_ARRAY, 0, blocks, off, ARRAY_LEN); off += ARRAY_LEN; remaining -= ARRAY_LEN; } System.arraycopy(NULL_ARRAY, 0, blocks, off, remaining); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/WatchServiceConfiguration.java000066400000000000000000000052251265745405500306640ustar00rootroot00000000000000/* * Copyright 2016 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 java.util.concurrent.TimeUnit.SECONDS; import java.nio.file.WatchService; import java.util.concurrent.TimeUnit; /** * Configuration for the {@link WatchService} implementation used by a file system. * * @author Colin Decker * @since 1.1 */ public abstract class WatchServiceConfiguration { /** * The default configuration that's used if the user doesn't provide anything more specific. */ static final WatchServiceConfiguration DEFAULT = polling(5, SECONDS); /** * Returns a configuration for a {@link WatchService} that polls watched directories for changes * every {@code interval} of the given {@code timeUnit} (e.g. every 5 * {@link TimeUnit#SECONDS seconds}). */ public static WatchServiceConfiguration polling(long interval, TimeUnit timeUnit) { return new PollingConfig(interval, timeUnit); } WatchServiceConfiguration() {} /** * Creates a new {@link AbstractWatchService} implementation. */ // return type and parameters of this method subject to change if needed for any future // implementations abstract AbstractWatchService newWatchService(FileSystemView view, PathService pathService); /** * Implementation for {@link #polling}. */ private static final class PollingConfig extends WatchServiceConfiguration { private final long interval; private final TimeUnit timeUnit; private PollingConfig(long interval, TimeUnit timeUnit) { checkArgument(interval > 0, "interval (%s) must be positive", interval); this.interval = interval; this.timeUnit = checkNotNull(timeUnit); } @Override AbstractWatchService newWatchService(FileSystemView view, PathService pathService) { return new PollingWatchService(view, pathService, view.state(), interval, timeUnit); } @Override public String toString() { return "WatchServiceConfiguration.polling(" + interval + ", " + timeUnit + ")"; } } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java000066400000000000000000000154401265745405500266560ustar00rootroot00000000000000/* * 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 java.nio.file.InvalidPathException; import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; /** * Windows-style path type. * * @author Colin Decker */ final class WindowsPathType extends PathType { /** * Windows path type. */ static final WindowsPathType INSTANCE = new WindowsPathType(); /** * Matches the C:foo\bar path format, which has a root (C:) and names (foo\bar) and matches * a path relative to the working directory on that drive. Currently can't support that format * as it requires behavior that differs completely from Unix. */ // TODO(cgdecker): Can probably support this at some point // It would require: // - A method like PathType.isAbsolute(Path) or something to that effect; this would allow // WindowsPathType to distinguish between an absolute root path (C:\) and a relative root // path (C:) // - Special handling for relative paths that have a root. This handling would determine the // root directory and then determine the working directory from there. The file system would // still have one working directory; for the root that working directory is under, it is the // working directory. For every other root, the root itself is the working directory. private static final Pattern WORKING_DIR_WITH_DRIVE = Pattern.compile("^[a-zA-Z]:([^\\\\].*)?$"); /** * Pattern for matching trailing spaces in file names. */ private static final Pattern TRAILING_SPACES = Pattern.compile("[ ]+(\\\\|$)"); private WindowsPathType() { super(true, '\\', '/'); } @Override public ParseResult parsePath(String path) { String original = path; path = path.replace('/', '\\'); if (WORKING_DIR_WITH_DRIVE.matcher(path).matches()) { throw new InvalidPathException( original, "Jimfs does not currently support the Windows syntax for a relative path " + "on a specific drive (e.g. \"C:foo\\bar\""); } String root; if (path.startsWith("\\\\")) { root = parseUncRoot(path, original); } else if (path.startsWith("\\")) { throw new InvalidPathException( original, "Jimfs does not currently support the Windows syntax for an absolute path " + "on the current drive (e.g. \"\\foo\\bar\""); } else { root = parseDriveRoot(path); } // check for root.length() > 3 because only "C:\" type roots are allowed to have : int startIndex = root == null || root.length() > 3 ? 0 : root.length(); for (int i = startIndex; i < path.length(); i++) { char c = path.charAt(i); if (isReserved(c)) { throw new InvalidPathException(original, "Illegal char <" + c + ">", i); } } Matcher trailingSpaceMatcher = TRAILING_SPACES.matcher(path); if (trailingSpaceMatcher.find()) { throw new InvalidPathException(original, "Trailing char < >", trailingSpaceMatcher.start()); } if (root != null) { path = path.substring(root.length()); if (!root.endsWith("\\")) { root = root + "\\"; } } return new ParseResult(root, splitter().split(path)); } /** * Pattern for matching UNC \\host\share root syntax. */ private static final Pattern UNC_ROOT = Pattern.compile("^(\\\\\\\\)([^\\\\]+)?(\\\\[^\\\\]+)?"); /** * Parse the root of a UNC-style path, throwing an exception if the path does not start with * a valid UNC root. */ private String parseUncRoot(String path, String original) { Matcher uncMatcher = UNC_ROOT.matcher(path); if (uncMatcher.find()) { String host = uncMatcher.group(2); if (host == null) { throw new InvalidPathException(original, "UNC path is missing hostname"); } String share = uncMatcher.group(3); if (share == null) { throw new InvalidPathException(original, "UNC path is missing sharename"); } return path.substring(uncMatcher.start(), uncMatcher.end()); } else { // probably shouldn't ever reach this throw new InvalidPathException(original, "Invalid UNC path"); } } /** * Pattern for matching normal C:\ drive letter root syntax. */ private static final Pattern DRIVE_LETTER_ROOT = Pattern.compile("^[a-zA-Z]:\\\\"); /** * Parses a normal drive-letter root, e.g. "C:\". */ @Nullable private String parseDriveRoot(String path) { Matcher drivePathMatcher = DRIVE_LETTER_ROOT.matcher(path); if (drivePathMatcher.find()) { return path.substring(drivePathMatcher.start(), drivePathMatcher.end()); } return null; } /** * Checks if c is one of the reserved characters that aren't allowed in Windows file names. */ private static boolean isReserved(char c) { switch (c) { case '<': case '>': case ':': case '"': case '|': case '?': case '*': return true; default: return c <= 31; } } @Override public String toString(@Nullable String root, Iterable names) { StringBuilder builder = new StringBuilder(); if (root != null) { builder.append(root); } joiner().appendTo(builder, names); return builder.toString(); } @Override public String toUriPath(String root, Iterable names, boolean directory) { if (root.startsWith("\\\\")) { root = root.replace('\\', '/'); } else { root = "/" + root.replace('\\', '/'); } StringBuilder builder = new StringBuilder(); builder.append(root); Iterator iter = names.iterator(); if (iter.hasNext()) { builder.append(iter.next()); while (iter.hasNext()) { builder.append('/').append(iter.next()); } } if (directory && builder.charAt(builder.length() - 1) != '/') { builder.append('/'); } return builder.toString(); } @Override public ParseResult parseUriPath(String uriPath) { uriPath = uriPath.replace('/', '\\'); if (uriPath.charAt(0) == '\\' && uriPath.charAt(1) != '\\') { // non-UNC path, so the leading / was just there for the URI path format and isn't part // of what should be parsed uriPath = uriPath.substring(1); } return parsePath(uriPath); } } jimfs-1.1/jimfs/src/main/java/com/google/common/jimfs/package-info.java000066400000000000000000000016721265745405500260730ustar00rootroot00000000000000/* * 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 containing the Jimfs file system API and implementation. Most users should only need to * use the {@link com.google.common.jimfs.Jimfs Jimfs} and * {@link com.google.common.jimfs.Configuration Configuration} classes. */ @ParametersAreNonnullByDefault package com.google.common.jimfs; import javax.annotation.ParametersAreNonnullByDefault; jimfs-1.1/jimfs/src/test/000077500000000000000000000000001265745405500153565ustar00rootroot00000000000000jimfs-1.1/jimfs/src/test/java/000077500000000000000000000000001265745405500162775ustar00rootroot00000000000000jimfs-1.1/jimfs/src/test/java/com/000077500000000000000000000000001265745405500170555ustar00rootroot00000000000000jimfs-1.1/jimfs/src/test/java/com/google/000077500000000000000000000000001265745405500203315ustar00rootroot00000000000000jimfs-1.1/jimfs/src/test/java/com/google/common/000077500000000000000000000000001265745405500216215ustar00rootroot00000000000000jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/000077500000000000000000000000001265745405500227315ustar00rootroot00000000000000jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/AbstractAttributeProviderTest.java000066400000000000000000000103051265745405500315750ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableMap; import org.junit.Before; import java.io.IOException; import java.nio.file.attribute.FileAttributeView; import java.util.Map; import java.util.Set; /** * Abstract base class for tests of individual {@link AttributeProvider} implementations. * * @author Colin Decker */ public abstract class AbstractAttributeProviderTest

{ protected static final ImmutableMap NO_INHERITED_VIEWS = ImmutableMap.of(); protected P provider; protected File file; /** * Create the provider being tested. */ protected abstract P createProvider(); /** * Creates the set of providers the provider being tested depends on. */ protected abstract Set createInheritedProviders(); protected FileLookup fileLookup() { return new FileLookup() { @Override public File lookup() throws IOException { return file; } }; } @Before public void setUp() { this.provider = createProvider(); this.file = Directory.create(0); Map defaultValues = createDefaultValues(); setDefaultValues(file, provider, defaultValues); Set inheritedProviders = createInheritedProviders(); for (AttributeProvider inherited : inheritedProviders) { setDefaultValues(file, inherited, defaultValues); } } private static void setDefaultValues( File file, AttributeProvider provider, Map defaultValues) { Map defaults = provider.defaultValues(defaultValues); for (Map.Entry entry : defaults.entrySet()) { int separatorIndex = entry.getKey().indexOf(':'); String view = entry.getKey().substring(0, separatorIndex); String attr = entry.getKey().substring(separatorIndex + 1); file.setAttribute(view, attr, entry.getValue()); } } protected Map createDefaultValues() { return ImmutableMap.of(); } // assertions protected void assertSupportsAll(String... attributes) { for (String attribute : attributes) { assertThat(provider.supports(attribute)).isTrue(); } } protected void assertContainsAll(File file, ImmutableMap expectedAttributes) { for (Map.Entry entry : expectedAttributes.entrySet()) { String attribute = entry.getKey(); Object value = entry.getValue(); assertThat(provider.get(file, attribute)).isEqualTo(value); } } protected void assertSetAndGetSucceeds(String attribute, Object value) { assertSetAndGetSucceeds(attribute, value, false); } protected void assertSetAndGetSucceedsOnCreate(String attribute, Object value) { assertSetAndGetSucceeds(attribute, value, true); } protected void assertSetAndGetSucceeds(String attribute, Object value, boolean create) { provider.set(file, provider.name(), attribute, value, create); assertThat(provider.get(file, attribute)).isEqualTo(value); } @SuppressWarnings("EmptyCatchBlock") protected void assertSetFails(String attribute, Object value) { try { provider.set(file, provider.name(), attribute, value, false); fail(); } catch (IllegalArgumentException expected) { } } @SuppressWarnings("EmptyCatchBlock") protected void assertSetFailsOnCreate(String attribute, Object value) { try { provider.set(file, provider.name(), attribute, value, true); fail(); } catch (UnsupportedOperationException expected) { } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/AbstractGlobMatcherTest.java000066400000000000000000000155051265745405500303150ustar00rootroot00000000000000/* * 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 org.junit.Test; /** * @author Colin Decker */ public abstract class AbstractGlobMatcherTest extends AbstractPathMatcherTest { @Test public void testMatching_literal() { assertThat("foo").matches("foo"); assertThat("/foo").matches("/foo"); assertThat("/foo/bar/baz").matches("/foo/bar/baz"); } @Test public void testMatching_questionMark() { assertThat("?") .matches("a", "A", "$", "5", "_") .doesNotMatch("/", "ab", ""); assertThat("??").matches("ab"); assertThat("????").matches("1234"); assertThat("?oo?") .matches("book", "doom") .doesNotMatch("/oom"); assertThat("/?oo/ba?").matches("/foo/bar"); assertThat("foo.?").matches("foo.h"); assertThat("foo.??").matches("foo.cc"); } @Test public void testMatching_star() { assertThat("*") .matches("a", "abc", "298347829473928423", "abc12345", "") .doesNotMatch("/", "/abc"); assertThat("/*") .matches("/a", "/abcd", "/abc123", "/") .doesNotMatch("/foo/bar"); assertThat("/*/*/*") .matches("/a/b/c", "/foo/bar/baz") .doesNotMatch("/foo/bar", "/foo/bar/baz/abc"); assertThat("/*/bar") .matches("/foo/bar", "/abc/bar") .doesNotMatch("/bar"); assertThat("/foo/*") .matches("/foo/bar", "/foo/baz") .doesNotMatch("/foo", "foo/bar", "/foo/bar/baz"); assertThat("/foo*/ba*") .matches("/food/bar", "/fool/bat", "/foo/ba", "/foot/ba", "/foo/bar", "/foods/bartender") .doesNotMatch("/food/baz/bar"); assertThat("*.java") .matches("Foo.java", "Bar.java", "GlobPatternTest.java", "Foo.java.java", ".java") .doesNotMatch("Foo.jav", "Foo", "java.Foo", "Foo.java."); assertThat("Foo.*") .matches("Foo.java", "Foo.txt", "Foo.tar.gz", "Foo.Foo.", "Foo.") .doesNotMatch("Foo", ".Foo"); assertThat("*/*.java") .matches("foo/Bar.java", "foo/.java"); assertThat("*/Bar.*") .matches("foo/Bar.java"); assertThat(".*") .matches(".bashrc", ".bash_profile"); assertThat("*.............").matches( "............a............a..............a.............a............a.........." + ".........................................................a...................."); assertThat("*.............*..").matches( "............a............a..............a.............a............a.........." + "..........a..................................................................."); assertThat(".................*........*.*.....*....................*..............*").matches( ".................................abc.........................................." + ".............................................................................." + ".............................................................................." + ".............................................12..............................." + ".........................................................................hello" + ".............................................................................."); } @Test public void testMatching_starStar() { assertThat("**") .matches("", "a", "abc", "293874982374913794141", "/foo/bar/baz", "foo/bar.txt"); assertThat("**foo") .matches("foo", "barfoo", "/foo", "/a/b/c/foo", "c.foo", "a/b/c.foo") .doesNotMatch("foo.bar", "/a/b/food"); assertThat("/foo/**/bar.txt") .matches("/foo/baz/bar.txt", "/foo/bar/asdf/bar.txt") .doesNotMatch("/foo/bar.txt", "/foo/baz/bar"); assertThat("**/*.java") .matches("/Foo.java", "foo/Bar.java", "/.java", "foo/.java"); } @Test public void testMatching_brackets() { assertThat("[ab]") .matches("a", "b") .doesNotMatch("ab", "ba", "aa", "bb", "c", "", "/"); assertThat("[a-d]") .matches("a", "b", "c", "d") .doesNotMatch("e", "f", "z", "aa", "ab", "abcd", "", "/"); assertThat("[a-dz]") .matches("a", "b", "c", "d", "z") .doesNotMatch("e", "f", "aa", "ab", "dz", "", "/"); assertThat("[!b]") .matches("a", "c", "d", "0", "!", "$") .doesNotMatch("b", "/", "", "ac"); assertThat("[!b-d3]") .matches("a", "e", "f", "0", "1", "2", "4") .doesNotMatch("b", "c", "d", "3"); assertThat("[-]").matches("-"); assertThat("[-a-c]").matches("-", "a", "b", "c"); assertThat("[!-a-c]") .matches("d", "e", "0") .doesNotMatch("a", "b", "c", "-"); assertThat("[\\d]") .matches("\\", "d") .doesNotMatch("0", "1"); assertThat("[\\s]") .matches("\\", "s") .doesNotMatch(" "); assertThat("[\\]") .matches("\\") .doesNotMatch("]"); } @Test public void testMatching_curlyBraces() { assertThat("{a,b}") .matches("a", "b") .doesNotMatch("/", "c", "0", "", ",", "{", "}"); assertThat("{ab,cd}") .matches("ab", "cd") .doesNotMatch("bc", "ac", "ad", "ba", "dc", ","); assertThat(".{h,cc}") .matches(".h", ".cc") .doesNotMatch("h", "cc"); assertThat("{?oo,ba?}") .matches("foo", "boo", "moo", "bat", "bar", "baz"); assertThat("{[Ff]oo*,[Bb]a*,[A-Ca-c]*/[!z]*.txt}") .matches("foo", "Foo", "fools", "ba", "Ba", "bar", "Bar", "Bart", "c/y.txt", "Cat/foo.txt") .doesNotMatch("Cat", "Cat/foo", "blah", "bAr", "c/z.txt", "c/.txt", "*"); } @Test public void testMatching_escapes() { assertThat("\\\\").matches("\\"); assertThat("\\*").matches("*"); assertThat("\\*\\*").matches("**"); assertThat("\\[").matches("["); assertThat("\\{").matches("{"); assertThat("\\a").matches("a"); assertThat("{a,\\}}").matches("a", "}"); assertThat("{a\\,,b}") .matches("a,", "b") .doesNotMatch("a", ","); } @Test public void testMatching_various() { assertThat("**/[A-Z]*.{[Jj][Aa][Vv][Aa],[Tt][Xx][Tt]}") .matches("/foo/bar/Baz.java", "/A.java", "bar/Test.JAVA", "foo/Foo.tXt"); } @Test public void testInvalidSyntax() { assertSyntaxError("\\"); assertSyntaxError("["); assertSyntaxError("[]"); assertSyntaxError("{"); assertSyntaxError("{{}"); assertSyntaxError("{a,b,a{b,c},d}"); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/AbstractJimfsIntegrationTest.java000066400000000000000000000065021265745405500313770ustar00rootroot00000000000000/* * 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.jimfs.PathSubject.paths; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assert_; import org.junit.After; import org.junit.Before; import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; /** * @author Colin Decker */ public abstract class AbstractJimfsIntegrationTest { protected FileSystem fs; @Before public void setUp() throws IOException { fs = createFileSystem(); } @After public void tearDown() throws IOException { fs.close(); } /** * Creates the file system to use in the tests. */ protected abstract FileSystem createFileSystem(); // helpers protected Path path(String first, String... more) { return fs.getPath(first, more); } protected Object getFileKey(String path, LinkOption... options) throws IOException { return Files.getAttribute(path(path), "fileKey", options); } protected PathSubject assertThatPath(String path, LinkOption... options) { return assertThatPath(path(path), options); } protected static PathSubject assertThatPath(Path path, LinkOption... options) { PathSubject subject = assert_().about(paths()).that(path); if (options.length != 0) { subject = subject.noFollowLinks(); } return subject; } /** * Tester for testing changes in file times. */ protected static final class FileTimeTester { private final Path path; private FileTime accessTime; private FileTime modifiedTime; FileTimeTester(Path path) throws IOException { this.path = path; BasicFileAttributes attrs = attrs(); accessTime = attrs.lastAccessTime(); modifiedTime = attrs.lastModifiedTime(); } private BasicFileAttributes attrs() throws IOException { return Files.readAttributes(path, BasicFileAttributes.class); } public void assertAccessTimeChanged() throws IOException { FileTime t = attrs().lastAccessTime(); assertThat(t).isNotEqualTo(accessTime); accessTime = t; } public void assertAccessTimeDidNotChange() throws IOException { FileTime t = attrs().lastAccessTime(); assertThat(t).isEqualTo(accessTime); } public void assertModifiedTimeChanged() throws IOException { FileTime t = attrs().lastModifiedTime(); assertThat(t).isNotEqualTo(modifiedTime); modifiedTime = t; } public void assertModifiedTimeDidNotChange() throws IOException { FileTime t = attrs().lastModifiedTime(); assertThat(t).isEqualTo(modifiedTime); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/AbstractPathMatcherTest.java000066400000000000000000000136121265745405500303230ustar00rootroot00000000000000/* * 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 org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.Iterator; import java.util.regex.PatternSyntaxException; import javax.annotation.Nullable; /** * Abstract base class for tests of {@link PathMatcher} implementations. * * @author Colin Decker */ public abstract class AbstractPathMatcherTest { /** * Creates a new {@code PathMatcher} using the given pattern in the syntax this test is testing. */ protected abstract PathMatcher matcher(String pattern); /** * Override to return a real matcher for the given pattern. */ @Nullable protected PathMatcher realMatcher(String pattern) { return null; } protected void assertSyntaxError(String pattern) { try { matcher(pattern); fail(); } catch (PatternSyntaxException expected) { } try { PathMatcher real = realMatcher(pattern); if (real != null) { fail(); } } catch (PatternSyntaxException expected) { } } protected final PatternAsserter assertThat(String pattern) { return new PatternAsserter(pattern); } protected final class PatternAsserter { private final PathMatcher matcher; @Nullable private final PathMatcher realMatcher; PatternAsserter(String pattern) { this.matcher = matcher(pattern); this.realMatcher = realMatcher(pattern); } PatternAsserter matches(String... paths) { for (String path : paths) { assertTrue( "matcher '" + matcher + "' did not match '" + path + "'", matcher.matches(fake(path))); if (realMatcher != null) { Path realPath = Paths.get(path); assertTrue( "real matcher '" + realMatcher + "' did not match '" + realPath + "'", realMatcher.matches(realPath)); } } return this; } PatternAsserter doesNotMatch(String... paths) { for (String path : paths) { assertFalse( "glob '" + matcher + "' should not have matched '" + path + "'", matcher.matches(fake(path))); if (realMatcher != null) { Path realPath = Paths.get(path); assertFalse( "real matcher '" + realMatcher + "' matched '" + realPath + "'", realMatcher.matches(realPath)); } } return this; } } /** * Path that only provides toString(). */ private static Path fake(final String path) { return new Path() { @Override public FileSystem getFileSystem() { return null; } @Override public boolean isAbsolute() { return false; } @Override public Path getRoot() { return null; } @Override public Path getFileName() { return null; } @Override public Path getParent() { return null; } @Override public int getNameCount() { return 0; } @Override public Path getName(int index) { return null; } @Override public Path subpath(int beginIndex, int endIndex) { return null; } @Override public boolean startsWith(Path other) { return false; } @Override public boolean startsWith(String other) { return false; } @Override public boolean endsWith(Path other) { return false; } @Override public boolean endsWith(String other) { return false; } @Override public Path normalize() { return null; } @Override public Path resolve(Path other) { return null; } @Override public Path resolve(String other) { return null; } @Override public Path resolveSibling(Path other) { return null; } @Override public Path resolveSibling(String other) { return null; } @Override public Path relativize(Path other) { return null; } @Override public URI toUri() { return null; } @Override public Path toAbsolutePath() { return null; } @Override public Path toRealPath(LinkOption... options) throws IOException { return null; } @Override public File toFile() { return null; } @Override public WatchKey register( WatchService watcher, WatchEvent.Kind[] events, WatchEvent.Modifier... modifiers) throws IOException { return null; } @Override public WatchKey register(WatchService watcher, WatchEvent.Kind... events) throws IOException { return null; } @Override public Iterator iterator() { return null; } @Override public int compareTo(Path other) { return 0; } @Override public String toString() { return path; } }; } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/AbstractWatchServiceTest.java000066400000000000000000000176471265745405500305260ustar00rootroot00000000000000/* * 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.jimfs.AbstractWatchService.Key.State.READY; import static com.google.common.jimfs.AbstractWatchService.Key.State.SIGNALLED; import static com.google.common.truth.Truth.assertThat; import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static java.nio.file.StandardWatchEventKinds.OVERFLOW; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.ClosedWatchServiceException; import java.nio.file.Path; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.Watchable; import java.util.Arrays; import java.util.List; /** * Tests for {@link AbstractWatchService}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class AbstractWatchServiceTest { private AbstractWatchService watcher; @Before public void setUp() throws IOException { watcher = new AbstractWatchService() {}; } @Test public void testNewWatcher() throws IOException { assertThat(watcher.isOpen()).isTrue(); assertThat(watcher.poll()).isNull(); assertThat(watcher.queuedKeys()).isEmpty(); watcher.close(); assertThat(watcher.isOpen()).isFalse(); } @Test public void testRegister() throws IOException { Watchable watchable = new StubWatchable(); AbstractWatchService.Key key = watcher.register(watchable, ImmutableSet.of(ENTRY_CREATE)); assertThat(key.isValid()).isTrue(); assertThat(key.pollEvents()).isEmpty(); assertThat(key.subscribesTo(ENTRY_CREATE)).isTrue(); assertThat(key.subscribesTo(ENTRY_DELETE)).isFalse(); assertThat(key.watchable()).isEqualTo(watchable); assertThat(key.state()).isEqualTo(READY); } @Test public void testPostEvent() throws IOException { AbstractWatchService.Key key = watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE)); AbstractWatchService.Event event = new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null); key.post(event); key.signal(); assertThat(watcher.queuedKeys()).containsExactly(key); WatchKey retrievedKey = watcher.poll(); assertThat(retrievedKey).isEqualTo(key); List> events = retrievedKey.pollEvents(); assertThat(events).hasSize(1); assertThat(events.get(0)).isEqualTo(event); // polling should have removed all events assertThat(retrievedKey.pollEvents()).isEmpty(); } @Test public void testKeyStates() throws IOException { AbstractWatchService.Key key = watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE)); AbstractWatchService.Event event = new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null); assertThat(key.state()).isEqualTo(READY); key.post(event); key.signal(); assertThat(key.state()).isEqualTo(SIGNALLED); AbstractWatchService.Event event2 = new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null); key.post(event2); assertThat(key.state()).isEqualTo(SIGNALLED); // key was not queued twice assertThat(watcher.queuedKeys()).containsExactly(key); assertThat(watcher.poll().pollEvents()).containsExactly(event, event2); assertThat(watcher.poll()).isNull(); key.post(event); // still not added to queue; already signalled assertThat(watcher.poll()).isNull(); assertThat(key.pollEvents()).containsExactly(event); key.reset(); assertThat(key.state()).isEqualTo(READY); key.post(event2); key.signal(); // now that it's reset it can be requeued assertThat(watcher.poll()).isEqualTo(key); } @Test public void testKeyRequeuedOnResetIfEventsArePending() throws IOException { AbstractWatchService.Key key = watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE)); key.post(new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null)); key.signal(); key = (AbstractWatchService.Key) watcher.poll(); assertThat(watcher.queuedKeys()).isEmpty(); assertThat(key.pollEvents()).hasSize(1); key.post(new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null)); assertThat(watcher.queuedKeys()).isEmpty(); key.reset(); assertThat(key.state()).isEqualTo(SIGNALLED); assertThat(watcher.queuedKeys()).hasSize(1); } @Test public void testOverflow() throws IOException { AbstractWatchService.Key key = watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE)); for (int i = 0; i < AbstractWatchService.Key.MAX_QUEUE_SIZE + 10; i++) { key.post(new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null)); } key.signal(); List> events = key.pollEvents(); assertThat(events).hasSize(AbstractWatchService.Key.MAX_QUEUE_SIZE + 1); for (int i = 0; i < AbstractWatchService.Key.MAX_QUEUE_SIZE; i++) { assertThat(events.get(i).kind()).isEqualTo(ENTRY_CREATE); } WatchEvent lastEvent = events.get(AbstractWatchService.Key.MAX_QUEUE_SIZE); assertThat(lastEvent.kind()).isEqualTo(OVERFLOW); assertThat(lastEvent.count()).isEqualTo(10); } @Test public void testResetAfterCancelReturnsFalse() throws IOException { AbstractWatchService.Key key = watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE)); key.signal(); key.cancel(); assertThat(key.reset()).isFalse(); } @Test public void testClosedWatcher() throws IOException, InterruptedException { AbstractWatchService.Key key1 = watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE)); AbstractWatchService.Key key2 = watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_MODIFY)); assertThat(key1.isValid()).isTrue(); assertThat(key2.isValid()).isTrue(); watcher.close(); assertThat(key1.isValid()).isFalse(); assertThat(key2.isValid()).isFalse(); assertThat(key1.reset()).isFalse(); assertThat(key2.reset()).isFalse(); try { watcher.poll(); fail(); } catch (ClosedWatchServiceException expected) { } try { watcher.poll(10, SECONDS); fail(); } catch (ClosedWatchServiceException expected) { } try { watcher.take(); fail(); } catch (ClosedWatchServiceException expected) { } try { watcher.register(new StubWatchable(), ImmutableList.>of()); fail(); } catch (ClosedWatchServiceException expected) { } } // TODO(cgdecker): Test concurrent use of Watcher /** * A fake {@link Watchable} for testing. */ private static final class StubWatchable implements Watchable { @Override public WatchKey register( WatchService watcher, WatchEvent.Kind[] events, WatchEvent.Modifier... modifiers) throws IOException { return register(watcher, events); } @Override public WatchKey register(WatchService watcher, WatchEvent.Kind... events) throws IOException { return ((AbstractWatchService) watcher).register(this, Arrays.asList(events)); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/AclAttributeProviderTest.java000066400000000000000000000076671265745405500305520ustar00rootroot00000000000000/* * 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.jimfs.UserLookupService.createUserPrincipal; import static com.google.common.truth.Truth.assertThat; import static java.nio.file.attribute.AclEntryFlag.DIRECTORY_INHERIT; import static java.nio.file.attribute.AclEntryPermission.APPEND_DATA; import static java.nio.file.attribute.AclEntryPermission.DELETE; import static java.nio.file.attribute.AclEntryType.ALLOW; import static org.junit.Assert.assertNotNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.attribute.AclEntry; import java.nio.file.attribute.AclFileAttributeView; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.UserPrincipal; import java.util.Map; import java.util.Set; /** * Tests for {@link AclAttributeProvider}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class AclAttributeProviderTest extends AbstractAttributeProviderTest { private static final UserPrincipal USER = createUserPrincipal("user"); private static final UserPrincipal FOO = createUserPrincipal("foo"); private static final ImmutableList defaultAcl = new ImmutableList.Builder() .add( AclEntry.newBuilder() .setType(ALLOW) .setFlags(DIRECTORY_INHERIT) .setPermissions(DELETE, APPEND_DATA) .setPrincipal(USER) .build()) .add( AclEntry.newBuilder() .setType(ALLOW) .setFlags(DIRECTORY_INHERIT) .setPermissions(DELETE, APPEND_DATA) .setPrincipal(FOO) .build()) .build(); @Override protected AclAttributeProvider createProvider() { return new AclAttributeProvider(); } @Override protected Set createInheritedProviders() { return ImmutableSet.of(new BasicAttributeProvider(), new OwnerAttributeProvider()); } @Override protected Map createDefaultValues() { return ImmutableMap.of("acl:acl", defaultAcl); } @Test public void testInitialAttributes() { assertThat(provider.get(file, "acl")).isEqualTo(defaultAcl); } @Test public void testSet() { assertSetAndGetSucceeds("acl", ImmutableList.of()); assertSetFailsOnCreate("acl", ImmutableList.of()); assertSetFails("acl", ImmutableSet.of()); assertSetFails("acl", ImmutableList.of("hello")); } @Test public void testView() throws IOException { AclFileAttributeView view = provider.view( fileLookup(), ImmutableMap.of( "owner", new OwnerAttributeProvider().view(fileLookup(), NO_INHERITED_VIEWS))); assertNotNull(view); assertThat(view.name()).isEqualTo("acl"); assertThat(view.getAcl()).isEqualTo(defaultAcl); view.setAcl(ImmutableList.of()); view.setOwner(FOO); assertThat(view.getAcl()).isEqualTo(ImmutableList.of()); assertThat(view.getOwner()).isEqualTo(FOO); assertThat(file.getAttribute("acl", "acl")).isEqualTo(ImmutableList.of()); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/AttributeServiceTest.java000066400000000000000000000311361265745405500277240ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.math.BigInteger; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; /** * Tests for {@link AttributeService}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class AttributeServiceTest { private AttributeService service; @Before public void setUp() { ImmutableSet providers = ImmutableSet.of( StandardAttributeProviders.get("basic"), StandardAttributeProviders.get("owner"), new TestAttributeProvider()); service = new AttributeService(providers, ImmutableMap.of()); } @Test public void testSupportedFileAttributeViews() { assertThat(service.supportedFileAttributeViews()) .isEqualTo(ImmutableSet.of("basic", "test", "owner")); } @Test public void testSupportsFileAttributeView() { assertThat(service.supportsFileAttributeView(BasicFileAttributeView.class)).isTrue(); assertThat(service.supportsFileAttributeView(TestAttributeView.class)).isTrue(); assertThat(service.supportsFileAttributeView(PosixFileAttributeView.class)).isFalse(); } @Test public void testSetInitialAttributes() { File file = Directory.create(0); service.setInitialAttributes(file); assertThat(file.getAttributeNames("test")).containsExactly("bar", "baz"); assertThat(file.getAttributeNames("owner")).containsExactly("owner"); assertThat(service.getAttribute(file, "basic:lastModifiedTime")).isInstanceOf(FileTime.class); assertThat(file.getAttribute("test", "bar")).isEqualTo(0L); assertThat(file.getAttribute("test", "baz")).isEqualTo(1); } @Test public void testGetAttribute() { File file = Directory.create(0); service.setInitialAttributes(file); assertThat(service.getAttribute(file, "test:foo")).isEqualTo("hello"); assertThat(service.getAttribute(file, "test", "foo")).isEqualTo("hello"); assertThat(service.getAttribute(file, "basic:isRegularFile")).isEqualTo(false); assertThat(service.getAttribute(file, "isDirectory")).isEqualTo(true); assertThat(service.getAttribute(file, "test:baz")).isEqualTo(1); } @Test public void testGetAttribute_fromInheritedProvider() { File file = Directory.create(0); assertThat(service.getAttribute(file, "test:isRegularFile")).isEqualTo(false); assertThat(service.getAttribute(file, "test:isDirectory")).isEqualTo(true); assertThat(service.getAttribute(file, "test", "fileKey")).isEqualTo(0); } @Test public void testGetAttribute_failsForAttributesNotDefinedByProvider() { File file = Directory.create(0); try { service.getAttribute(file, "test:blah"); fail(); } catch (IllegalArgumentException expected) { } try { // baz is defined by "test", but basic doesn't inherit test service.getAttribute(file, "basic", "baz"); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testSetAttribute() { File file = Directory.create(0); service.setAttribute(file, "test:bar", 10L, false); assertThat(file.getAttribute("test", "bar")).isEqualTo(10L); service.setAttribute(file, "test:baz", 100, false); assertThat(file.getAttribute("test", "baz")).isEqualTo(100); } @Test public void testSetAttribute_forInheritedProvider() { File file = Directory.create(0); service.setAttribute(file, "test:lastModifiedTime", FileTime.fromMillis(0), false); assertThat(file.getAttribute("test", "lastModifiedTime")).isNull(); assertThat(service.getAttribute(file, "basic:lastModifiedTime")) .isEqualTo(FileTime.fromMillis(0)); } @Test public void testSetAttribute_withAlternateAcceptedType() { File file = Directory.create(0); service.setAttribute(file, "test:bar", 10F, false); assertThat(file.getAttribute("test", "bar")).isEqualTo(10L); service.setAttribute(file, "test:bar", BigInteger.valueOf(123), false); assertThat(file.getAttribute("test", "bar")).isEqualTo(123L); } @Test public void testSetAttribute_onCreate() { File file = Directory.create(0); service.setInitialAttributes(file, new BasicFileAttribute<>("test:baz", 123)); assertThat(file.getAttribute("test", "baz")).isEqualTo(123); } @Test public void testSetAttribute_failsForAttributesNotDefinedByProvider() { File file = Directory.create(0); service.setInitialAttributes(file); try { service.setAttribute(file, "test:blah", "blah", false); fail(); } catch (IllegalArgumentException expected) { } try { // baz is defined by "test", but basic doesn't inherit test service.setAttribute(file, "basic:baz", 5, false); fail(); } catch (IllegalArgumentException expected) { } assertThat(file.getAttribute("test", "baz")).isEqualTo(1); } @Test public void testSetAttribute_failsForArgumentThatIsNotOfCorrectType() { File file = Directory.create(0); service.setInitialAttributes(file); try { service.setAttribute(file, "test:bar", "wrong", false); fail(); } catch (IllegalArgumentException expected) { } assertThat(file.getAttribute("test", "bar")).isEqualTo(0L); } @Test public void testSetAttribute_failsForNullArgument() { File file = Directory.create(0); service.setInitialAttributes(file); try { service.setAttribute(file, "test:bar", null, false); fail(); } catch (NullPointerException expected) { } assertThat(file.getAttribute("test", "bar")).isEqualTo(0L); } @Test public void testSetAttribute_failsForAttributeThatIsNotSettable() { File file = Directory.create(0); try { service.setAttribute(file, "test:foo", "world", false); fail(); } catch (IllegalArgumentException expected) { } assertThat(file.getAttribute("test", "foo")).isNull(); } @Test public void testSetAttribute_onCreate_failsForAttributeThatIsNotSettableOnCreate() { File file = Directory.create(0); try { service.setInitialAttributes(file, new BasicFileAttribute<>("test:foo", "world")); fail(); } catch (IllegalArgumentException expected) { // IAE because test:foo just can't be set } try { service.setInitialAttributes(file, new BasicFileAttribute<>("test:bar", 5)); fail(); } catch (UnsupportedOperationException expected) { } } @SuppressWarnings("ConstantConditions") @Test public void testGetFileAttributeView() throws IOException { final File file = Directory.create(0); service.setInitialAttributes(file); FileLookup fileLookup = new FileLookup() { @Override public File lookup() throws IOException { return file; } }; assertThat(service.getFileAttributeView(fileLookup, TestAttributeView.class)).isNotNull(); assertThat(service.getFileAttributeView(fileLookup, BasicFileAttributeView.class)).isNotNull(); TestAttributes attrs = service.getFileAttributeView(fileLookup, TestAttributeView.class).readAttributes(); assertThat(attrs.foo()).isEqualTo("hello"); assertThat(attrs.bar()).isEqualTo(0); assertThat(attrs.baz()).isEqualTo(1); } @Test public void testGetFileAttributeView_isNullForUnsupportedView() { final File file = Directory.create(0); FileLookup fileLookup = new FileLookup() { @Override public File lookup() throws IOException { return file; } }; assertThat(service.getFileAttributeView(fileLookup, PosixFileAttributeView.class)).isNull(); } @Test public void testReadAttributes_asMap() { File file = Directory.create(0); service.setInitialAttributes(file); ImmutableMap map = service.readAttributes(file, "test:foo,bar,baz"); assertThat(map) .isEqualTo( ImmutableMap.of( "foo", "hello", "bar", 0L, "baz", 1)); FileTime time = (FileTime) service.getAttribute(file, "basic:creationTime"); map = service.readAttributes(file, "test:*"); assertThat(map) .isEqualTo( ImmutableMap.builder() .put("foo", "hello") .put("bar", 0L) .put("baz", 1) .put("fileKey", 0) .put("isDirectory", true) .put("isRegularFile", false) .put("isSymbolicLink", false) .put("isOther", false) .put("size", 0L) .put("lastModifiedTime", time) .put("lastAccessTime", time) .put("creationTime", time) .build()); map = service.readAttributes(file, "basic:*"); assertThat(map) .isEqualTo( ImmutableMap.builder() .put("fileKey", 0) .put("isDirectory", true) .put("isRegularFile", false) .put("isSymbolicLink", false) .put("isOther", false) .put("size", 0L) .put("lastModifiedTime", time) .put("lastAccessTime", time) .put("creationTime", time) .build()); } @Test public void testReadAttributes_asMap_failsForInvalidAttributes() { File file = Directory.create(0); try { service.readAttributes(file, "basic:fileKey,isOther,*,creationTime"); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("invalid attributes"); } try { service.readAttributes(file, "basic:fileKey,isOther,foo"); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("invalid attribute"); } } @Test public void testReadAttributes_asObject() { File file = Directory.create(0); service.setInitialAttributes(file); BasicFileAttributes basicAttrs = service.readAttributes(file, BasicFileAttributes.class); assertThat(basicAttrs.fileKey()).isEqualTo(0); assertThat(basicAttrs.isDirectory()).isTrue(); assertThat(basicAttrs.isRegularFile()).isFalse(); TestAttributes testAttrs = service.readAttributes(file, TestAttributes.class); assertThat(testAttrs.foo()).isEqualTo("hello"); assertThat(testAttrs.bar()).isEqualTo(0); assertThat(testAttrs.baz()).isEqualTo(1); file.setAttribute("test", "baz", 100); assertThat(service.readAttributes(file, TestAttributes.class).baz()).isEqualTo(100); } @Test public void testReadAttributes_failsForUnsupportedAttributesType() { File file = Directory.create(0); try { service.readAttributes(file, PosixFileAttributes.class); fail(); } catch (UnsupportedOperationException expected) { } } @Test public void testIllegalAttributeFormats() { File file = Directory.create(0); try { service.getAttribute(file, ":bar"); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("attribute format"); } try { service.getAttribute(file, "test:"); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("attribute format"); } try { service.getAttribute(file, "basic:test:isDirectory"); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("attribute format"); } try { service.getAttribute(file, "basic:fileKey,size"); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("single attribute"); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/BasicAttributeProviderTest.java000066400000000000000000000106321265745405500310560ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.util.Set; /** * Tests for {@link BasicAttributeProvider}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class BasicAttributeProviderTest extends AbstractAttributeProviderTest { @Override protected BasicAttributeProvider createProvider() { return new BasicAttributeProvider(); } @Override protected Set createInheritedProviders() { return ImmutableSet.of(); } @Test public void testSupportedAttributes() { assertSupportsAll( "fileKey", "size", "isDirectory", "isRegularFile", "isSymbolicLink", "isOther", "creationTime", "lastModifiedTime", "lastAccessTime"); } @Test public void testInitialAttributes() { long time = file.getCreationTime(); assertThat(time).isNotEqualTo(0L); assertThat(time).isEqualTo(file.getLastAccessTime()); assertThat(time).isEqualTo(file.getLastModifiedTime()); assertContainsAll( file, ImmutableMap.builder() .put("fileKey", 0) .put("size", 0L) .put("isDirectory", true) .put("isRegularFile", false) .put("isSymbolicLink", false) .put("isOther", false) .build()); } @Test public void testSet() { FileTime time = FileTime.fromMillis(0L); // settable assertSetAndGetSucceeds("creationTime", time); assertSetAndGetSucceeds("lastModifiedTime", time); assertSetAndGetSucceeds("lastAccessTime", time); // unsettable assertSetFails("fileKey", 3L); assertSetFails("size", 1L); assertSetFails("isRegularFile", true); assertSetFails("isDirectory", true); assertSetFails("isSymbolicLink", true); assertSetFails("isOther", true); // invalid type assertSetFails("creationTime", "foo"); } @Test public void testSetOnCreate() { FileTime time = FileTime.fromMillis(0L); assertSetFailsOnCreate("creationTime", time); assertSetFailsOnCreate("lastModifiedTime", time); assertSetFailsOnCreate("lastAccessTime", time); } @Test public void testView() throws IOException { BasicFileAttributeView view = provider.view(fileLookup(), NO_INHERITED_VIEWS); assertThat(view).isNotNull(); assertThat(view.name()).isEqualTo("basic"); BasicFileAttributes attrs = view.readAttributes(); assertThat(attrs.fileKey()).isEqualTo(0); FileTime time = attrs.creationTime(); assertThat(attrs.lastAccessTime()).isEqualTo(time); assertThat(attrs.lastModifiedTime()).isEqualTo(time); view.setTimes(null, null, null); attrs = view.readAttributes(); assertThat(attrs.creationTime()).isEqualTo(time); assertThat(attrs.lastAccessTime()).isEqualTo(time); assertThat(attrs.lastModifiedTime()).isEqualTo(time); view.setTimes(FileTime.fromMillis(0L), null, null); attrs = view.readAttributes(); assertThat(attrs.creationTime()).isEqualTo(time); assertThat(attrs.lastAccessTime()).isEqualTo(time); assertThat(attrs.lastModifiedTime()).isEqualTo(FileTime.fromMillis(0L)); } @Test public void testAttributes() { BasicFileAttributes attrs = provider.readAttributes(file); assertThat(attrs.fileKey()).isEqualTo(0); assertThat(attrs.isDirectory()).isTrue(); assertThat(attrs.isRegularFile()).isFalse(); assertThat(attrs.creationTime()).isNotNull(); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/BasicFileAttribute.java000066400000000000000000000021671265745405500273070ustar00rootroot00000000000000/* * 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.checkNotNull; import java.nio.file.attribute.FileAttribute; /** * @author Colin Decker */ public class BasicFileAttribute implements FileAttribute { private final String name; private final T value; public BasicFileAttribute(String name, T value) { this.name = checkNotNull(name); this.value = checkNotNull(value); } @Override public String name() { return name; } @Override public T value() { return value; } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/ByteBufferChannel.java000066400000000000000000000045621265745405500271310ustar00rootroot00000000000000/* * 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 java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; /** * @author Colin Decker */ public class ByteBufferChannel implements SeekableByteChannel { private final ByteBuffer buffer; public ByteBufferChannel(byte[] bytes) { this.buffer = ByteBuffer.wrap(bytes); } public ByteBufferChannel(byte[] bytes, int offset, int length) { this.buffer = ByteBuffer.wrap(bytes, offset, length); } public ByteBufferChannel(int capacity) { this.buffer = ByteBuffer.allocate(capacity); } public ByteBufferChannel(ByteBuffer buffer) { this.buffer = buffer; } public ByteBuffer buffer() { return buffer; } @Override public int read(ByteBuffer dst) throws IOException { if (buffer.remaining() == 0) { return -1; } int length = Math.min(dst.remaining(), buffer.remaining()); for (int i = 0; i < length; i++) { dst.put(buffer.get()); } return length; } @Override public int write(ByteBuffer src) throws IOException { int length = Math.min(src.remaining(), buffer.remaining()); for (int i = 0; i < length; i++) { buffer.put(src.get()); } return length; } @Override public long position() throws IOException { return buffer.position(); } @Override public SeekableByteChannel position(long newPosition) throws IOException { buffer.position((int) newPosition); return this; } @Override public long size() throws IOException { return buffer.limit(); } @Override public SeekableByteChannel truncate(long size) throws IOException { buffer.limit((int) size); return this; } @Override public boolean isOpen() { return true; } @Override public void close() throws IOException {} } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/ClassLoaderTest.java000066400000000000000000000111531265745405500266310ustar00rootroot00000000000000/* * Copyright 2015 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 java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.lang.reflect.Method; import java.net.URLClassLoader; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.spi.FileSystemProvider; import java.util.List; /** * Tests behavior when user code loads Jimfs in a separate class loader from the system class * loader (which is what {@link FileSystemProvider#installedProviders()} uses to load * {@link FileSystemProvider}s as services from the classpath). * * @author Colin Decker */ @RunWith(JUnit4.class) public class ClassLoaderTest { @Test public void separateClassLoader() throws Exception { ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); ClassLoader loader = MoreObjects.firstNonNull(contextLoader, systemLoader); if (loader instanceof URLClassLoader) { // Anything we can do if it isn't a URLClassLoader? URLClassLoader urlLoader = (URLClassLoader) loader; ClassLoader separateLoader = new URLClassLoader( urlLoader.getURLs(), systemLoader.getParent()); // either null or the boostrap loader Thread.currentThread().setContextClassLoader(separateLoader); try { Class thisClass = separateLoader.loadClass(getClass().getName()); Method createFileSystem = thisClass.getDeclaredMethod("createFileSystem"); // First, the call to Jimfs.newFileSystem in createFileSystem needs to succeed Object fs = createFileSystem.invoke(null); // Next, some sanity checks: // The file system is a JimfsFileSystem assertEquals("com.google.common.jimfs.JimfsFileSystem", fs.getClass().getName()); // But it is not seen as an instance of JimfsFileSystem here because it was loaded by a // different ClassLoader assertFalse(fs instanceof JimfsFileSystem); // But it should be an instance of FileSystem regardless, which is the important thing. assertTrue(fs instanceof FileSystem); // And normal file operations should work on it despite its provenance from a different // ClassLoader writeAndRead((FileSystem) fs, "bar.txt", "blah blah"); // And for the heck of it, test the contents of the file that was created in // createFileSystem too assertEquals( "blah", Files.readAllLines(((FileSystem) fs).getPath("foo.txt"), UTF_8).get(0)); } finally { Thread.currentThread().setContextClassLoader(contextLoader); } } } /** * This method is really just testing that {@code Jimfs.newFileSystem()} succeeds. Without * special handling, when the system class loader loads our {@code FileSystemProvider} * implementation as a service and this code (the user code) is loaded in a separate class * loader, the system-loaded provider won't see the instance of {@code Configuration} we give it * as being an instance of the {@code Configuration} it's expecting (they're completely separate * classes) and creation of the file system will fail. */ public static FileSystem createFileSystem() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); // Just some random operations to verify that basic things work on the created file system. writeAndRead(fs, "foo.txt", "blah"); return fs; } private static void writeAndRead(FileSystem fs, String path, String text) throws IOException { Path p = fs.getPath(path); Files.write(p, ImmutableList.of(text), UTF_8); List lines = Files.readAllLines(p, UTF_8); assertEquals(text, lines.get(0)); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/ConfigurationTest.java000066400000000000000000000334471265745405500272560ustar00rootroot00000000000000/* * 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.jimfs.PathNormalization.CASE_FOLD_ASCII; import static com.google.common.jimfs.PathNormalization.CASE_FOLD_UNICODE; import static com.google.common.jimfs.PathNormalization.NFC; import static com.google.common.jimfs.PathNormalization.NFD; import static com.google.common.jimfs.PathSubject.paths; import static com.google.common.jimfs.WatchServiceConfiguration.polling; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assert_; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.WatchService; import java.nio.file.attribute.PosixFilePermissions; import java.util.concurrent.TimeUnit; /** * Tests for {@link Configuration}, {@link Configuration.Builder} and file systems created from * them. * * @author Colin Decker */ @RunWith(JUnit4.class) public class ConfigurationTest { private static PathSubject assertThatPath(Path path) { return assert_().about(paths()).that(path); } @Test public void testDefaultUnixConfiguration() { Configuration config = Configuration.unix(); assertThat(config.pathType).isEqualTo(PathType.unix()); assertThat(config.roots).containsExactly("/"); assertThat(config.workingDirectory).isEqualTo("/work"); assertThat(config.nameCanonicalNormalization).isEmpty(); assertThat(config.nameDisplayNormalization).isEmpty(); assertThat(config.pathEqualityUsesCanonicalForm).isFalse(); assertThat(config.blockSize).isEqualTo(8192); assertThat(config.maxSize).isEqualTo(4L * 1024 * 1024 * 1024); assertThat(config.maxCacheSize).isEqualTo(-1); assertThat(config.attributeViews).containsExactly("basic"); assertThat(config.attributeProviders).isEmpty(); assertThat(config.defaultAttributeValues).isEmpty(); } @Test public void testFileSystemForDefaultUnixConfiguration() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); assertThat(fs.getRootDirectories()) .containsExactlyElementsIn(ImmutableList.of(fs.getPath("/"))) .inOrder(); assertThatPath(fs.getPath("").toRealPath()).isEqualTo(fs.getPath("/work")); assertThat(Iterables.getOnlyElement(fs.getFileStores()).getTotalSpace()) .isEqualTo(4L * 1024 * 1024 * 1024); assertThat(fs.supportedFileAttributeViews()).containsExactly("basic"); Files.createFile(fs.getPath("/foo")); Files.createFile(fs.getPath("/FOO")); } @Test public void testDefaultOsXConfiguration() { Configuration config = Configuration.osX(); assertThat(config.pathType).isEqualTo(PathType.unix()); assertThat(config.roots).containsExactly("/"); assertThat(config.workingDirectory).isEqualTo("/work"); assertThat(config.nameCanonicalNormalization).containsExactly(NFD, CASE_FOLD_ASCII); assertThat(config.nameDisplayNormalization).containsExactly(NFC); assertThat(config.pathEqualityUsesCanonicalForm).isFalse(); assertThat(config.blockSize).isEqualTo(8192); assertThat(config.maxSize).isEqualTo(4L * 1024 * 1024 * 1024); assertThat(config.maxCacheSize).isEqualTo(-1); assertThat(config.attributeViews).containsExactly("basic"); assertThat(config.attributeProviders).isEmpty(); assertThat(config.defaultAttributeValues).isEmpty(); } @Test public void testFileSystemForDefaultOsXConfiguration() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.osX()); assertThat(fs.getRootDirectories()) .containsExactlyElementsIn(ImmutableList.of(fs.getPath("/"))) .inOrder(); assertThatPath(fs.getPath("").toRealPath()).isEqualTo(fs.getPath("/work")); assertThat(Iterables.getOnlyElement(fs.getFileStores()).getTotalSpace()) .isEqualTo(4L * 1024 * 1024 * 1024); assertThat(fs.supportedFileAttributeViews()).containsExactly("basic"); Files.createFile(fs.getPath("/foo")); try { Files.createFile(fs.getPath("/FOO")); fail(); } catch (FileAlreadyExistsException expected) { } } @Test public void testDefaultWindowsConfiguration() { Configuration config = Configuration.windows(); assertThat(config.pathType).isEqualTo(PathType.windows()); assertThat(config.roots).containsExactly("C:\\"); assertThat(config.workingDirectory).isEqualTo("C:\\work"); assertThat(config.nameCanonicalNormalization).containsExactly(CASE_FOLD_ASCII); assertThat(config.nameDisplayNormalization).isEmpty(); assertThat(config.pathEqualityUsesCanonicalForm).isTrue(); assertThat(config.blockSize).isEqualTo(8192); assertThat(config.maxSize).isEqualTo(4L * 1024 * 1024 * 1024); assertThat(config.maxCacheSize).isEqualTo(-1); assertThat(config.attributeViews).containsExactly("basic"); assertThat(config.attributeProviders).isEmpty(); assertThat(config.defaultAttributeValues).isEmpty(); } @Test public void testFileSystemForDefaultWindowsConfiguration() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.windows()); assertThat(fs.getRootDirectories()) .containsExactlyElementsIn(ImmutableList.of(fs.getPath("C:\\"))) .inOrder(); assertThatPath(fs.getPath("").toRealPath()).isEqualTo(fs.getPath("C:\\work")); assertThat(Iterables.getOnlyElement(fs.getFileStores()).getTotalSpace()) .isEqualTo(4L * 1024 * 1024 * 1024); assertThat(fs.supportedFileAttributeViews()).containsExactly("basic"); Files.createFile(fs.getPath("C:\\foo")); try { Files.createFile(fs.getPath("C:\\FOO")); fail(); } catch (FileAlreadyExistsException expected) { } } @Test public void testBuilder() { AttributeProvider unixProvider = StandardAttributeProviders.get("unix"); Configuration config = Configuration.builder(PathType.unix()) .setRoots("/") .setWorkingDirectory("/hello/world") .setNameCanonicalNormalization(NFD, CASE_FOLD_UNICODE) .setNameDisplayNormalization(NFC) .setPathEqualityUsesCanonicalForm(true) .setBlockSize(10) .setMaxSize(100) .setMaxCacheSize(50) .setAttributeViews("basic", "posix") .addAttributeProvider(unixProvider) .setDefaultAttributeValue( "posix:permissions", PosixFilePermissions.fromString("---------")) .build(); assertThat(config.pathType).isEqualTo(PathType.unix()); assertThat(config.roots).containsExactly("/"); assertThat(config.workingDirectory).isEqualTo("/hello/world"); assertThat(config.nameCanonicalNormalization).containsExactly(NFD, CASE_FOLD_UNICODE); assertThat(config.nameDisplayNormalization).containsExactly(NFC); assertThat(config.pathEqualityUsesCanonicalForm).isTrue(); assertThat(config.blockSize).isEqualTo(10); assertThat(config.maxSize).isEqualTo(100); assertThat(config.maxCacheSize).isEqualTo(50); assertThat(config.attributeViews).containsExactly("basic", "posix"); assertThat(config.attributeProviders).containsExactly(unixProvider); assertThat(config.defaultAttributeValues) .containsEntry("posix:permissions", PosixFilePermissions.fromString("---------")); } @Test public void testFileSystemForCustomConfiguration() throws IOException { Configuration config = Configuration.builder(PathType.unix()) .setRoots("/") .setWorkingDirectory("/hello/world") .setNameCanonicalNormalization(NFD, CASE_FOLD_UNICODE) .setNameDisplayNormalization(NFC) .setPathEqualityUsesCanonicalForm(true) .setBlockSize(10) .setMaxSize(100) .setMaxCacheSize(50) .setAttributeViews("unix") .setDefaultAttributeValue( "posix:permissions", PosixFilePermissions.fromString("---------")) .build(); FileSystem fs = Jimfs.newFileSystem(config); assertThat(fs.getRootDirectories()) .containsExactlyElementsIn(ImmutableList.of(fs.getPath("/"))) .inOrder(); assertThatPath(fs.getPath("").toRealPath()).isEqualTo(fs.getPath("/hello/world")); assertThat(Iterables.getOnlyElement(fs.getFileStores()).getTotalSpace()).isEqualTo(100); assertThat(fs.supportedFileAttributeViews()).containsExactly("basic", "owner", "posix", "unix"); Files.createFile(fs.getPath("/foo")); assertThat(Files.getAttribute(fs.getPath("/foo"), "posix:permissions")) .isEqualTo(PosixFilePermissions.fromString("---------")); try { Files.createFile(fs.getPath("/FOO")); fail(); } catch (FileAlreadyExistsException expected) { } } @Test public void testToBuilder() { Configuration config = Configuration.unix() .toBuilder() .setWorkingDirectory("/hello/world") .setAttributeViews("basic", "posix") .build(); assertThat(config.pathType).isEqualTo(PathType.unix()); assertThat(config.roots).containsExactly("/"); assertThat(config.workingDirectory).isEqualTo("/hello/world"); assertThat(config.nameCanonicalNormalization).isEmpty(); assertThat(config.nameDisplayNormalization).isEmpty(); assertThat(config.pathEqualityUsesCanonicalForm).isFalse(); assertThat(config.blockSize).isEqualTo(8192); assertThat(config.maxSize).isEqualTo(4L * 1024 * 1024 * 1024); assertThat(config.maxCacheSize).isEqualTo(-1); assertThat(config.attributeViews).containsExactly("basic", "posix"); assertThat(config.attributeProviders).isEmpty(); assertThat(config.defaultAttributeValues).isEmpty(); } @Test public void testSettingRootsUnsupportedByPathType() { assertIllegalRoots(PathType.unix(), "\\"); assertIllegalRoots(PathType.unix(), "/", "\\"); assertIllegalRoots(PathType.windows(), "/"); assertIllegalRoots(PathType.windows(), "C:"); // must have a \ (or a /) } private static void assertIllegalRoots(PathType type, String first, String... more) { try { Configuration.builder(type).setRoots(first, more); // wrong root fail(); } catch (IllegalArgumentException expected) { } } @Test public void testSettingWorkingDirectoryWithRelativePath() { try { Configuration.unix().toBuilder().setWorkingDirectory("foo/bar"); fail(); } catch (IllegalArgumentException expected) { } try { Configuration.windows().toBuilder().setWorkingDirectory("foo\\bar"); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testSettingNormalizationWhenNormalizationAlreadySet() { assertIllegalNormalizations(NFC, NFC); assertIllegalNormalizations(NFC, NFD); assertIllegalNormalizations(CASE_FOLD_ASCII, CASE_FOLD_ASCII); assertIllegalNormalizations(CASE_FOLD_ASCII, CASE_FOLD_UNICODE); } private static void assertIllegalNormalizations( PathNormalization first, PathNormalization... more) { try { Configuration.builder(PathType.unix()).setNameCanonicalNormalization(first, more); fail(); } catch (IllegalArgumentException expected) { } try { Configuration.builder(PathType.unix()).setNameDisplayNormalization(first, more); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testSetDefaultAttributeValue_illegalAttributeFormat() { try { Configuration.unix().toBuilder().setDefaultAttributeValue("foo", 1); fail(); } catch (IllegalArgumentException expected) { } } @Test // how's that for a name? public void testCreateFileSystemFromConfigurationWithWorkingDirectoryNotUnderConfiguredRoot() { try { Jimfs.newFileSystem( Configuration.windows() .toBuilder() .setRoots("C:\\", "D:\\") .setWorkingDirectory("E:\\foo") .build()); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testFileSystemWithDefaultWatchService() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); WatchService watchService = fs.newWatchService(); assertThat(watchService).isInstanceOf(PollingWatchService.class); PollingWatchService pollingWatchService = (PollingWatchService) watchService; assertThat(pollingWatchService.interval).isEqualTo(5); assertThat(pollingWatchService.timeUnit).isEqualTo(SECONDS); } @Test public void testFileSystemWithCustomWatchServicePollingInterval() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix().toBuilder() .setWatchServiceConfiguration(polling(10, MILLISECONDS)) .build()); WatchService watchService = fs.newWatchService(); assertThat(watchService).isInstanceOf(PollingWatchService.class); PollingWatchService pollingWatchService = (PollingWatchService) watchService; assertThat(pollingWatchService.interval).isEqualTo(10); assertThat(pollingWatchService.timeUnit).isEqualTo(MILLISECONDS); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/DirectoryTest.java000066400000000000000000000257331265745405500264120ustar00rootroot00000000000000/* * 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.jimfs.Name.PARENT; import static com.google.common.jimfs.Name.SELF; import static com.google.common.jimfs.TestUtils.regularFile; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import com.google.common.base.Functions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.HashSet; import java.util.Set; import javax.annotation.Nullable; /** * Tests for {@link Directory}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class DirectoryTest { private Directory root; private Directory dir; @Before public void setUp() { root = Directory.createRoot(0, Name.simple("/")); dir = Directory.create(1); root.link(Name.simple("foo"), dir); } @Test public void testRootDirectory() { assertThat(root.entryCount()).isEqualTo(3); // two for parent/self, one for dir assertThat(root.isEmpty()).isFalse(); assertThat(root.entryInParent()).isEqualTo(entry(root, "/", root)); assertThat(root.entryInParent().name()).isEqualTo(Name.simple("/")); assertParentAndSelf(root, root, root); } @Test public void testEmptyDirectory() { assertThat(dir.entryCount()).isEqualTo(2); assertThat(dir.isEmpty()).isTrue(); assertParentAndSelf(dir, root, dir); } @Test public void testGet() { assertThat(root.get(Name.simple("foo"))).isEqualTo(entry(root, "foo", dir)); assertThat(dir.get(Name.simple("foo"))).isNull(); assertThat(root.get(Name.simple("Foo"))).isNull(); } @Test public void testLink() { assertThat(dir.get(Name.simple("bar"))).isNull(); File bar = Directory.create(2); dir.link(Name.simple("bar"), bar); assertThat(dir.get(Name.simple("bar"))).isEqualTo(entry(dir, "bar", bar)); } @Test public void testLink_existingNameFails() { try { root.link(Name.simple("foo"), Directory.create(2)); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testLink_parentAndSelfNameFails() { try { dir.link(Name.simple("."), Directory.create(2)); fail(); } catch (IllegalArgumentException expected) { } try { dir.link(Name.simple(".."), Directory.create(2)); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testGet_normalizingCaseInsensitive() { File bar = Directory.create(2); Name barName = caseInsensitive("bar"); dir.link(barName, bar); DirectoryEntry expected = new DirectoryEntry(dir, barName, bar); assertThat(dir.get(caseInsensitive("bar"))).isEqualTo(expected); assertThat(dir.get(caseInsensitive("BAR"))).isEqualTo(expected); assertThat(dir.get(caseInsensitive("Bar"))).isEqualTo(expected); assertThat(dir.get(caseInsensitive("baR"))).isEqualTo(expected); } @Test public void testUnlink() { assertThat(root.get(Name.simple("foo"))).isNotNull(); root.unlink(Name.simple("foo")); assertThat(root.get(Name.simple("foo"))).isNull(); } @Test public void testUnlink_nonExistentNameFails() { try { dir.unlink(Name.simple("bar")); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testUnlink_parentAndSelfNameFails() { try { dir.unlink(Name.simple(".")); fail(); } catch (IllegalArgumentException expected) { } try { dir.unlink(Name.simple("..")); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testUnlink_normalizingCaseInsensitive() { dir.link(caseInsensitive("bar"), Directory.create(2)); assertThat(dir.get(caseInsensitive("bar"))).isNotNull(); dir.unlink(caseInsensitive("BAR")); assertThat(dir.get(caseInsensitive("bar"))).isNull(); } @Test public void testLinkDirectory() { Directory newDir = Directory.create(10); assertThat(newDir.entryInParent()).isNull(); assertThat(newDir.get(Name.SELF).file()).isEqualTo(newDir); assertThat(newDir.get(Name.PARENT)).isNull(); assertThat(newDir.links()).isEqualTo(1); dir.link(Name.simple("foo"), newDir); assertThat(newDir.entryInParent()).isEqualTo(entry(dir, "foo", newDir)); assertThat(newDir.parent()).isEqualTo(dir); assertThat(newDir.entryInParent().name()).isEqualTo(Name.simple("foo")); assertThat(newDir.get(Name.SELF)).isEqualTo(entry(newDir, ".", newDir)); assertThat(newDir.get(Name.PARENT)).isEqualTo(entry(newDir, "..", dir)); assertThat(newDir.links()).isEqualTo(2); } @Test public void testUnlinkDirectory() { Directory newDir = Directory.create(10); dir.link(Name.simple("foo"), newDir); assertThat(dir.links()).isEqualTo(3); assertThat(newDir.entryInParent()).isEqualTo(entry(dir, "foo", newDir)); assertThat(newDir.links()).isEqualTo(2); dir.unlink(Name.simple("foo")); assertThat(dir.links()).isEqualTo(2); assertThat(newDir.entryInParent()).isEqualTo(entry(dir, "foo", newDir)); assertThat(newDir.get(Name.SELF).file()).isEqualTo(newDir); assertThat(newDir.get(Name.PARENT)).isEqualTo(entry(newDir, "..", dir)); assertThat(newDir.links()).isEqualTo(1); } @Test public void testSnapshot() { root.link(Name.simple("bar"), regularFile(10)); root.link(Name.simple("abc"), regularFile(10)); // does not include . or .. and is sorted by the name assertThat(root.snapshot()) .containsExactly(Name.simple("abc"), Name.simple("bar"), Name.simple("foo")); } @Test public void testSnapshot_sortsUsingStringAndNotCanonicalValueOfNames() { dir.link(caseInsensitive("FOO"), regularFile(10)); dir.link(caseInsensitive("bar"), regularFile(10)); ImmutableSortedSet snapshot = dir.snapshot(); Iterable strings = Iterables.transform(snapshot, Functions.toStringFunction()); // "FOO" comes before "bar" // if the order were based on the normalized, canonical form of the names ("foo" and "bar"), // "bar" would come first assertThat(strings).containsExactly("FOO", "bar").inOrder(); } // Tests for internal hash table implementation private static final Directory A = Directory.create(0); @Test public void testInitialState() { assertThat(dir.entryCount()).isEqualTo(2); assertThat(ImmutableSet.copyOf(dir)) .containsExactly( new DirectoryEntry(dir, Name.SELF, dir), new DirectoryEntry(dir, Name.PARENT, root)); assertThat(dir.get(Name.simple("foo"))).isNull(); } @Test public void testPutAndGet() { dir.put(entry("foo")); assertThat(dir.entryCount()).isEqualTo(3); assertThat(ImmutableSet.copyOf(dir)).contains(entry("foo")); assertThat(dir.get(Name.simple("foo"))).isEqualTo(entry("foo")); dir.put(entry("bar")); assertThat(dir.entryCount()).isEqualTo(4); assertThat(ImmutableSet.copyOf(dir)).containsAllOf(entry("foo"), entry("bar")); assertThat(dir.get(Name.simple("foo"))).isEqualTo(entry("foo")); assertThat(dir.get(Name.simple("bar"))).isEqualTo(entry("bar")); } @Test public void testPutEntryForExistingNameIsIllegal() { dir.put(entry("foo")); try { dir.put(entry("foo")); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testRemove() { dir.put(entry("foo")); dir.put(entry("bar")); dir.remove(Name.simple("foo")); assertThat(dir.entryCount()).isEqualTo(3); assertThat(ImmutableSet.copyOf(dir)) .containsExactly( entry("bar"), new DirectoryEntry(dir, Name.SELF, dir), new DirectoryEntry(dir, Name.PARENT, root)); assertThat(dir.get(Name.simple("foo"))).isNull(); assertThat(dir.get(Name.simple("bar"))).isEqualTo(entry("bar")); dir.remove(Name.simple("bar")); assertThat(dir.entryCount()).isEqualTo(2); dir.put(entry("bar")); dir.put(entry("foo")); // these should just succeeded } @Test public void testManyPutsAndRemoves() { // test resizing/rehashing Set entriesInDir = new HashSet<>(); entriesInDir.add(new DirectoryEntry(dir, Name.SELF, dir)); entriesInDir.add(new DirectoryEntry(dir, Name.PARENT, root)); // add 1000 entries for (int i = 0; i < 1000; i++) { DirectoryEntry entry = entry(String.valueOf(i)); dir.put(entry); entriesInDir.add(entry); assertThat(ImmutableSet.copyOf(dir)).isEqualTo(entriesInDir); for (DirectoryEntry expected : entriesInDir) { assertThat(dir.get(expected.name())).isEqualTo(expected); } } // remove 1000 entries for (int i = 0; i < 1000; i++) { dir.remove(Name.simple(String.valueOf(i))); entriesInDir.remove(entry(String.valueOf(i))); assertThat(ImmutableSet.copyOf(dir)).isEqualTo(entriesInDir); for (DirectoryEntry expected : entriesInDir) { assertThat(dir.get(expected.name())).isEqualTo(expected); } } // mixed adds and removes for (int i = 0; i < 10000; i++) { DirectoryEntry entry = entry(String.valueOf(i)); dir.put(entry); entriesInDir.add(entry); if (i > 0 && i % 20 == 0) { String nameToRemove = String.valueOf(i / 2); dir.remove(Name.simple(nameToRemove)); entriesInDir.remove(entry(nameToRemove)); } } // for this one, only test that the end result is correct // takes too long to test at each iteration assertThat(ImmutableSet.copyOf(dir)).isEqualTo(entriesInDir); for (DirectoryEntry expected : entriesInDir) { assertThat(dir.get(expected.name())).isEqualTo(expected); } } private static DirectoryEntry entry(String name) { return new DirectoryEntry(A, Name.simple(name), A); } private static DirectoryEntry entry(Directory dir, String name, @Nullable File file) { return new DirectoryEntry(dir, Name.simple(name), file); } private static void assertParentAndSelf(Directory dir, File parent, File self) { assertThat(dir).isEqualTo(self); assertThat(dir.parent()).isEqualTo(parent); assertThat(dir.get(PARENT)).isEqualTo(entry((Directory) self, "..", parent)); assertThat(dir.get(SELF)).isEqualTo(entry((Directory) self, ".", self)); } private static Name caseInsensitive(String name) { return Name.create(name, PathNormalization.CASE_FOLD_UNICODE.apply(name)); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/DosAttributeProviderTest.java000066400000000000000000000075531265745405500305720ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import static org.junit.Assert.assertNotNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.attribute.DosFileAttributeView; import java.nio.file.attribute.DosFileAttributes; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileTime; import java.util.Set; /** * Tests for {@link DosAttributeProvider}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class DosAttributeProviderTest extends AbstractAttributeProviderTest { private static final ImmutableList DOS_ATTRIBUTES = ImmutableList.of("hidden", "archive", "readonly", "system"); @Override protected DosAttributeProvider createProvider() { return new DosAttributeProvider(); } @Override protected Set createInheritedProviders() { return ImmutableSet.of(new BasicAttributeProvider(), new OwnerAttributeProvider()); } @Test public void testInitialAttributes() { for (String attribute : DOS_ATTRIBUTES) { assertThat(provider.get(file, attribute)).isEqualTo(false); } } @Test public void testSet() { for (String attribute : DOS_ATTRIBUTES) { assertSetAndGetSucceeds(attribute, true); assertSetFailsOnCreate(attribute, true); } } @Test public void testView() throws IOException { DosFileAttributeView view = provider.view( fileLookup(), ImmutableMap.of( "basic", new BasicAttributeProvider().view(fileLookup(), NO_INHERITED_VIEWS))); assertNotNull(view); assertThat(view.name()).isEqualTo("dos"); DosFileAttributes attrs = view.readAttributes(); assertThat(attrs.isHidden()).isFalse(); assertThat(attrs.isArchive()).isFalse(); assertThat(attrs.isReadOnly()).isFalse(); assertThat(attrs.isSystem()).isFalse(); view.setArchive(true); view.setReadOnly(true); view.setHidden(true); view.setSystem(false); assertThat(attrs.isHidden()).isFalse(); assertThat(attrs.isArchive()).isFalse(); assertThat(attrs.isReadOnly()).isFalse(); attrs = view.readAttributes(); assertThat(attrs.isHidden()).isTrue(); assertThat(attrs.isArchive()).isTrue(); assertThat(attrs.isReadOnly()).isTrue(); assertThat(attrs.isSystem()).isFalse(); view.setTimes(FileTime.fromMillis(0L), null, null); assertThat(view.readAttributes().lastModifiedTime()).isEqualTo(FileTime.fromMillis(0L)); } @Test public void testAttributes() { DosFileAttributes attrs = provider.readAttributes(file); assertThat(attrs.isHidden()).isFalse(); assertThat(attrs.isArchive()).isFalse(); assertThat(attrs.isReadOnly()).isFalse(); assertThat(attrs.isSystem()).isFalse(); file.setAttribute("dos", "hidden", true); attrs = provider.readAttributes(file); assertThat(attrs.isHidden()).isTrue(); assertThat(attrs.isArchive()).isFalse(); assertThat(attrs.isReadOnly()).isFalse(); assertThat(attrs.isSystem()).isFalse(); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/FileFactoryTest.java000066400000000000000000000040431265745405500266440ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for {@link FileFactory}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class FileFactoryTest { private FileFactory factory; @Before public void setUp() { factory = new FileFactory(new HeapDisk(2, 2, 0)); } @Test public void testCreateFiles_basic() { File file = factory.createDirectory(); assertThat(file.id()).isEqualTo(0L); assertThat(file.isDirectory()).isTrue(); file = factory.createRegularFile(); assertThat(file.id()).isEqualTo(1L); assertThat(file.isRegularFile()).isTrue(); file = factory.createSymbolicLink(fakePath()); assertThat(file.id()).isEqualTo(2L); assertThat(file.isSymbolicLink()).isTrue(); } @Test public void testCreateFiles_withSupplier() { File file = factory.directoryCreator().get(); assertThat(file.id()).isEqualTo(0L); assertThat(file.isDirectory()).isTrue(); file = factory.regularFileCreator().get(); assertThat(file.id()).isEqualTo(1L); assertThat(file.isRegularFile()).isTrue(); file = factory.symbolicLinkCreator(fakePath()).get(); assertThat(file.id()).isEqualTo(2L); assertThat(file.isSymbolicLink()).isTrue(); } static JimfsPath fakePath() { return PathServiceTest.fakeUnixPathService().emptyPath(); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/FileSystemStateTest.java000066400000000000000000000112361265745405500275240ustar00rootroot00000000000000/* * 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 org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.Closeable; import java.io.IOException; import java.nio.file.ClosedFileSystemException; import java.util.List; /** * Tests for {@link FileSystemState}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class FileSystemStateTest { private final TestRunnable onClose = new TestRunnable(); private final FileSystemState state = new FileSystemState(onClose); @Test public void testIsOpen() throws IOException { assertTrue(state.isOpen()); state.close(); assertFalse(state.isOpen()); } @Test public void testCheckOpen() throws IOException { state.checkOpen(); // does not throw state.close(); try { state.checkOpen(); fail(); } catch (ClosedFileSystemException expected) { } } @Test public void testClose_callsOnCloseRunnable() throws IOException { assertEquals(0, onClose.runCount); state.close(); assertEquals(1, onClose.runCount); } @Test public void testClose_multipleTimesDoNothing() throws IOException { state.close(); assertEquals(1, onClose.runCount); state.close(); state.close(); assertEquals(1, onClose.runCount); } @Test public void testClose_registeredResourceIsClosed() throws IOException { TestCloseable resource = new TestCloseable(); state.register(resource); assertFalse(resource.closed); state.close(); assertTrue(resource.closed); } @Test public void testClose_unregisteredResourceIsNotClosed() throws IOException { TestCloseable resource = new TestCloseable(); state.register(resource); assertFalse(resource.closed); state.unregister(resource); state.close(); assertFalse(resource.closed); } @Test public void testClose_multipleRegisteredResourcesAreClosed() throws IOException { List resources = ImmutableList.of(new TestCloseable(), new TestCloseable(), new TestCloseable()); for (TestCloseable resource : resources) { state.register(resource); assertFalse(resource.closed); } state.close(); for (TestCloseable resource : resources) { assertTrue(resource.closed); } } @Test public void testClose_resourcesThatThrowOnClose() { List resources = ImmutableList.of( new TestCloseable(), new ThrowsOnClose("a"), new TestCloseable(), new ThrowsOnClose("b"), new ThrowsOnClose("c"), new TestCloseable(), new TestCloseable()); for (TestCloseable resource : resources) { state.register(resource); assertFalse(resource.closed); } try { state.close(); fail(); } catch (IOException expected) { Throwable[] suppressed = expected.getSuppressed(); assertEquals(2, suppressed.length); ImmutableSet messages = ImmutableSet.of( expected.getMessage(), suppressed[0].getMessage(), suppressed[1].getMessage()); assertEquals(ImmutableSet.of("a", "b", "c"), messages); } for (TestCloseable resource : resources) { assertTrue(resource.closed); } } private static class TestCloseable implements Closeable { boolean closed = false; @Override public void close() throws IOException { closed = true; } } private static final class TestRunnable implements Runnable { int runCount = 0; @Override public void run() { runCount++; } } private static class ThrowsOnClose extends TestCloseable { private final String string; private ThrowsOnClose(String string) { this.string = string; } @Override public void close() throws IOException { super.close(); throw new IOException(string); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/FileTest.java000066400000000000000000000063351265745405500253220ustar00rootroot00000000000000/* * 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.jimfs.FileFactoryTest.fakePath; import static com.google.common.jimfs.TestUtils.regularFile; import static com.google.common.truth.Truth.assertThat; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for {@link File}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class FileTest { @Test public void testAttributes() { // these methods are basically just thin wrappers around a map, so no need to test too // thoroughly File file = RegularFile.create(0, new HeapDisk(10, 10, 10)); assertThat(file.getAttributeKeys()).isEmpty(); assertThat(file.getAttribute("foo", "foo")).isNull(); file.deleteAttribute("foo", "foo"); // doesn't throw file.setAttribute("foo", "foo", "foo"); assertThat(file.getAttributeKeys()).containsExactly("foo:foo"); assertThat(file.getAttribute("foo", "foo")).isEqualTo("foo"); file.deleteAttribute("foo", "foo"); assertThat(file.getAttributeKeys()).isEmpty(); assertThat(file.getAttribute("foo", "foo")).isNull(); } @Test public void testFileBasics() { File file = regularFile(0); assertThat(file.id()).isEqualTo(0); assertThat(file.links()).isEqualTo(0); } @Test public void testDirectory() { File file = Directory.create(0); assertThat(file.isDirectory()).isTrue(); assertThat(file.isRegularFile()).isFalse(); assertThat(file.isSymbolicLink()).isFalse(); } @Test public void testRegularFile() { File file = regularFile(10); assertThat(file.isDirectory()).isFalse(); assertThat(file.isRegularFile()).isTrue(); assertThat(file.isSymbolicLink()).isFalse(); } @Test public void testSymbolicLink() { File file = SymbolicLink.create(0, fakePath()); assertThat(file.isDirectory()).isFalse(); assertThat(file.isRegularFile()).isFalse(); assertThat(file.isSymbolicLink()).isTrue(); } @Test public void testRootDirectory() { Directory file = Directory.createRoot(0, Name.simple("/")); assertThat(file.isRootDirectory()).isTrue(); Directory otherFile = Directory.createRoot(1, Name.simple("$")); assertThat(otherFile.isRootDirectory()).isTrue(); } @Test public void testLinkAndUnlink() { File file = regularFile(0); assertThat(file.links()).isEqualTo(0); file.incrementLinkCount(); assertThat(file.links()).isEqualTo(1); file.incrementLinkCount(); assertThat(file.links()).isEqualTo(2); file.decrementLinkCount(); assertThat(file.links()).isEqualTo(1); file.decrementLinkCount(); assertThat(file.links()).isEqualTo(0); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/FileTreeTest.java000066400000000000000000000342421265745405500261400ustar00rootroot00000000000000/* * 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.jimfs.TestUtils.regularFile; import static com.google.common.truth.Truth.assertThat; import static java.nio.file.LinkOption.NOFOLLOW_LINKS; import static org.junit.Assert.fail; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.base.Strings; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.util.HashMap; import java.util.Map; import java.util.Random; import javax.annotation.Nullable; /** * Tests for {@link FileTree}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class FileTreeTest { /* * Directory structure. Each file should have a unique name. * * / * work/ * one/ * two/ * three/ * eleven * four/ * five -> /foo * six -> ../one * loop -> ../four/loop * foo/ * bar/ * $ * a/ * b/ * c/ */ /** * This path service is for unix-like paths, with the exception that it recognizes $ and ! as * roots in addition to /, allowing for up to three roots. When creating a * {@linkplain PathType#toUriPath URI path}, we prefix the path with / to differentiate between * a path like "$foo/bar" and one like "/$foo/bar". They would become "/$foo/bar" and * "//$foo/bar" respectively. */ private final PathService pathService = PathServiceTest.fakePathService( new PathType(true, '/') { @Override public ParseResult parsePath(String path) { String root = null; if (path.matches("^[/$!].*")) { root = path.substring(0, 1); path = path.substring(1); } return new ParseResult(root, Splitter.on('/').omitEmptyStrings().split(path)); } @Override public String toString(@Nullable String root, Iterable names) { root = Strings.nullToEmpty(root); return root + Joiner.on('/').join(names); } @Override public String toUriPath(String root, Iterable names, boolean directory) { // need to add extra / to differentiate between paths "/$foo/bar" and "$foo/bar". return "/" + toString(root, names); } @Override public ParseResult parseUriPath(String uriPath) { checkArgument( uriPath.matches("^/[/$!].*"), "uriPath (%s) must start with // or /$ or /!"); return parsePath(uriPath.substring(1)); // skip leading / } }, false); private FileTree fileTree; private File workingDirectory; private final Map files = new HashMap<>(); @Before public void setUp() { Directory root = Directory.createRoot(0, Name.simple("/")); files.put("/", root); Directory otherRoot = Directory.createRoot(2, Name.simple("$")); files.put("$", otherRoot); Map roots = new HashMap<>(); roots.put(Name.simple("/"), root); roots.put(Name.simple("$"), otherRoot); fileTree = new FileTree(roots); workingDirectory = createDirectory("/", "work"); createDirectory("work", "one"); createDirectory("one", "two"); createFile("one", "eleven"); createDirectory("two", "three"); createDirectory("work", "four"); createSymbolicLink("four", "five", "/foo"); createSymbolicLink("four", "six", "../one"); createSymbolicLink("four", "loop", "../four/loop"); createDirectory("/", "foo"); createDirectory("foo", "bar"); createDirectory("$", "a"); createDirectory("a", "b"); createDirectory("b", "c"); } // absolute lookups @Test public void testLookup_root() throws IOException { assertExists(lookup("/"), "/", "/"); assertExists(lookup("$"), "$", "$"); } @Test public void testLookup_nonExistentRoot() throws IOException { try { lookup("!"); fail(); } catch (NoSuchFileException expected) { } try { lookup("!a"); fail(); } catch (NoSuchFileException expected) { } } @Test public void testLookup_absolute() throws IOException { assertExists(lookup("/work"), "/", "work"); assertExists(lookup("/work/one/two/three"), "two", "three"); assertExists(lookup("$a"), "$", "a"); assertExists(lookup("$a/b/c"), "b", "c"); } @Test public void testLookup_absolute_notExists() throws IOException { try { lookup("/a/b"); fail(); } catch (NoSuchFileException expected) { } try { lookup("/work/one/foo/bar"); fail(); } catch (NoSuchFileException expected) { } try { lookup("$c/d"); fail(); } catch (NoSuchFileException expected) { } try { lookup("$a/b/c/d/e"); fail(); } catch (NoSuchFileException expected) { } } @Test public void testLookup_absolute_parentExists() throws IOException { assertParentExists(lookup("/a"), "/"); assertParentExists(lookup("/foo/baz"), "foo"); assertParentExists(lookup("$c"), "$"); assertParentExists(lookup("$a/b/c/d"), "c"); } @Test public void testLookup_absolute_nonDirectoryIntermediateFile() throws IOException { try { lookup("/work/one/eleven/twelve"); fail(); } catch (NoSuchFileException expected) { } try { lookup("/work/one/eleven/twelve/thirteen/fourteen"); fail(); } catch (NoSuchFileException expected) { } } @Test public void testLookup_absolute_intermediateSymlink() throws IOException { assertExists(lookup("/work/four/five/bar"), "foo", "bar"); assertExists(lookup("/work/four/six/two/three"), "two", "three"); // NOFOLLOW_LINKS doesn't affect intermediate symlinks assertExists(lookup("/work/four/five/bar", NOFOLLOW_LINKS), "foo", "bar"); assertExists(lookup("/work/four/six/two/three", NOFOLLOW_LINKS), "two", "three"); } @Test public void testLookup_absolute_intermediateSymlink_parentExists() throws IOException { assertParentExists(lookup("/work/four/five/baz"), "foo"); assertParentExists(lookup("/work/four/six/baz"), "one"); } @Test public void testLookup_absolute_finalSymlink() throws IOException { assertExists(lookup("/work/four/five"), "/", "foo"); assertExists(lookup("/work/four/six"), "work", "one"); } @Test public void testLookup_absolute_finalSymlink_nofollowLinks() throws IOException { assertExists(lookup("/work/four/five", NOFOLLOW_LINKS), "four", "five"); assertExists(lookup("/work/four/six", NOFOLLOW_LINKS), "four", "six"); assertExists(lookup("/work/four/loop", NOFOLLOW_LINKS), "four", "loop"); } @Test public void testLookup_absolute_symlinkLoop() { try { lookup("/work/four/loop"); fail(); } catch (IOException expected) { } try { lookup("/work/four/loop/whatever"); fail(); } catch (IOException expected) { } } @Test public void testLookup_absolute_withDotsInPath() throws IOException { assertExists(lookup("/."), "/", "/"); assertExists(lookup("/./././."), "/", "/"); assertExists(lookup("/work/./one/./././two/three"), "two", "three"); assertExists(lookup("/work/./one/./././two/././three"), "two", "three"); assertExists(lookup("/work/./one/./././two/three/././."), "two", "three"); } @Test public void testLookup_absolute_withDotDotsInPath() throws IOException { assertExists(lookup("/.."), "/", "/"); assertExists(lookup("/../../.."), "/", "/"); assertExists(lookup("/work/.."), "/", "/"); assertExists(lookup("/work/../work/one/two/../two/three"), "two", "three"); assertExists(lookup("/work/one/two/../../four/../one/two/three/../three"), "two", "three"); assertExists(lookup("/work/one/two/three/../../two/three/.."), "one", "two"); assertExists(lookup("/work/one/two/three/../../two/three/../.."), "work", "one"); } @Test public void testLookup_absolute_withDotDotsInPath_afterSymlink() throws IOException { assertExists(lookup("/work/four/five/.."), "/", "/"); assertExists(lookup("/work/four/six/.."), "/", "work"); } // relative lookups @Test public void testLookup_relative() throws IOException { assertExists(lookup("one"), "work", "one"); assertExists(lookup("one/two/three"), "two", "three"); } @Test public void testLookup_relative_notExists() throws IOException { try { lookup("a/b"); fail(); } catch (NoSuchFileException expected) { } try { lookup("one/foo/bar"); fail(); } catch (NoSuchFileException expected) { } } @Test public void testLookup_relative_parentExists() throws IOException { assertParentExists(lookup("a"), "work"); assertParentExists(lookup("one/two/four"), "two"); } @Test public void testLookup_relative_nonDirectoryIntermediateFile() throws IOException { try { lookup("one/eleven/twelve"); fail(); } catch (NoSuchFileException expected) { } try { lookup("one/eleven/twelve/thirteen/fourteen"); fail(); } catch (NoSuchFileException expected) { } } @Test public void testLookup_relative_intermediateSymlink() throws IOException { assertExists(lookup("four/five/bar"), "foo", "bar"); assertExists(lookup("four/six/two/three"), "two", "three"); // NOFOLLOW_LINKS doesn't affect intermediate symlinks assertExists(lookup("four/five/bar", NOFOLLOW_LINKS), "foo", "bar"); assertExists(lookup("four/six/two/three", NOFOLLOW_LINKS), "two", "three"); } @Test public void testLookup_relative_intermediateSymlink_parentExists() throws IOException { assertParentExists(lookup("four/five/baz"), "foo"); assertParentExists(lookup("four/six/baz"), "one"); } @Test public void testLookup_relative_finalSymlink() throws IOException { assertExists(lookup("four/five"), "/", "foo"); assertExists(lookup("four/six"), "work", "one"); } @Test public void testLookup_relative_finalSymlink_nofollowLinks() throws IOException { assertExists(lookup("four/five", NOFOLLOW_LINKS), "four", "five"); assertExists(lookup("four/six", NOFOLLOW_LINKS), "four", "six"); assertExists(lookup("four/loop", NOFOLLOW_LINKS), "four", "loop"); } @Test public void testLookup_relative_symlinkLoop() { try { lookup("four/loop"); fail(); } catch (IOException expected) { } try { lookup("four/loop/whatever"); fail(); } catch (IOException expected) { } } @Test public void testLookup_relative_emptyPath() throws IOException { assertExists(lookup(""), "/", "work"); } @Test public void testLookup_relative_withDotsInPath() throws IOException { assertExists(lookup("."), "/", "work"); assertExists(lookup("././."), "/", "work"); assertExists(lookup("./one/./././two/three"), "two", "three"); assertExists(lookup("./one/./././two/././three"), "two", "three"); assertExists(lookup("./one/./././two/three/././."), "two", "three"); } @Test public void testLookup_relative_withDotDotsInPath() throws IOException { assertExists(lookup(".."), "/", "/"); assertExists(lookup("../../.."), "/", "/"); assertExists(lookup("../work"), "/", "work"); assertExists(lookup("../../work"), "/", "work"); assertExists(lookup("../foo"), "/", "foo"); assertExists(lookup("../work/one/two/../two/three"), "two", "three"); assertExists(lookup("one/two/../../four/../one/two/three/../three"), "two", "three"); assertExists(lookup("one/two/three/../../two/three/.."), "one", "two"); assertExists(lookup("one/two/three/../../two/three/../.."), "work", "one"); } @Test public void testLookup_relative_withDotDotsInPath_afterSymlink() throws IOException { assertExists(lookup("four/five/.."), "/", "/"); assertExists(lookup("four/six/.."), "/", "work"); } private DirectoryEntry lookup(String path, LinkOption... options) throws IOException { JimfsPath pathObj = pathService.parsePath(path); return fileTree.lookUp(workingDirectory, pathObj, Options.getLinkOptions(options)); } private void assertExists(DirectoryEntry entry, String parent, String file) { assertThat(entry.exists()).isTrue(); assertThat(entry.name()).isEqualTo(Name.simple(file)); assertThat(entry.directory()).isEqualTo(files.get(parent)); assertThat(entry.file()).isEqualTo(files.get(file)); } private void assertParentExists(DirectoryEntry entry, String parent) { assertThat(entry.exists()).isFalse(); assertThat(entry.directory()).isEqualTo(files.get(parent)); try { entry.file(); fail(); } catch (IllegalStateException expected) { } } private File createDirectory(String parent, String name) { Directory dir = (Directory) files.get(parent); Directory newFile = Directory.create(new Random().nextInt()); dir.link(Name.simple(name), newFile); files.put(name, newFile); return newFile; } private File createFile(String parent, String name) { Directory dir = (Directory) files.get(parent); File newFile = regularFile(0); dir.link(Name.simple(name), newFile); files.put(name, newFile); return newFile; } private File createSymbolicLink(String parent, String name, String target) { Directory dir = (Directory) files.get(parent); File newFile = SymbolicLink.create(new Random().nextInt(), pathService.parsePath(target)); dir.link(Name.simple(name), newFile); files.put(name, newFile); return newFile; } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/HeapDiskTest.java000066400000000000000000000150071265745405500261270ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Tests for {@link HeapDisk}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class HeapDiskTest { private RegularFile blocks; @Before public void setUp() { // the HeapDisk of this file is unused; it's passed to other HeapDisks to test operations blocks = RegularFile.create(-1, new HeapDisk(2, 2, 2)); } @Test public void testInitialSettings_basic() { HeapDisk disk = new HeapDisk(8192, 100, 100); assertThat(disk.blockSize()).isEqualTo(8192); assertThat(disk.getTotalSpace()).isEqualTo(819200); assertThat(disk.getUnallocatedSpace()).isEqualTo(819200); assertThat(disk.blockCache.blockCount()).isEqualTo(0); } @Test public void testInitialSettings_fromConfiguration() { Configuration config = Configuration.unix() .toBuilder() .setBlockSize(4) .setMaxSize(99) // not a multiple of 4 .setMaxCacheSize(25) .build(); HeapDisk disk = new HeapDisk(config); assertThat(disk.blockSize()).isEqualTo(4); assertThat(disk.getTotalSpace()).isEqualTo(96); assertThat(disk.getUnallocatedSpace()).isEqualTo(96); assertThat(disk.blockCache.blockCount()).isEqualTo(0); } @Test public void testAllocate() throws IOException { HeapDisk disk = new HeapDisk(4, 10, 0); disk.allocate(blocks, 1); assertThat(blocks.blockCount()).isEqualTo(1); assertThat(blocks.getBlock(0).length).isEqualTo(4); assertThat(disk.getUnallocatedSpace()).isEqualTo(36); disk.allocate(blocks, 5); assertThat(blocks.blockCount()).isEqualTo(6); for (int i = 0; i < blocks.blockCount(); i++) { assertThat(blocks.getBlock(i).length).isEqualTo(4); } assertThat(disk.getUnallocatedSpace()).isEqualTo(16); assertThat(disk.blockCache.blockCount()).isEqualTo(0); } @Test public void testFree_noCaching() throws IOException { HeapDisk disk = new HeapDisk(4, 10, 0); disk.allocate(blocks, 6); disk.free(blocks, 2); assertThat(blocks.blockCount()).isEqualTo(4); assertThat(disk.getUnallocatedSpace()).isEqualTo(24); assertThat(disk.blockCache.blockCount()).isEqualTo(0); disk.free(blocks); assertThat(blocks.blockCount()).isEqualTo(0); assertThat(disk.getUnallocatedSpace()).isEqualTo(40); assertThat(disk.blockCache.blockCount()).isEqualTo(0); } @Test public void testFree_fullCaching() throws IOException { HeapDisk disk = new HeapDisk(4, 10, 10); disk.allocate(blocks, 6); disk.free(blocks, 2); assertThat(blocks.blockCount()).isEqualTo(4); assertThat(disk.getUnallocatedSpace()).isEqualTo(24); assertThat(disk.blockCache.blockCount()).isEqualTo(2); disk.free(blocks); assertThat(blocks.blockCount()).isEqualTo(0); assertThat(disk.getUnallocatedSpace()).isEqualTo(40); assertThat(disk.blockCache.blockCount()).isEqualTo(6); } @Test public void testFree_partialCaching() throws IOException { HeapDisk disk = new HeapDisk(4, 10, 4); disk.allocate(blocks, 6); disk.free(blocks, 2); assertThat(blocks.blockCount()).isEqualTo(4); assertThat(disk.getUnallocatedSpace()).isEqualTo(24); assertThat(disk.blockCache.blockCount()).isEqualTo(2); disk.free(blocks); assertThat(blocks.blockCount()).isEqualTo(0); assertThat(disk.getUnallocatedSpace()).isEqualTo(40); assertThat(disk.blockCache.blockCount()).isEqualTo(4); } @Test public void testAllocateFromCache_fullAllocationFromCache() throws IOException { HeapDisk disk = new HeapDisk(4, 10, 10); disk.allocate(blocks, 10); assertThat(disk.getUnallocatedSpace()).isEqualTo(0); disk.free(blocks); assertThat(blocks.blockCount()).isEqualTo(0); assertThat(disk.blockCache.blockCount()).isEqualTo(10); List cachedBlocks = new ArrayList<>(); for (int i = 0; i < 10; i++) { cachedBlocks.add(disk.blockCache.getBlock(i)); } disk.allocate(blocks, 6); assertThat(blocks.blockCount()).isEqualTo(6); assertThat(disk.blockCache.blockCount()).isEqualTo(4); // the 6 arrays in blocks are the last 6 arrays that were cached for (int i = 0; i < 6; i++) { assertThat(blocks.getBlock(i)).isEqualTo(cachedBlocks.get(i + 4)); } } @Test public void testAllocateFromCache_partialAllocationFromCache() throws IOException { HeapDisk disk = new HeapDisk(4, 10, 4); disk.allocate(blocks, 10); assertThat(disk.getUnallocatedSpace()).isEqualTo(0); disk.free(blocks); assertThat(blocks.blockCount()).isEqualTo(0); assertThat(disk.blockCache.blockCount()).isEqualTo(4); List cachedBlocks = new ArrayList<>(); for (int i = 0; i < 4; i++) { cachedBlocks.add(disk.blockCache.getBlock(i)); } disk.allocate(blocks, 6); assertThat(blocks.blockCount()).isEqualTo(6); assertThat(disk.blockCache.blockCount()).isEqualTo(0); // the last 4 arrays in blocks are the 4 arrays that were cached for (int i = 2; i < 6; i++) { assertThat(blocks.getBlock(i)).isEqualTo(cachedBlocks.get(i - 2)); } } @Test public void testFullDisk() throws IOException { HeapDisk disk = new HeapDisk(4, 10, 4); disk.allocate(blocks, 10); try { disk.allocate(blocks, 1); fail(); } catch (IOException expected) { } } @Test public void testFullDisk_doesNotAllocatePartiallyWhenTooManyBlocksRequested() throws IOException { HeapDisk disk = new HeapDisk(4, 10, 4); disk.allocate(blocks, 6); RegularFile blocks2 = RegularFile.create(-2, disk); try { disk.allocate(blocks2, 5); fail(); } catch (IOException expected) { } assertThat(blocks2.blockCount()).isEqualTo(0); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java000066400000000000000000000223521265745405500322150ustar00rootroot00000000000000/* * 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.jimfs.TestUtils.buffer; import static com.google.common.jimfs.TestUtils.regularFile; import static com.google.common.truth.Truth.assertThat; import static java.nio.file.StandardOpenOption.READ; import static java.nio.file.StandardOpenOption.WRITE; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Runnables; import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.Uninterruptibles; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousCloseException; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.ClosedChannelException; import java.nio.channels.CompletionHandler; import java.nio.channels.FileLock; import java.nio.file.OpenOption; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * Tests for {@link JimfsAsynchronousFileChannel}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class JimfsAsynchronousFileChannelTest { private static JimfsAsynchronousFileChannel channel( RegularFile file, ExecutorService executor, OpenOption... options) throws IOException { JimfsFileChannel channel = new JimfsFileChannel( file, Options.getOptionsForChannel(ImmutableSet.copyOf(options)), new FileSystemState(Runnables.doNothing())); return new JimfsAsynchronousFileChannel(channel, executor); } /** * Just tests the main read/write methods... the methods all delegate to the non-async channel * anyway. */ @Test public void testAsyncChannel() throws Throwable { RegularFile file = regularFile(15); ExecutorService executor = Executors.newSingleThreadExecutor(); JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE); try { assertEquals(15, channel.size()); assertSame(channel, channel.truncate(5)); assertEquals(5, channel.size()); file.write(5, new byte[5], 0, 5); checkAsyncRead(channel); checkAsyncWrite(channel); checkAsyncLock(channel); channel.close(); assertFalse(channel.isOpen()); } finally { executor.shutdown(); } } @Test public void testClosedChannel() throws Throwable { RegularFile file = regularFile(15); ExecutorService executor = Executors.newSingleThreadExecutor(); try { JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE); channel.close(); assertClosed(channel.read(ByteBuffer.allocate(10), 0)); assertClosed(channel.write(ByteBuffer.allocate(10), 15)); assertClosed(channel.lock()); assertClosed(channel.lock(0, 10, true)); } finally { executor.shutdown(); } } @Test public void testAsyncClose_write() throws Throwable { RegularFile file = regularFile(15); ExecutorService executor = Executors.newFixedThreadPool(4); try { JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE); file.writeLock().lock(); // cause another thread trying to write to block // future-returning write Future future = channel.write(ByteBuffer.allocate(10), 0); // completion handler write SettableFuture completionHandlerFuture = SettableFuture.create(); channel.write(ByteBuffer.allocate(10), 0, null, setFuture(completionHandlerFuture)); // Despite this 10ms sleep to allow plenty of time, it's possible, though very rare, for a // race to cause the channel to be closed before the asynchronous calls get to the initial // check that the channel is open, causing ClosedChannelException to be thrown rather than // AsynchronousCloseException. This is not a problem in practice, just a quirk of how these // tests work and that we don't have a way of waiting for the operations to get past that // check. Uninterruptibles.sleepUninterruptibly(10, MILLISECONDS); channel.close(); assertAsynchronousClose(future); assertAsynchronousClose(completionHandlerFuture); } finally { executor.shutdown(); } } @Test public void testAsyncClose_read() throws Throwable { RegularFile file = regularFile(15); ExecutorService executor = Executors.newFixedThreadPool(2); try { JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE); file.writeLock().lock(); // cause another thread trying to read to block // future-returning read Future future = channel.read(ByteBuffer.allocate(10), 0); // completion handler read SettableFuture completionHandlerFuture = SettableFuture.create(); channel.read(ByteBuffer.allocate(10), 0, null, setFuture(completionHandlerFuture)); // Despite this 10ms sleep to allow plenty of time, it's possible, though very rare, for a // race to cause the channel to be closed before the asynchronous calls get to the initial // check that the channel is open, causing ClosedChannelException to be thrown rather than // AsynchronousCloseException. This is not a problem in practice, just a quirk of how these // tests work and that we don't have a way of waiting for the operations to get past that // check. Uninterruptibles.sleepUninterruptibly(10, MILLISECONDS); channel.close(); assertAsynchronousClose(future); assertAsynchronousClose(completionHandlerFuture); } finally { executor.shutdown(); } } private static void checkAsyncRead(AsynchronousFileChannel channel) throws Throwable { ByteBuffer buf = buffer("1234567890"); assertEquals(10, (int) channel.read(buf, 0).get()); buf.flip(); SettableFuture future = SettableFuture.create(); channel.read(buf, 0, null, setFuture(future)); assertThat(future.get(10, SECONDS)).isEqualTo(10); } private static void checkAsyncWrite(AsynchronousFileChannel asyncChannel) throws Throwable { ByteBuffer buf = buffer("1234567890"); assertEquals(10, (int) asyncChannel.write(buf, 0).get()); buf.flip(); SettableFuture future = SettableFuture.create(); asyncChannel.write(buf, 0, null, setFuture(future)); assertThat(future.get(10, SECONDS)).isEqualTo(10); } private static void checkAsyncLock(AsynchronousFileChannel channel) throws Throwable { assertNotNull(channel.lock().get()); assertNotNull(channel.lock(0, 10, true).get()); SettableFuture future = SettableFuture.create(); channel.lock(0, 10, true, null, setFuture(future)); assertNotNull(future.get(10, SECONDS)); } /** * Returns a {@code CompletionHandler} that sets the appropriate result or exception on the given * {@code future} on completion. */ private static CompletionHandler setFuture(final SettableFuture future) { return new CompletionHandler() { @Override public void completed(T result, Object attachment) { future.set(result); } @Override public void failed(Throwable exc, Object attachment) { future.setException(exc); } }; } /** * Assert that the future fails, with the failure caused by {@code ClosedChannelException}. */ private static void assertClosed(Future future) throws Throwable { try { future.get(10, SECONDS); fail("ChannelClosedException was not thrown"); } catch (ExecutionException expected) { assertThat(expected.getCause()).isInstanceOf(ClosedChannelException.class); } } /** * Assert that the future fails, with the failure caused by either * {@code AsynchronousCloseException} or (rarely) {@code ClosedChannelException}. */ private static void assertAsynchronousClose(Future future) throws Throwable { try { future.get(10, SECONDS); fail("no exception was thrown"); } catch (ExecutionException expected) { Throwable t = expected.getCause(); if (!(t instanceof AsynchronousCloseException || t instanceof ClosedChannelException)) { fail("expected AsynchronousCloseException (or in rare cases ClosedChannelException): " + "got " + t); } } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java000066400000000000000000000743731265745405500276130ustar00rootroot00000000000000/* * 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.jimfs.TestUtils.assertNotEquals; import static com.google.common.jimfs.TestUtils.buffer; import static com.google.common.jimfs.TestUtils.bytes; import static com.google.common.jimfs.TestUtils.regularFile; import static com.google.common.truth.Truth.assertThat; import static java.nio.file.StandardOpenOption.APPEND; import static java.nio.file.StandardOpenOption.READ; import static java.nio.file.StandardOpenOption.WRITE; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableSet; import com.google.common.testing.NullPointerTester; import com.google.common.util.concurrent.Runnables; import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.Uninterruptibles; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousCloseException; import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.FileLockInterruptionException; import java.nio.channels.NonReadableChannelException; import java.nio.channels.NonWritableChannelException; import java.nio.file.OpenOption; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * Most of the behavior of {@link JimfsFileChannel} is handled by the {@link RegularFile} * implementations, so the thorough tests of that are in {@link RegularFileTest}. This mostly * tests interactions with the file and channel positions. * * @author Colin Decker */ @RunWith(JUnit4.class) public class JimfsFileChannelTest { private static FileChannel channel(RegularFile file, OpenOption... options) throws IOException { return new JimfsFileChannel( file, Options.getOptionsForChannel(ImmutableSet.copyOf(options)), new FileSystemState(Runnables.doNothing())); } @Test public void testPosition() throws IOException { FileChannel channel = channel(regularFile(10), READ); assertEquals(0, channel.position()); assertSame(channel, channel.position(100)); assertEquals(100, channel.position()); } @Test public void testSize() throws IOException { RegularFile file = regularFile(10); FileChannel channel = channel(file, READ); assertEquals(10, channel.size()); file.write(10, new byte[90], 0, 90); assertEquals(100, channel.size()); } @Test public void testRead() throws IOException { RegularFile file = regularFile(20); FileChannel channel = channel(file, READ); assertEquals(0, channel.position()); ByteBuffer buf = buffer("1234567890"); ByteBuffer buf2 = buffer("123457890"); assertEquals(10, channel.read(buf)); assertEquals(10, channel.position()); buf.flip(); assertEquals(10, channel.read(new ByteBuffer[] {buf, buf2})); assertEquals(20, channel.position()); buf.flip(); buf2.flip(); file.write(20, new byte[10], 0, 10); assertEquals(10, channel.read(new ByteBuffer[] {buf, buf2}, 0, 2)); assertEquals(30, channel.position()); buf.flip(); assertEquals(10, channel.read(buf, 5)); assertEquals(30, channel.position()); buf.flip(); assertEquals(-1, channel.read(buf)); assertEquals(30, channel.position()); } @Test public void testWrite() throws IOException { RegularFile file = regularFile(0); FileChannel channel = channel(file, WRITE); assertEquals(0, channel.position()); ByteBuffer buf = buffer("1234567890"); ByteBuffer buf2 = buffer("1234567890"); assertEquals(10, channel.write(buf)); assertEquals(10, channel.position()); buf.flip(); assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2})); assertEquals(30, channel.position()); buf.flip(); buf2.flip(); assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}, 0, 2)); assertEquals(50, channel.position()); buf.flip(); assertEquals(10, channel.write(buf, 5)); assertEquals(50, channel.position()); } @Test public void testAppend() throws IOException { RegularFile file = regularFile(0); FileChannel channel = channel(file, WRITE, APPEND); assertEquals(0, channel.position()); ByteBuffer buf = buffer("1234567890"); ByteBuffer buf2 = buffer("1234567890"); assertEquals(10, channel.write(buf)); assertEquals(10, channel.position()); buf.flip(); channel.position(0); assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2})); assertEquals(30, channel.position()); buf.flip(); buf2.flip(); channel.position(0); assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}, 0, 2)); assertEquals(50, channel.position()); buf.flip(); channel.position(0); assertEquals(10, channel.write(buf, 5)); assertEquals(60, channel.position()); buf.flip(); channel.position(0); assertEquals(10, channel.transferFrom(new ByteBufferChannel(buf), 0, 10)); assertEquals(70, channel.position()); } @Test public void testTransferTo() throws IOException { RegularFile file = regularFile(10); FileChannel channel = channel(file, READ); ByteBufferChannel writeChannel = new ByteBufferChannel(buffer("1234567890")); assertEquals(10, channel.transferTo(0, 100, writeChannel)); assertEquals(0, channel.position()); } @Test public void testTransferFrom() throws IOException { RegularFile file = regularFile(0); FileChannel channel = channel(file, WRITE); ByteBufferChannel readChannel = new ByteBufferChannel(buffer("1234567890")); assertEquals(10, channel.transferFrom(readChannel, 0, 100)); assertEquals(0, channel.position()); } @Test public void testTruncate() throws IOException { RegularFile file = regularFile(10); FileChannel channel = channel(file, WRITE); channel.truncate(10); // no resize, >= size assertEquals(10, file.size()); channel.truncate(11); // no resize, > size assertEquals(10, file.size()); channel.truncate(5); // resize down to 5 assertEquals(5, file.size()); channel.position(20); channel.truncate(10); assertEquals(10, channel.position()); channel.truncate(2); assertEquals(2, channel.position()); } @Test public void testFileTimeUpdates() throws IOException { RegularFile file = regularFile(10); FileChannel channel = new JimfsFileChannel( file, ImmutableSet.of(READ, WRITE), new FileSystemState(Runnables.doNothing())); // accessed long accessTime = file.getLastAccessTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.read(ByteBuffer.allocate(10)); assertNotEquals(accessTime, file.getLastAccessTime()); accessTime = file.getLastAccessTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.read(ByteBuffer.allocate(10), 0); assertNotEquals(accessTime, file.getLastAccessTime()); accessTime = file.getLastAccessTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.read(new ByteBuffer[] {ByteBuffer.allocate(10)}); assertNotEquals(accessTime, file.getLastAccessTime()); accessTime = file.getLastAccessTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.read(new ByteBuffer[] {ByteBuffer.allocate(10)}, 0, 1); assertNotEquals(accessTime, file.getLastAccessTime()); accessTime = file.getLastAccessTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.transferTo(0, 10, new ByteBufferChannel(10)); assertNotEquals(accessTime, file.getLastAccessTime()); // modified long modifiedTime = file.getLastModifiedTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.write(ByteBuffer.allocate(10)); assertNotEquals(modifiedTime, file.getLastModifiedTime()); modifiedTime = file.getLastModifiedTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.write(ByteBuffer.allocate(10), 0); assertNotEquals(modifiedTime, file.getLastModifiedTime()); modifiedTime = file.getLastModifiedTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.write(new ByteBuffer[] {ByteBuffer.allocate(10)}); assertNotEquals(modifiedTime, file.getLastModifiedTime()); modifiedTime = file.getLastModifiedTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.write(new ByteBuffer[] {ByteBuffer.allocate(10)}, 0, 1); assertNotEquals(modifiedTime, file.getLastModifiedTime()); modifiedTime = file.getLastModifiedTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.truncate(0); assertNotEquals(modifiedTime, file.getLastModifiedTime()); modifiedTime = file.getLastModifiedTime(); Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); channel.transferFrom(new ByteBufferChannel(10), 0, 10); assertNotEquals(modifiedTime, file.getLastModifiedTime()); } @Test public void testClose() throws IOException { FileChannel channel = channel(regularFile(0), READ, WRITE); ExecutorService executor = Executors.newSingleThreadExecutor(); assertTrue(channel.isOpen()); channel.close(); assertFalse(channel.isOpen()); try { channel.position(); fail(); } catch (ClosedChannelException expected) { } try { channel.position(0); fail(); } catch (ClosedChannelException expected) { } try { channel.lock(); fail(); } catch (ClosedChannelException expected) { } try { channel.lock(0, 10, true); fail(); } catch (ClosedChannelException expected) { } try { channel.tryLock(); fail(); } catch (ClosedChannelException expected) { } try { channel.tryLock(0, 10, true); fail(); } catch (ClosedChannelException expected) { } try { channel.force(true); fail(); } catch (ClosedChannelException expected) { } try { channel.write(buffer("111")); fail(); } catch (ClosedChannelException expected) { } try { channel.write(buffer("111"), 10); fail(); } catch (ClosedChannelException expected) { } try { channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}); fail(); } catch (ClosedChannelException expected) { } try { channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2); fail(); } catch (ClosedChannelException expected) { } try { channel.transferFrom(new ByteBufferChannel(bytes("1111")), 0, 4); fail(); } catch (ClosedChannelException expected) { } try { channel.truncate(0); fail(); } catch (ClosedChannelException expected) { } try { channel.read(buffer("111")); fail(); } catch (ClosedChannelException expected) { } try { channel.read(buffer("111"), 10); fail(); } catch (ClosedChannelException expected) { } try { channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}); fail(); } catch (ClosedChannelException expected) { } try { channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2); fail(); } catch (ClosedChannelException expected) { } try { channel.transferTo(0, 10, new ByteBufferChannel(buffer("111"))); fail(); } catch (ClosedChannelException expected) { } executor.shutdown(); } @Test public void testWritesInReadOnlyMode() throws IOException { FileChannel channel = channel(regularFile(0), READ); try { channel.write(buffer("111")); fail(); } catch (NonWritableChannelException expected) { } try { channel.write(buffer("111"), 10); fail(); } catch (NonWritableChannelException expected) { } try { channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}); fail(); } catch (NonWritableChannelException expected) { } try { channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2); fail(); } catch (NonWritableChannelException expected) { } try { channel.transferFrom(new ByteBufferChannel(bytes("1111")), 0, 4); fail(); } catch (NonWritableChannelException expected) { } try { channel.truncate(0); fail(); } catch (NonWritableChannelException expected) { } try { channel.lock(0, 10, false); fail(); } catch (NonWritableChannelException expected) { } } @Test public void testReadsInWriteOnlyMode() throws IOException { FileChannel channel = channel(regularFile(0), WRITE); try { channel.read(buffer("111")); fail(); } catch (NonReadableChannelException expected) { } try { channel.read(buffer("111"), 10); fail(); } catch (NonReadableChannelException expected) { } try { channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}); fail(); } catch (NonReadableChannelException expected) { } try { channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2); fail(); } catch (NonReadableChannelException expected) { } try { channel.transferTo(0, 10, new ByteBufferChannel(buffer("111"))); fail(); } catch (NonReadableChannelException expected) { } try { channel.lock(0, 10, true); fail(); } catch (NonReadableChannelException expected) { } } @Test public void testPositionNegative() throws IOException { FileChannel channel = channel(regularFile(0), READ, WRITE); try { channel.position(-1); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testTruncateNegative() throws IOException { FileChannel channel = channel(regularFile(0), READ, WRITE); try { channel.truncate(-1); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testWriteNegative() throws IOException { FileChannel channel = channel(regularFile(0), READ, WRITE); try { channel.write(buffer("111"), -1); fail(); } catch (IllegalArgumentException expected) { } ByteBuffer[] bufs = {buffer("111"), buffer("111")}; try { channel.write(bufs, -1, 10); fail(); } catch (IndexOutOfBoundsException expected) { } try { channel.write(bufs, 0, -1); fail(); } catch (IndexOutOfBoundsException expected) { } } @Test public void testReadNegative() throws IOException { FileChannel channel = channel(regularFile(0), READ, WRITE); try { channel.read(buffer("111"), -1); fail(); } catch (IllegalArgumentException expected) { } ByteBuffer[] bufs = {buffer("111"), buffer("111")}; try { channel.read(bufs, -1, 10); fail(); } catch (IndexOutOfBoundsException expected) { } try { channel.read(bufs, 0, -1); fail(); } catch (IndexOutOfBoundsException expected) { } } @Test public void testTransferToNegative() throws IOException { FileChannel channel = channel(regularFile(0), READ, WRITE); try { channel.transferTo(-1, 0, new ByteBufferChannel(10)); fail(); } catch (IllegalArgumentException expected) { } try { channel.transferTo(0, -1, new ByteBufferChannel(10)); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testTransferFromNegative() throws IOException { FileChannel channel = channel(regularFile(0), READ, WRITE); try { channel.transferFrom(new ByteBufferChannel(10), -1, 0); fail(); } catch (IllegalArgumentException expected) { } try { channel.transferFrom(new ByteBufferChannel(10), 0, -1); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testLockNegative() throws IOException { FileChannel channel = channel(regularFile(0), READ, WRITE); try { channel.lock(-1, 10, true); fail(); } catch (IllegalArgumentException expected) { } try { channel.lock(0, -1, true); fail(); } catch (IllegalArgumentException expected) { } try { channel.tryLock(-1, 10, true); fail(); } catch (IllegalArgumentException expected) { } try { channel.tryLock(0, -1, true); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testNullPointerExceptions() throws IOException { FileChannel channel = channel(regularFile(100), READ, WRITE); NullPointerTester tester = new NullPointerTester(); tester.testAllPublicInstanceMethods(channel); } @Test public void testLock() throws IOException { FileChannel channel = channel(regularFile(10), READ, WRITE); assertNotNull(channel.lock()); assertNotNull(channel.lock(0, 10, false)); assertNotNull(channel.lock(0, 10, true)); assertNotNull(channel.tryLock()); assertNotNull(channel.tryLock(0, 10, false)); assertNotNull(channel.tryLock(0, 10, true)); FileLock lock = channel.lock(); assertTrue(lock.isValid()); lock.release(); assertFalse(lock.isValid()); } @Test public void testAsynchronousClose() throws Exception { RegularFile file = regularFile(10); final FileChannel channel = channel(file, READ, WRITE); file.writeLock().lock(); // ensure all operations on the channel will block ExecutorService executor = Executors.newCachedThreadPool(); CountDownLatch latch = new CountDownLatch(BLOCKING_OP_COUNT); List> futures = queueAllBlockingOperations(channel, executor, latch); // wait for all the threads to have started running latch.await(); // then ensure time for operations to start blocking Uninterruptibles.sleepUninterruptibly(20, MILLISECONDS); // close channel on this thread channel.close(); // the blocking operations are running on different threads, so they all get // AsynchronousCloseException for (Future future : futures) { try { future.get(); fail(); } catch (ExecutionException expected) { assertThat(expected.getCause()) .named("blocking thread exception") .isInstanceOf(AsynchronousCloseException.class); } } } @Test public void testCloseByInterrupt() throws Exception { RegularFile file = regularFile(10); final FileChannel channel = channel(file, READ, WRITE); file.writeLock().lock(); // ensure all operations on the channel will block ExecutorService executor = Executors.newCachedThreadPool(); final CountDownLatch threadStartLatch = new CountDownLatch(1); final SettableFuture interruptException = SettableFuture.create(); // This thread, being the first to run, will be blocking on the interruptible lock (the byte // file's write lock) and as such will be interrupted properly... the other threads will be // blocked on the lock that guards the position field and the specification that only one method // on the channel will be in progress at a time. That lock is not interruptible, so we must // interrupt this thread. Thread thread = new Thread( new Runnable() { @Override public void run() { threadStartLatch.countDown(); try { channel.write(ByteBuffer.allocate(20)); interruptException.set(null); } catch (Throwable e) { interruptException.set(e); } } }); thread.start(); // let the thread start running threadStartLatch.await(); // then ensure time for thread to start blocking on the write lock Uninterruptibles.sleepUninterruptibly(10, MILLISECONDS); CountDownLatch blockingStartLatch = new CountDownLatch(BLOCKING_OP_COUNT); List> futures = queueAllBlockingOperations(channel, executor, blockingStartLatch); // wait for all blocking threads to start blockingStartLatch.await(); // then ensure time for the operations to start blocking Uninterruptibles.sleepUninterruptibly(20, MILLISECONDS); // interrupting this blocking thread closes the channel and makes all the other threads // throw AsynchronousCloseException... the operation on this thread should throw // ClosedByInterruptException thread.interrupt(); // get the exception that caused the interrupted operation to terminate assertThat(interruptException.get(200, MILLISECONDS)) .named("interrupted thread exception") .isInstanceOf(ClosedByInterruptException.class); // check that each other thread got AsynchronousCloseException (since the interrupt, on a // different thread, closed the channel) for (Future future : futures) { try { future.get(); fail(); } catch (ExecutionException expected) { assertThat(expected.getCause()) .named("blocking thread exception") .isInstanceOf(AsynchronousCloseException.class); } } } private static final int BLOCKING_OP_COUNT = 10; /** * Queues blocking operations on the channel in separate threads using the given executor. * The given latch should have a count of BLOCKING_OP_COUNT to allow the caller wants to wait for * all threads to start executing. */ private List> queueAllBlockingOperations( final FileChannel channel, ExecutorService executor, final CountDownLatch startLatch) { List> futures = new ArrayList<>(); final ByteBuffer buffer = ByteBuffer.allocate(10); futures.add( executor.submit( new Callable() { @Override public Object call() throws Exception { startLatch.countDown(); channel.write(buffer); return null; } })); futures.add( executor.submit( new Callable() { @Override public Object call() throws Exception { startLatch.countDown(); channel.write(buffer, 0); return null; } })); futures.add( executor.submit( new Callable() { @Override public Object call() throws Exception { startLatch.countDown(); channel.write(new ByteBuffer[] {buffer, buffer}); return null; } })); futures.add( executor.submit( new Callable() { @Override public Object call() throws Exception { startLatch.countDown(); channel.write(new ByteBuffer[] {buffer, buffer, buffer}, 0, 2); return null; } })); futures.add( executor.submit( new Callable() { @Override public Object call() throws Exception { startLatch.countDown(); channel.read(buffer); return null; } })); futures.add( executor.submit( new Callable() { @Override public Object call() throws Exception { startLatch.countDown(); channel.read(buffer, 0); return null; } })); futures.add( executor.submit( new Callable() { @Override public Object call() throws Exception { startLatch.countDown(); channel.read(new ByteBuffer[] {buffer, buffer}); return null; } })); futures.add( executor.submit( new Callable() { @Override public Object call() throws Exception { startLatch.countDown(); channel.read(new ByteBuffer[] {buffer, buffer, buffer}, 0, 2); return null; } })); futures.add( executor.submit( new Callable() { @Override public Object call() throws Exception { startLatch.countDown(); channel.transferTo(0, 10, new ByteBufferChannel(buffer)); return null; } })); futures.add( executor.submit( new Callable() { @Override public Object call() throws Exception { startLatch.countDown(); channel.transferFrom(new ByteBufferChannel(buffer), 0, 10); return null; } })); return futures; } /** * Tests that the methods on the default FileChannel that support InterruptibleChannel behavior * also support it on JimfsFileChannel, by just interrupting the thread before calling the * method. */ @Test public void testInterruptedThreads() throws IOException { final ByteBuffer buf = ByteBuffer.allocate(10); final ByteBuffer[] bufArray = {buf}; assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.size(); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.position(); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.position(0); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.write(buf); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.write(bufArray, 0, 1); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.read(buf); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.read(bufArray, 0, 1); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.write(buf, 0); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.read(buf, 0); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.transferTo(0, 1, channel(regularFile(10), READ, WRITE)); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.transferFrom(channel(regularFile(10), READ, WRITE), 0, 1); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.force(true); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.truncate(0); } }); assertClosedByInterrupt( new FileChannelMethod() { @Override public void call(FileChannel channel) throws IOException { channel.lock(0, 1, true); } }); // tryLock() does not handle interruption // map() always throws UOE; it doesn't make sense for it to try to handle interruption } private interface FileChannelMethod { void call(FileChannel channel) throws IOException; } /** * Asserts that when the given operation is run on an interrupted thread, * {@code ClosedByInterruptException} is thrown, the channel is closed and the thread is no * longer interrupted. */ private static void assertClosedByInterrupt(FileChannelMethod method) throws IOException { FileChannel channel = channel(regularFile(10), READ, WRITE); Thread.currentThread().interrupt(); try { method.call(channel); fail( "expected the method to throw ClosedByInterruptException or " + "FileLockInterruptionException"); } catch (ClosedByInterruptException | FileLockInterruptionException expected) { assertFalse("expected the channel to be closed", channel.isOpen()); assertTrue("expected the thread to still be interrupted", Thread.interrupted()); } finally { Thread.interrupted(); // ensure the thread isn't interrupted when this method returns } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/JimfsFileSystemCloseTest.java000066400000000000000000000244641265745405500305110ustar00rootroot00000000000000/* * Copyright 2014 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 java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.READ; import static java.nio.file.StandardOpenOption.WRITE; import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; import java.nio.file.ClosedDirectoryStreamException; import java.nio.file.ClosedFileSystemException; import java.nio.file.ClosedWatchServiceException; import java.nio.file.DirectoryStream; import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.WatchService; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; /** * Tests for what happens when a file system is closed. * * @author Colin Decker */ @RunWith(JUnit4.class) public class JimfsFileSystemCloseTest { private JimfsFileSystem fs = (JimfsFileSystem) Jimfs.newFileSystem(Configuration.unix()); @Test public void testIsNotOpen() throws IOException { assertTrue(fs.isOpen()); fs.close(); assertFalse(fs.isOpen()); } @Test public void testIsNotAvailableFromProvider() throws IOException { URI uri = fs.getUri(); assertEquals(fs, FileSystems.getFileSystem(uri)); fs.close(); try { FileSystems.getFileSystem(uri); fail(); } catch (FileSystemNotFoundException expected) { } } @Test public void testOpenStreamsClosed() throws IOException { Path p = fs.getPath("/foo"); OutputStream out = Files.newOutputStream(p); InputStream in = Files.newInputStream(p); out.write(1); assertEquals(1, in.read()); fs.close(); try { out.write(1); fail(); } catch (IOException expected) { assertEquals("stream is closed", expected.getMessage()); } try { in.read(); fail(); } catch (IOException expected) { assertEquals("stream is closed", expected.getMessage()); } } @Test public void testOpenChannelsClosed() throws IOException { Path p = fs.getPath("/foo"); FileChannel fc = FileChannel.open(p, READ, WRITE, CREATE); SeekableByteChannel sbc = Files.newByteChannel(p, READ); AsynchronousFileChannel afc = AsynchronousFileChannel.open(p, READ, WRITE); assertTrue(fc.isOpen()); assertTrue(sbc.isOpen()); assertTrue(afc.isOpen()); fs.close(); assertFalse(fc.isOpen()); assertFalse(sbc.isOpen()); assertFalse(afc.isOpen()); try { fc.size(); fail(); } catch (ClosedChannelException expected) { } try { sbc.size(); fail(); } catch (ClosedChannelException expected) { } try { afc.size(); fail(); } catch (ClosedChannelException expected) { } } @Test public void testOpenDirectoryStreamsClosed() throws IOException { Path p = fs.getPath("/foo"); Files.createDirectory(p); DirectoryStream stream = Files.newDirectoryStream(p); fs.close(); try { stream.iterator(); fail(); } catch (ClosedDirectoryStreamException expected) { } } @Test public void testOpenWatchServicesClosed() throws IOException { WatchService ws1 = fs.newWatchService(); WatchService ws2 = fs.newWatchService(); assertNull(ws1.poll()); assertNull(ws2.poll()); fs.close(); try { ws1.poll(); fail(); } catch (ClosedWatchServiceException expected) { } try { ws2.poll(); fail(); } catch (ClosedWatchServiceException expected) { } } @Test public void testPathMethodsThrow() throws IOException { Path p = fs.getPath("/foo"); Files.createDirectory(p); WatchService ws = fs.newWatchService(); fs.close(); try { p.register(ws, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); fail(); } catch (ClosedWatchServiceException expected) { } try { p = p.toRealPath(); fail(); } catch (ClosedFileSystemException expected) { } // While technically (according to the FileSystem.close() spec) all methods on Path should // probably throw, we only throw for methods that access the file system itself in some way... // path manipulation methods seem totally harmless to keep working, and I don't see any need to // add the overhead of checking that the file system is open for each of those method calls. } @Test public void testOpenFileAttributeViewsThrow() throws IOException { Path p = fs.getPath("/foo"); Files.createFile(p); BasicFileAttributeView view = Files.getFileAttributeView(p, BasicFileAttributeView.class); fs.close(); try { view.readAttributes(); fail(); } catch (ClosedFileSystemException expected) { } try { view.setTimes(null, null, null); fail(); } catch (ClosedFileSystemException expected) { } } @Test public void testFileSystemMethodsThrow() throws IOException { fs.close(); try { fs.getPath("/foo"); fail(); } catch (ClosedFileSystemException expected) { } try { fs.getRootDirectories(); fail(); } catch (ClosedFileSystemException expected) { } try { fs.getFileStores(); fail(); } catch (ClosedFileSystemException expected) { } try { fs.getPathMatcher("glob:*.java"); fail(); } catch (ClosedFileSystemException expected) { } try { fs.getUserPrincipalLookupService(); fail(); } catch (ClosedFileSystemException expected) { } try { fs.newWatchService(); fail(); } catch (ClosedFileSystemException expected) { } try { fs.supportedFileAttributeViews(); fail(); } catch (ClosedFileSystemException expected) { } } @Test public void testFilesMethodsThrow() throws IOException { Path file = fs.getPath("/file"); Path dir = fs.getPath("/dir"); Path nothing = fs.getPath("/nothing"); Files.createDirectory(dir); Files.createFile(file); fs.close(); // not exhaustive, but should cover every major type of functionality accessible through Files // TODO(cgdecker): reflectively invoke all methods with default arguments? try { Files.delete(file); fail(); } catch (ClosedFileSystemException expected) { } try { Files.createDirectory(nothing); fail(); } catch (ClosedFileSystemException expected) { } try { Files.createFile(nothing); fail(); } catch (ClosedFileSystemException expected) { } try { Files.write(nothing, ImmutableList.of("hello world"), UTF_8); fail(); } catch (ClosedFileSystemException expected) { } try { Files.newInputStream(file); fail(); } catch (ClosedFileSystemException expected) { } try { Files.newOutputStream(file); fail(); } catch (ClosedFileSystemException expected) { } try { Files.newByteChannel(file); fail(); } catch (ClosedFileSystemException expected) { } try { Files.newDirectoryStream(dir); fail(); } catch (ClosedFileSystemException expected) { } try { Files.copy(file, nothing); fail(); } catch (ClosedFileSystemException expected) { } try { Files.move(file, nothing); fail(); } catch (ClosedFileSystemException expected) { } try { Files.copy(dir, nothing); fail(); } catch (ClosedFileSystemException expected) { } try { Files.move(dir, nothing); fail(); } catch (ClosedFileSystemException expected) { } try { Files.createSymbolicLink(nothing, file); fail(); } catch (ClosedFileSystemException expected) { } try { Files.createLink(nothing, file); fail(); } catch (ClosedFileSystemException expected) { } try { Files.exists(file); fail(); } catch (ClosedFileSystemException expected) { } try { Files.getAttribute(file, "size"); fail(); } catch (ClosedFileSystemException expected) { } try { Files.setAttribute(file, "lastModifiedTime", FileTime.fromMillis(0)); fail(); } catch (ClosedFileSystemException expected) { } try { Files.getFileAttributeView(file, BasicFileAttributeView.class); fail(); } catch (ClosedFileSystemException expected) { } try { Files.readAttributes(file, "basic:size,lastModifiedTime"); fail(); } catch (ClosedFileSystemException expected) { } try { Files.readAttributes(file, BasicFileAttributes.class); fail(); } catch (ClosedFileSystemException expected) { } try { Files.isDirectory(dir); fail(); } catch (ClosedFileSystemException expected) { } try { Files.readAllBytes(file); fail(); } catch (ClosedFileSystemException expected) { } try { Files.isReadable(file); fail(); } catch (ClosedFileSystemException expected) { } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/JimfsInputStreamTest.java000066400000000000000000000157161265745405500277120ustar00rootroot00000000000000/* * 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.jimfs.TestUtils.bytes; import static com.google.common.jimfs.TestUtils.regularFile; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.fail; import com.google.common.util.concurrent.Runnables; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; /** * Tests for {@link JimfsInputStream}. * * @author Colin Decker */ @RunWith(JUnit4.class) @SuppressWarnings("ResultOfMethodCallIgnored") public class JimfsInputStreamTest { @Test public void testRead_singleByte() throws IOException { JimfsInputStream in = newInputStream(2); assertThat(in.read()).isEqualTo(2); assertEmpty(in); } @Test public void testRead_wholeArray() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8); byte[] bytes = new byte[8]; assertThat(in.read(bytes)).isEqualTo(8); assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 7, 8), bytes); assertEmpty(in); } @Test public void testRead_wholeArray_arrayLarger() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8); byte[] bytes = new byte[12]; assertThat(in.read(bytes)).isEqualTo(8); assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0), bytes); assertEmpty(in); } @Test public void testRead_wholeArray_arraySmaller() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8); byte[] bytes = new byte[6]; assertThat(in.read(bytes)).isEqualTo(6); assertArrayEquals(bytes(1, 2, 3, 4, 5, 6), bytes); bytes = new byte[6]; assertThat(in.read(bytes)).isEqualTo(2); assertArrayEquals(bytes(7, 8, 0, 0, 0, 0), bytes); assertEmpty(in); } @Test public void testRead_partialArray() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8); byte[] bytes = new byte[12]; assertThat(in.read(bytes, 0, 8)).isEqualTo(8); assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0), bytes); assertEmpty(in); } @Test public void testRead_partialArray_sliceLarger() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8); byte[] bytes = new byte[12]; assertThat(in.read(bytes, 0, 10)).isEqualTo(8); assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0), bytes); assertEmpty(in); } @Test public void testRead_partialArray_sliceSmaller() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8); byte[] bytes = new byte[12]; assertThat(in.read(bytes, 0, 6)).isEqualTo(6); assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0), bytes); assertThat(in.read(bytes, 6, 6)).isEqualTo(2); assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0), bytes); assertEmpty(in); } @Test public void testRead_partialArray_invalidInput() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3, 4, 5); try { in.read(new byte[3], -1, 1); fail(); } catch (IndexOutOfBoundsException expected) { } try { in.read(new byte[3], 0, 4); fail(); } catch (IndexOutOfBoundsException expected) { } try { in.read(new byte[3], 1, 3); fail(); } catch (IndexOutOfBoundsException expected) { } } @Test public void testAvailable() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8); assertThat(in.available()).isEqualTo(8); assertThat(in.read()).isEqualTo(1); assertThat(in.available()).isEqualTo(7); assertThat(in.read(new byte[3])).isEqualTo(3); assertThat(in.available()).isEqualTo(4); assertThat(in.read(new byte[10], 1, 2)).isEqualTo(2); assertThat(in.available()).isEqualTo(2); assertThat(in.read(new byte[10])).isEqualTo(2); assertThat(in.available()).isEqualTo(0); } @Test public void testSkip() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8); assertThat(in.skip(0)).isEqualTo(0); assertThat(in.skip(-10)).isEqualTo(0); assertThat(in.skip(2)).isEqualTo(2); assertThat(in.read()).isEqualTo(3); assertThat(in.skip(3)).isEqualTo(3); assertThat(in.read()).isEqualTo(7); assertThat(in.skip(10)).isEqualTo(1); assertEmpty(in); assertThat(in.skip(10)).isEqualTo(0); assertEmpty(in); } @SuppressWarnings("GuardedByChecker") @Test public void testFullyReadInputStream_doesNotChangeStateWhenStoreChanges() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3, 4, 5); assertThat(in.read(new byte[5])).isEqualTo(5); assertEmpty(in); in.file.write(5, new byte[10], 0, 10); // append more bytes to file assertEmpty(in); } @Test public void testMark_unsupported() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3); assertThat(in.markSupported()).isFalse(); // mark does nothing in.mark(1); try { // reset throws IOException when unsupported in.reset(); fail(); } catch (IOException expected) { } } @Test public void testClosedInputStream_throwsException() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3); in.close(); try { in.read(); fail(); } catch (IOException expected) { } try { in.read(new byte[3]); fail(); } catch (IOException expected) { } try { in.read(new byte[10], 0, 2); fail(); } catch (IOException expected) { } try { in.skip(10); fail(); } catch (IOException expected) { } try { in.available(); fail(); } catch (IOException expected) { } in.close(); // does nothing } private static JimfsInputStream newInputStream(int... bytes) throws IOException { byte[] b = new byte[bytes.length]; for (int i = 0; i < bytes.length; i++) { b[i] = (byte) bytes[i]; } RegularFile file = regularFile(0); file.write(0, b, 0, b.length); return new JimfsInputStream(file, new FileSystemState(Runnables.doNothing())); } private static void assertEmpty(JimfsInputStream in) throws IOException { assertThat(in.read()).isEqualTo(-1); assertThat(in.read(new byte[3])).isEqualTo(-1); assertThat(in.read(new byte[10], 1, 5)).isEqualTo(-1); assertThat(in.available()).isEqualTo(0); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/JimfsOutputStreamTest.java000066400000000000000000000142201265745405500301000ustar00rootroot00000000000000/* * 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.jimfs.TestUtils.bytes; import static com.google.common.jimfs.TestUtils.regularFile; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.fail; import com.google.common.util.concurrent.Runnables; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; /** * Tests for {@link JimfsOutputStream}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class JimfsOutputStreamTest { @Test public void testWrite_singleByte() throws IOException { JimfsOutputStream out = newOutputStream(false); out.write(1); out.write(2); out.write(3); assertStoreContains(out, 1, 2, 3); } @Test public void testWrite_wholeArray() throws IOException { JimfsOutputStream out = newOutputStream(false); out.write(new byte[] {1, 2, 3, 4}); assertStoreContains(out, 1, 2, 3, 4); } @Test public void testWrite_partialArray() throws IOException { JimfsOutputStream out = newOutputStream(false); out.write(new byte[] {1, 2, 3, 4, 5, 6}, 1, 3); assertStoreContains(out, 2, 3, 4); } @Test public void testWrite_partialArray_invalidInput() throws IOException { JimfsOutputStream out = newOutputStream(false); try { out.write(new byte[3], -1, 1); fail(); } catch (IndexOutOfBoundsException expected) { } try { out.write(new byte[3], 0, 4); fail(); } catch (IndexOutOfBoundsException expected) { } try { out.write(new byte[3], 1, 3); fail(); } catch (IndexOutOfBoundsException expected) { } } @Test public void testWrite_singleByte_appendMode() throws IOException { JimfsOutputStream out = newOutputStream(true); addBytesToStore(out, 9, 8, 7); out.write(1); out.write(2); out.write(3); assertStoreContains(out, 9, 8, 7, 1, 2, 3); } @Test public void testWrite_wholeArray_appendMode() throws IOException { JimfsOutputStream out = newOutputStream(true); addBytesToStore(out, 9, 8, 7); out.write(new byte[] {1, 2, 3, 4}); assertStoreContains(out, 9, 8, 7, 1, 2, 3, 4); } @Test public void testWrite_partialArray_appendMode() throws IOException { JimfsOutputStream out = newOutputStream(true); addBytesToStore(out, 9, 8, 7); out.write(new byte[] {1, 2, 3, 4, 5, 6}, 1, 3); assertStoreContains(out, 9, 8, 7, 2, 3, 4); } @Test public void testWrite_singleByte_overwriting() throws IOException { JimfsOutputStream out = newOutputStream(false); addBytesToStore(out, 9, 8, 7, 6, 5, 4, 3); out.write(1); out.write(2); out.write(3); assertStoreContains(out, 1, 2, 3, 6, 5, 4, 3); } @Test public void testWrite_wholeArray_overwriting() throws IOException { JimfsOutputStream out = newOutputStream(false); addBytesToStore(out, 9, 8, 7, 6, 5, 4, 3); out.write(new byte[] {1, 2, 3, 4}); assertStoreContains(out, 1, 2, 3, 4, 5, 4, 3); } @Test public void testWrite_partialArray_overwriting() throws IOException { JimfsOutputStream out = newOutputStream(false); addBytesToStore(out, 9, 8, 7, 6, 5, 4, 3); out.write(new byte[] {1, 2, 3, 4, 5, 6}, 1, 3); assertStoreContains(out, 2, 3, 4, 6, 5, 4, 3); } @Test public void testClosedOutputStream_throwsException() throws IOException { JimfsOutputStream out = newOutputStream(false); out.close(); try { out.write(1); fail(); } catch (IOException expected) { } try { out.write(new byte[3]); fail(); } catch (IOException expected) { } try { out.write(new byte[10], 1, 3); fail(); } catch (IOException expected) { } out.close(); // does nothing } @Test public void testClosedOutputStream_doesNotThrowOnFlush() throws IOException { JimfsOutputStream out = newOutputStream(false); out.close(); out.flush(); // does nothing try (JimfsOutputStream out2 = newOutputStream(false); BufferedOutputStream bout = new BufferedOutputStream(out2); OutputStreamWriter writer = new OutputStreamWriter(bout, UTF_8)) { /* * This specific scenario is why flush() shouldn't throw when the stream is already closed. * Nesting try-with-resources like this will cause close() to be called on the * BufferedOutputStream multiple times. Each time, BufferedOutputStream will first call * out2.flush(), then call out2.close(). If out2.flush() throws when the stream is already * closed, the second flush() will throw an exception. Prior to JDK8, this exception would be * swallowed and ignored completely; in JDK8, the exception is thrown from close(). */ } } private static JimfsOutputStream newOutputStream(boolean append) { RegularFile file = regularFile(0); return new JimfsOutputStream(file, append, new FileSystemState(Runnables.doNothing())); } @SuppressWarnings("GuardedByChecker") private static void addBytesToStore(JimfsOutputStream out, int... bytes) throws IOException { RegularFile file = out.file; long pos = file.sizeWithoutLocking(); for (int b : bytes) { file.write(pos++, (byte) b); } } @SuppressWarnings("GuardedByChecker") private static void assertStoreContains(JimfsOutputStream out, int... bytes) { byte[] actualBytes = new byte[bytes.length]; out.file.read(0, actualBytes, 0, actualBytes.length); assertArrayEquals(bytes(bytes), actualBytes); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/JimfsPathTest.java000066400000000000000000000326341265745405500263310ustar00rootroot00000000000000/* * 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 org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.InvalidPathException; import java.nio.file.LinkOption; import java.nio.file.Path; /** * Tests for {@link JimfsPath}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class JimfsPathTest { private final PathService pathService = PathServiceTest.fakeUnixPathService(); @Test public void testPathParsing() { assertPathEquals("/", "/"); assertPathEquals("/foo", "/foo"); assertPathEquals("/foo", "/", "foo"); assertPathEquals("/foo/bar", "/foo/bar"); assertPathEquals("/foo/bar", "/", "foo", "bar"); assertPathEquals("/foo/bar", "/foo", "bar"); assertPathEquals("/foo/bar", "/", "foo/bar"); assertPathEquals("foo/bar/baz", "foo/bar/baz"); assertPathEquals("foo/bar/baz", "foo", "bar", "baz"); assertPathEquals("foo/bar/baz", "foo/bar", "baz"); assertPathEquals("foo/bar/baz", "foo", "bar/baz"); } @Test public void testPathParsing_withExtraSeparators() { assertPathEquals("/foo/bar", "///foo/bar"); assertPathEquals("/foo/bar", "/foo///bar//"); assertPathEquals("/foo/bar/baz", "/foo", "/bar", "baz/"); //assertPathEquals("/foo/bar/baz", "/foo\\/bar//\\\\/baz\\/"); } @Test public void testPathParsing_windowsStylePaths() throws IOException { PathService windowsPathService = PathServiceTest.fakeWindowsPathService(); assertEquals("C:\\", pathService.parsePath("C:\\").toString()); assertEquals("C:\\foo", windowsPathService.parsePath("C:\\foo").toString()); assertEquals("C:\\foo", windowsPathService.parsePath("C:\\", "foo").toString()); assertEquals("C:\\foo", windowsPathService.parsePath("C:", "\\foo").toString()); assertEquals("C:\\foo", windowsPathService.parsePath("C:", "foo").toString()); assertEquals("C:\\foo\\bar", windowsPathService.parsePath("C:", "foo/bar").toString()); } @Test public void testParsing_windowsStylePaths_invalidPaths() { PathService windowsPathService = PathServiceTest.fakeWindowsPathService(); try { // The actual windows implementation seems to allow "C:" but treat it as a *name*, not a root // despite the fact that a : is illegal except in a root... a : at any position other than // index 1 in the string will produce an exception. // Here, I choose to be more strict windowsPathService.parsePath("C:"); fail(); } catch (InvalidPathException expected) { } try { // "1:\" isn't a root because 1 isn't a letter windowsPathService.parsePath("1:\\foo"); fail(); } catch (InvalidPathException expected) { } try { // < and > are reserved characters windowsPathService.parsePath("foo"); fail(); } catch (InvalidPathException expected) { } } @Test public void testPathParsing_withAlternateSeparator() { // windows recognizes / as an alternate separator PathService windowsPathService = PathServiceTest.fakeWindowsPathService(); assertEquals( windowsPathService.parsePath("foo\\bar\\baz"), windowsPathService.parsePath("foo/bar/baz")); assertEquals( windowsPathService.parsePath("C:\\foo\\bar"), windowsPathService.parsePath("C:\\foo/bar")); assertEquals( windowsPathService.parsePath("c:\\foo\\bar\\baz"), windowsPathService.parsePath("c:", "foo/", "bar/baz")); } @Test public void testRootPath() { new PathTester(pathService, "/").root("/").test("/"); } @Test public void testRelativePath_singleName() { new PathTester(pathService, "test").names("test").test("test"); Path path = pathService.parsePath("test"); assertEquals(path, path.getFileName()); } @Test public void testRelativePath_twoNames() { PathTester tester = new PathTester(pathService, "foo/bar").names("foo", "bar"); tester.test("foo/bar"); } @Test public void testRelativePath_fourNames() { new PathTester(pathService, "foo/bar/baz/test") .names("foo", "bar", "baz", "test").test("foo/bar/baz/test"); } @Test public void testAbsolutePath_singleName() { new PathTester(pathService, "/foo") .root("/") .names("foo") .test("/foo"); } @Test public void testAbsolutePath_twoNames() { new PathTester(pathService, "/foo/bar") .root("/") .names("foo", "bar") .test("/foo/bar"); } @Test public void testAbsoluteMultiNamePath_fourNames() { new PathTester(pathService, "/foo/bar/baz/test") .root("/") .names("foo", "bar", "baz", "test") .test("/foo/bar/baz/test"); } @Test public void testResolve_fromRoot() { Path root = pathService.parsePath("/"); assertResolvedPathEquals("/foo", root, "foo"); assertResolvedPathEquals("/foo/bar", root, "foo/bar"); assertResolvedPathEquals("/foo/bar", root, "foo", "bar"); assertResolvedPathEquals("/foo/bar/baz/test", root, "foo/bar/baz/test"); assertResolvedPathEquals("/foo/bar/baz/test", root, "foo", "bar/baz", "test"); } @Test public void testResolve_fromAbsolute() { Path path = pathService.parsePath("/foo"); assertResolvedPathEquals("/foo/bar", path, "bar"); assertResolvedPathEquals("/foo/bar/baz/test", path, "bar/baz/test"); assertResolvedPathEquals("/foo/bar/baz/test", path, "bar/baz", "test"); assertResolvedPathEquals("/foo/bar/baz/test", path, "bar", "baz", "test"); } @Test public void testResolve_fromRelative() { Path path = pathService.parsePath("foo"); assertResolvedPathEquals("foo/bar", path, "bar"); assertResolvedPathEquals("foo/bar/baz/test", path, "bar/baz/test"); assertResolvedPathEquals("foo/bar/baz/test", path, "bar", "baz", "test"); assertResolvedPathEquals("foo/bar/baz/test", path, "bar/baz", "test"); } @Test public void testResolve_withThisAndParentDirNames() { Path path = pathService.parsePath("/foo"); assertResolvedPathEquals("/foo/bar/../baz", path, "bar/../baz"); assertResolvedPathEquals("/foo/bar/../baz", path, "bar", "..", "baz"); assertResolvedPathEquals("/foo/./bar/baz", path, "./bar/baz"); assertResolvedPathEquals("/foo/./bar/baz", path, ".", "bar/baz"); } @Test public void testResolve_givenAbsolutePath() { assertResolvedPathEquals("/test", pathService.parsePath("/foo"), "/test"); assertResolvedPathEquals("/test", pathService.parsePath("foo"), "/test"); } @Test public void testResolve_givenEmptyPath() { assertResolvedPathEquals("/foo", pathService.parsePath("/foo"), ""); assertResolvedPathEquals("foo", pathService.parsePath("foo"), ""); } @Test public void testResolve_againstEmptyPath() { assertResolvedPathEquals("foo/bar", pathService.emptyPath(), "foo/bar"); } @Test public void testResolveSibling_givenEmptyPath() { Path path = pathService.parsePath("foo/bar"); Path resolved = path.resolveSibling(""); assertPathEquals("foo", resolved); path = pathService.parsePath("foo"); resolved = path.resolveSibling(""); assertPathEquals("", resolved); } @Test public void testResolveSibling_againstEmptyPath() { Path path = pathService.parsePath(""); Path resolved = path.resolveSibling("foo"); assertPathEquals("foo", resolved); path = pathService.parsePath(""); resolved = path.resolveSibling(""); assertPathEquals("", resolved); } @Test public void testRelativize_bothAbsolute() { // TODO(cgdecker): When the paths have different roots, how should this work? // Should it work at all? assertRelativizedPathEquals("b/c", pathService.parsePath("/a"), "/a/b/c"); assertRelativizedPathEquals("c/d", pathService.parsePath("/a/b"), "/a/b/c/d"); } @Test public void testRelativize_bothRelative() { assertRelativizedPathEquals("b/c", pathService.parsePath("a"), "a/b/c"); assertRelativizedPathEquals("d", pathService.parsePath("a/b/c"), "a/b/c/d"); } @Test public void testRelativize_againstEmptyPath() { assertRelativizedPathEquals("foo/bar", pathService.emptyPath(), "foo/bar"); } @Test public void testRelativize_oneAbsoluteOneRelative() { try { pathService.parsePath("/foo/bar").relativize(pathService.parsePath("foo")); fail(); } catch (IllegalArgumentException expected) { } try { pathService.parsePath("foo").relativize(pathService.parsePath("/foo/bar")); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testNormalize_withParentDirName() { assertNormalizedPathEquals("/foo/baz", "/foo/bar/../baz"); assertNormalizedPathEquals("/foo/baz", "/foo", "bar", "..", "baz"); } @Test public void testNormalize_withThisDirName() { assertNormalizedPathEquals("/foo/bar/baz", "/foo/bar/./baz"); assertNormalizedPathEquals("/foo/bar/baz", "/foo", "bar", ".", "baz"); } @Test public void testNormalize_withThisAndParentDirNames() { assertNormalizedPathEquals("foo/test", "foo/./bar/../././baz/../test"); } @Test public void testNormalize_withLeadingParentDirNames() { assertNormalizedPathEquals("../../foo/baz", "../../foo/bar/../baz"); } @Test public void testNormalize_withLeadingThisAndParentDirNames() { assertNormalizedPathEquals("../../foo/baz", "./.././.././foo/bar/../baz"); } @Test public void testNormalize_withExtraParentDirNamesAtRoot() { assertNormalizedPathEquals("/", "/.."); assertNormalizedPathEquals("/", "/../../.."); assertNormalizedPathEquals("/", "/foo/../../.."); assertNormalizedPathEquals("/", "/../foo/../../bar/baz/../../../.."); } @Test public void testPathWithExtraSlashes() { assertPathEquals("/foo/bar/baz", pathService.parsePath("/foo/bar/baz/")); assertPathEquals("/foo/bar/baz", pathService.parsePath("/foo//bar///baz")); assertPathEquals("/foo/bar/baz", pathService.parsePath("///foo/bar/baz")); } @Test public void testEqualityBasedOnStringNotName() { Name a1 = Name.create("a", "a"); Name a2 = Name.create("A", "a"); Name a3 = Name.create("a", "A"); Path path1 = pathService.createFileName(a1); Path path2 = pathService.createFileName(a2); Path path3 = pathService.createFileName(a3); new EqualsTester() .addEqualityGroup(path1, path3) .addEqualityGroup(path2) .testEquals(); } @Test public void testNullPointerExceptions() throws NoSuchMethodException { NullPointerTester tester = new NullPointerTester().ignore(JimfsPath.class.getMethod("toRealPath", LinkOption[].class)); // ignore toRealPath because the pathService creates fake paths that do not have a // JimfsFileSystem instance, causing it to fail since it needs to access the file system tester.testAllPublicInstanceMethods(pathService.parsePath("/")); tester.testAllPublicInstanceMethods(pathService.parsePath("")); tester.testAllPublicInstanceMethods(pathService.parsePath("/foo")); tester.testAllPublicInstanceMethods(pathService.parsePath("/foo/bar/baz")); tester.testAllPublicInstanceMethods(pathService.parsePath("foo")); tester.testAllPublicInstanceMethods(pathService.parsePath("foo/bar")); tester.testAllPublicInstanceMethods(pathService.parsePath("foo/bar/baz")); tester.testAllPublicInstanceMethods(pathService.parsePath(".")); tester.testAllPublicInstanceMethods(pathService.parsePath("..")); } private void assertResolvedPathEquals( String expected, Path path, String firstResolvePath, String... moreResolvePaths) { Path resolved = path.resolve(firstResolvePath); for (String additionalPath : moreResolvePaths) { resolved = resolved.resolve(additionalPath); } assertPathEquals(expected, resolved); Path relative = pathService.parsePath(firstResolvePath, moreResolvePaths); resolved = path.resolve(relative); assertPathEquals(expected, resolved); // assert the invariant that p.relativize(p.resolve(q)).equals(q) when q does not have a root // p = path, q = relative, p.resolve(q) = resolved if (relative.getRoot() == null) { assertEquals(relative, path.relativize(resolved)); } } private void assertRelativizedPathEquals(String expected, Path path, String relativizePath) { Path relativized = path.relativize(pathService.parsePath(relativizePath)); assertPathEquals(expected, relativized); } private void assertNormalizedPathEquals(String expected, String first, String... more) { assertPathEquals(expected, pathService.parsePath(first, more).normalize()); } private void assertPathEquals(String expected, String first, String... more) { assertPathEquals(expected, pathService.parsePath(first, more)); } private void assertPathEquals(String expected, Path path) { assertEquals(pathService.parsePath(expected), path); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/JimfsUnixLikeFileSystemTest.java000066400000000000000000002310351265745405500311660ustar00rootroot00000000000000/* * 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.jimfs.TestUtils.bytes; import static com.google.common.jimfs.TestUtils.permutations; import static com.google.common.jimfs.TestUtils.preFilledBytes; import static com.google.common.primitives.Bytes.concat; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.LinkOption.NOFOLLOW_LINKS; import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.file.StandardOpenOption.APPEND; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.CREATE_NEW; import static java.nio.file.StandardOpenOption.DSYNC; import static java.nio.file.StandardOpenOption.READ; import static java.nio.file.StandardOpenOption.SPARSE; import static java.nio.file.StandardOpenOption.SYNC; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.WRITE; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Ordering; import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; import com.google.common.primitives.Bytes; import com.google.common.util.concurrent.Uninterruptibles; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.FileChannel; import java.nio.channels.NonReadableChannelException; import java.nio.channels.NonWritableChannelException; import java.nio.channels.SeekableByteChannel; import java.nio.file.ClosedDirectoryStreamException; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.DirectoryStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.FileSystemException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; import java.nio.file.NotLinkException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SecureDirectoryStream; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.nio.file.attribute.UserPrincipal; import java.util.Arrays; import java.util.Iterator; import java.util.Set; import java.util.regex.PatternSyntaxException; /** * Tests an in-memory file system through the public APIs in {@link Files}, etc. This also acts as * the tests for {@code FileSystemView}, as each public API method is (mostly) implemented by a * method in {@code FileSystemView}. * *

These tests uses a Unix-like file system, but most of what they test applies to any file * system configuration. * * @author Colin Decker */ @RunWith(JUnit4.class) public class JimfsUnixLikeFileSystemTest extends AbstractJimfsIntegrationTest { private static final Configuration UNIX_CONFIGURATION = Configuration.unix() .toBuilder() .setAttributeViews("basic", "owner", "posix", "unix") .setMaxSize(1024 * 1024 * 1024) // 1 GB .setMaxCacheSize(256 * 1024 * 1024) // 256 MB .build(); @Override protected FileSystem createFileSystem() { return Jimfs.newFileSystem("unix", UNIX_CONFIGURATION); } @Test public void testFileSystem() { assertThat(fs.getSeparator()).isEqualTo("/"); assertThat(fs.getRootDirectories()) .containsExactlyElementsIn(ImmutableSet.of(path("/"))) .inOrder(); assertThat(fs.isOpen()).isTrue(); assertThat(fs.isReadOnly()).isFalse(); assertThat(fs.supportedFileAttributeViews()).containsExactly("basic", "owner", "posix", "unix"); assertThat(fs.provider()).isInstanceOf(JimfsFileSystemProvider.class); } @Test public void testFileStore() throws IOException { FileStore fileStore = Iterables.getOnlyElement(fs.getFileStores()); assertThat(fileStore.name()).isEqualTo("jimfs"); assertThat(fileStore.type()).isEqualTo("jimfs"); assertThat(fileStore.isReadOnly()).isFalse(); long totalSpace = 1024 * 1024 * 1024; // 1 GB assertThat(fileStore.getTotalSpace()).isEqualTo(totalSpace); assertThat(fileStore.getUnallocatedSpace()).isEqualTo(totalSpace); assertThat(fileStore.getUsableSpace()).isEqualTo(totalSpace); Files.write(fs.getPath("/foo"), new byte[10000]); assertThat(fileStore.getTotalSpace()).isEqualTo(totalSpace); // We wrote 10000 bytes, but since the file system allocates fixed size blocks, more than 10k // bytes may have been allocated. As such, the unallocated space after the write can be at most // maxUnallocatedSpace. assertThat(fileStore.getUnallocatedSpace() <= totalSpace - 10000).isTrue(); // Usable space is at most unallocated space. (In this case, it's currently exactly unallocated // space, but that's not required.) assertThat(fileStore.getUsableSpace() <= fileStore.getUnallocatedSpace()).isTrue(); Files.delete(fs.getPath("/foo")); assertThat(fileStore.getTotalSpace()).isEqualTo(totalSpace); assertThat(fileStore.getUnallocatedSpace()).isEqualTo(totalSpace); assertThat(fileStore.getUsableSpace()).isEqualTo(totalSpace); } @Test public void testPaths() { assertThatPath("/").isAbsolute() .and().hasRootComponent("/") .and().hasNoNameComponents(); assertThatPath("foo").isRelative() .and().hasNameComponents("foo"); assertThatPath("foo/bar").isRelative() .and().hasNameComponents("foo", "bar"); assertThatPath("/foo/bar/baz").isAbsolute() .and().hasRootComponent("/") .and().hasNameComponents("foo", "bar", "baz"); } @Test public void testPaths_equalityIsCaseSensitive() { assertThatPath("foo").isNotEqualTo(path("FOO")); } @Test public void testPaths_areSortedCaseSensitive() { Path p1 = path("a"); Path p2 = path("B"); Path p3 = path("c"); Path p4 = path("D"); assertThat(Ordering.natural().immutableSortedCopy(Arrays.asList(p3, p4, p1, p2))) .isEqualTo(ImmutableList.of(p2, p4, p1, p3)); // would be p1, p2, p3, p4 if sorting were case insensitive } @Test public void testPaths_resolve() { assertThatPath(path("/").resolve("foo/bar")).isAbsolute() .and().hasRootComponent("/") .and().hasNameComponents("foo", "bar"); assertThatPath(path("foo/bar").resolveSibling("baz")).isRelative() .and().hasNameComponents("foo", "baz"); assertThatPath(path("foo/bar").resolve("/one/two")).isAbsolute() .and().hasRootComponent("/") .and().hasNameComponents("one", "two"); } @Test public void testPaths_normalize() { assertThatPath(path("foo/bar/..").normalize()).isRelative() .and().hasNameComponents("foo"); assertThatPath(path("foo/./bar/../baz/test/./../stuff").normalize()).isRelative() .and().hasNameComponents("foo", "baz", "stuff"); assertThatPath(path("../../foo/./bar").normalize()).isRelative() .and().hasNameComponents("..", "..", "foo", "bar"); assertThatPath(path("foo/../../bar").normalize()).isRelative() .and().hasNameComponents("..", "bar"); assertThatPath(path(".././..").normalize()).isRelative() .and().hasNameComponents("..", ".."); } @Test public void testPaths_relativize() { assertThatPath(path("/foo/bar").relativize(path("/foo/bar/baz"))).isRelative() .and().hasNameComponents("baz"); assertThatPath(path("/foo/bar/baz").relativize(path("/foo/bar"))).isRelative() .and().hasNameComponents(".."); assertThatPath(path("/foo/bar/baz").relativize(path("/foo/baz/bar"))).isRelative() .and().hasNameComponents("..", "..", "baz", "bar"); assertThatPath(path("foo/bar").relativize(path("foo"))).isRelative() .and().hasNameComponents(".."); assertThatPath(path("foo").relativize(path("foo/bar"))).isRelative() .and().hasNameComponents("bar"); try { Path unused = path("/foo/bar").relativize(path("bar")); fail(); } catch (IllegalArgumentException expected) { } try { Path unused = path("bar").relativize(path("/foo/bar")); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testPaths_startsWith_endsWith() { assertThat(path("/foo/bar").startsWith("/")).isTrue(); assertThat(path("/foo/bar").startsWith("/foo")).isTrue(); assertThat(path("/foo/bar").startsWith("/foo/bar")).isTrue(); assertThat(path("/foo/bar").endsWith("bar")).isTrue(); assertThat(path("/foo/bar").endsWith("foo/bar")).isTrue(); assertThat(path("/foo/bar").endsWith("/foo/bar")).isTrue(); assertThat(path("/foo/bar").endsWith("/foo")).isFalse(); assertThat(path("/foo/bar").startsWith("foo/bar")).isFalse(); } @Test public void testPaths_toAbsolutePath() { assertThatPath(path("/foo/bar").toAbsolutePath()).isAbsolute() .and().hasRootComponent("/") .and().hasNameComponents("foo", "bar") .and().isEqualTo(path("/foo/bar")); assertThatPath(path("foo/bar").toAbsolutePath()).isAbsolute() .and().hasRootComponent("/") .and().hasNameComponents("work", "foo", "bar") .and().isEqualTo(path("/work/foo/bar")); } @Test public void testPaths_toRealPath() throws IOException { Files.createDirectories(path("/foo/bar")); Files.createSymbolicLink(path("/link"), path("/")); assertThatPath(path("/link/foo/bar").toRealPath()).isEqualTo(path("/foo/bar")); assertThatPath(path("").toRealPath()).isEqualTo(path("/work")); assertThatPath(path(".").toRealPath()).isEqualTo(path("/work")); assertThatPath(path("..").toRealPath()).isEqualTo(path("/")); assertThatPath(path("../..").toRealPath()).isEqualTo(path("/")); assertThatPath(path("./.././..").toRealPath()).isEqualTo(path("/")); assertThatPath(path("./.././../.").toRealPath()).isEqualTo(path("/")); } @Test public void testPaths_toUri() { assertThat(path("/").toUri()).isEqualTo(URI.create("jimfs://unix/")); assertThat(path("/foo").toUri()).isEqualTo(URI.create("jimfs://unix/foo")); assertThat(path("/foo/bar").toUri()).isEqualTo(URI.create("jimfs://unix/foo/bar")); assertThat(path("foo").toUri()).isEqualTo(URI.create("jimfs://unix/work/foo")); assertThat(path("foo/bar").toUri()).isEqualTo(URI.create("jimfs://unix/work/foo/bar")); assertThat(path("").toUri()).isEqualTo(URI.create("jimfs://unix/work/")); assertThat(path("./../.").toUri()).isEqualTo(URI.create("jimfs://unix/work/./.././")); } @Test public void testPaths_getFromUri() { assertThatPath(Paths.get(URI.create("jimfs://unix/"))).isEqualTo(path("/")); assertThatPath(Paths.get(URI.create("jimfs://unix/foo"))).isEqualTo(path("/foo")); assertThatPath(Paths.get(URI.create("jimfs://unix/foo%20bar"))).isEqualTo(path("/foo bar")); assertThatPath(Paths.get(URI.create("jimfs://unix/foo/./bar"))).isEqualTo(path("/foo/./bar")); assertThatPath(Paths.get(URI.create("jimfs://unix/foo/bar/"))).isEqualTo(path("/foo/bar")); } @Test public void testPathMatchers_regex() { assertThatPath("bar").matches("regex:.*"); assertThatPath("bar").matches("regex:bar"); assertThatPath("bar").matches("regex:[a-z]+"); assertThatPath("/foo/bar").matches("regex:/.*"); assertThatPath("/foo/bar").matches("regex:/.*/bar"); } @Test public void testPathMatchers_glob() { assertThatPath("bar").matches("glob:bar"); assertThatPath("bar").matches("glob:*"); assertThatPath("/foo").doesNotMatch("glob:*"); assertThatPath("/foo/bar").doesNotMatch("glob:*"); assertThatPath("/foo/bar").matches("glob:**"); assertThatPath("/foo/bar").matches("glob:/**"); assertThatPath("foo/bar").doesNotMatch("glob:/**"); assertThatPath("/foo/bar/baz/stuff").matches("glob:/foo/**"); assertThatPath("/foo/bar/baz/stuff").matches("glob:/**/stuff"); assertThatPath("/foo").matches("glob:/[a-z]*"); assertThatPath("/Foo").doesNotMatch("glob:/[a-z]*"); assertThatPath("/foo/bar/baz/Stuff.java").matches("glob:**/*.java"); assertThatPath("/foo/bar/baz/Stuff.java").matches("glob:**/*.{java,class}"); assertThatPath("/foo/bar/baz/Stuff.class").matches("glob:**/*.{java,class}"); assertThatPath("/foo/bar/baz/Stuff.java").matches("glob:**/*.*"); try { fs.getPathMatcher("glob:**/*.{java,class"); fail(); } catch (PatternSyntaxException expected) { } } @Test public void testPathMatchers_invalid() { try { fs.getPathMatcher("glob"); fail(); } catch (IllegalArgumentException expected) { } try { fs.getPathMatcher("foo:foo"); fail(); } catch (UnsupportedOperationException expected) { assertThat(expected.getMessage()).contains("syntax"); } } @Test public void testNewFileSystem_hasRootAndWorkingDirectory() throws IOException { assertThatPath("/").hasChildren("work"); assertThatPath("/work").hasNoChildren(); } @Test public void testCreateDirectory_absolute() throws IOException { Files.createDirectory(path("/test")); assertThatPath("/test").exists(); assertThatPath("/").hasChildren("test", "work"); Files.createDirectory(path("/foo")); Files.createDirectory(path("/foo/bar")); assertThatPath("/foo/bar").exists(); assertThatPath("/foo").hasChildren("bar"); } @Test public void testCreateFile_absolute() throws IOException { Files.createFile(path("/test.txt")); assertThatPath("/test.txt").isRegularFile(); assertThatPath("/").hasChildren("test.txt", "work"); Files.createDirectory(path("/foo")); Files.createFile(path("/foo/test.txt")); assertThatPath("/foo/test.txt").isRegularFile(); assertThatPath("/foo").hasChildren("test.txt"); } @Test public void testCreateSymbolicLink_absolute() throws IOException { Files.createSymbolicLink(path("/link.txt"), path("test.txt")); assertThatPath("/link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("test.txt"); assertThatPath("/").hasChildren("link.txt", "work"); Files.createDirectory(path("/foo")); Files.createSymbolicLink(path("/foo/link.txt"), path("test.txt")); assertThatPath("/foo/link.txt") .noFollowLinks() .isSymbolicLink() .withTarget("test.txt"); assertThatPath("/foo").hasChildren("link.txt"); } @Test public void testCreateLink_absolute() throws IOException { Files.createFile(path("/test.txt")); Files.createLink(path("/link.txt"), path("/test.txt")); // don't assert that the link is the same file here, just that it was created // later tests check that linking works correctly assertThatPath("/link.txt", NOFOLLOW_LINKS).isRegularFile(); assertThatPath("/").hasChildren("link.txt", "test.txt", "work"); Files.createDirectory(path("/foo")); Files.createLink(path("/foo/link.txt"), path("/test.txt")); assertThatPath("/foo/link.txt", NOFOLLOW_LINKS).isRegularFile(); assertThatPath("/foo").hasChildren("link.txt"); } @Test public void testCreateDirectory_relative() throws IOException { Files.createDirectory(path("test")); assertThatPath("/work/test", NOFOLLOW_LINKS).isDirectory(); assertThatPath("test", NOFOLLOW_LINKS).isDirectory(); assertThatPath("/work").hasChildren("test"); assertThatPath("test").isSameFileAs("/work/test"); Files.createDirectory(path("foo")); Files.createDirectory(path("foo/bar")); assertThatPath("/work/foo/bar", NOFOLLOW_LINKS).isDirectory(); assertThatPath("foo/bar", NOFOLLOW_LINKS).isDirectory(); assertThatPath("/work/foo").hasChildren("bar"); assertThatPath("foo").hasChildren("bar"); assertThatPath("foo/bar").isSameFileAs("/work/foo/bar"); } @Test public void testCreateFile_relative() throws IOException { Files.createFile(path("test.txt")); assertThatPath("/work/test.txt", NOFOLLOW_LINKS).isRegularFile(); assertThatPath("test.txt", NOFOLLOW_LINKS).isRegularFile(); assertThatPath("/work").hasChildren("test.txt"); assertThatPath("test.txt").isSameFileAs("/work/test.txt"); Files.createDirectory(path("foo")); Files.createFile(path("foo/test.txt")); assertThatPath("/work/foo/test.txt", NOFOLLOW_LINKS).isRegularFile(); assertThatPath("foo/test.txt", NOFOLLOW_LINKS).isRegularFile(); assertThatPath("/work/foo").hasChildren("test.txt"); assertThatPath("foo").hasChildren("test.txt"); assertThatPath("foo/test.txt").isSameFileAs("/work/foo/test.txt"); } @Test public void testCreateSymbolicLink_relative() throws IOException { Files.createSymbolicLink(path("link.txt"), path("test.txt")); assertThatPath("/work/link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("test.txt"); assertThatPath("link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("test.txt"); assertThatPath("/work").hasChildren("link.txt"); Files.createDirectory(path("foo")); Files.createSymbolicLink(path("foo/link.txt"), path("test.txt")); assertThatPath("/work/foo/link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("test.txt"); assertThatPath("foo/link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("test.txt"); assertThatPath("/work/foo").hasChildren("link.txt"); assertThatPath("foo").hasChildren("link.txt"); } @Test public void testCreateLink_relative() throws IOException { Files.createFile(path("test.txt")); Files.createLink(path("link.txt"), path("test.txt")); // don't assert that the link is the same file here, just that it was created // later tests check that linking works correctly assertThatPath("/work/link.txt", NOFOLLOW_LINKS).isRegularFile(); assertThatPath("link.txt", NOFOLLOW_LINKS).isRegularFile(); assertThatPath("/work").hasChildren("link.txt", "test.txt"); Files.createDirectory(path("foo")); Files.createLink(path("foo/link.txt"), path("test.txt")); assertThatPath("/work/foo/link.txt", NOFOLLOW_LINKS).isRegularFile(); assertThatPath("foo/link.txt", NOFOLLOW_LINKS).isRegularFile(); assertThatPath("foo").hasChildren("link.txt"); } @Test public void testCreateFile_existing() throws IOException { Files.createFile(path("/test")); try { Files.createFile(path("/test")); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/test", expected.getMessage()); } try { Files.createDirectory(path("/test")); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/test", expected.getMessage()); } try { Files.createSymbolicLink(path("/test"), path("/foo")); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/test", expected.getMessage()); } Files.createFile(path("/foo")); try { Files.createLink(path("/test"), path("/foo")); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/test", expected.getMessage()); } } @Test public void testCreateFile_parentDoesNotExist() throws IOException { try { Files.createFile(path("/foo/test")); fail(); } catch (NoSuchFileException expected) { assertEquals("/foo/test", expected.getMessage()); } try { Files.createDirectory(path("/foo/test")); fail(); } catch (NoSuchFileException expected) { assertEquals("/foo/test", expected.getMessage()); } try { Files.createSymbolicLink(path("/foo/test"), path("/bar")); fail(); } catch (NoSuchFileException expected) { assertEquals("/foo/test", expected.getMessage()); } Files.createFile(path("/bar")); try { Files.createLink(path("/foo/test"), path("/bar")); fail(); } catch (NoSuchFileException expected) { assertEquals("/foo/test", expected.getMessage()); } } @Test public void testCreateFile_parentIsNotDirectory() throws IOException { Files.createDirectory(path("/foo")); Files.createFile(path("/foo/bar")); try { Files.createFile(path("/foo/bar/baz")); fail(); } catch (NoSuchFileException expected) { assertThat(expected.getFile()).isEqualTo("/foo/bar/baz"); } } @Test public void testCreateFile_nonDirectoryHigherInPath() throws IOException { Files.createDirectory(path("/foo")); Files.createFile(path("/foo/bar")); try { Files.createFile(path("/foo/bar/baz/stuff")); fail(); } catch (NoSuchFileException expected) { assertThat(expected.getFile()).isEqualTo("/foo/bar/baz/stuff"); } } @Test public void testCreateFile_parentSymlinkDoesNotExist() throws IOException { Files.createDirectory(path("/foo")); Files.createSymbolicLink(path("/foo/bar"), path("/foo/nope")); try { Files.createFile(path("/foo/bar/baz")); fail(); } catch (NoSuchFileException expected) { assertThat(expected.getFile()).isEqualTo("/foo/bar/baz"); } } @Test public void testCreateFile_symlinkHigherInPathDoesNotExist() throws IOException { Files.createDirectory(path("/foo")); Files.createSymbolicLink(path("/foo/bar"), path("nope")); try { Files.createFile(path("/foo/bar/baz/stuff")); fail(); } catch (NoSuchFileException expected) { assertThat(expected.getFile()).isEqualTo("/foo/bar/baz/stuff"); } } @Test public void testCreateFile_parentSymlinkDoesPointsToNonDirectory() throws IOException { Files.createDirectory(path("/foo")); Files.createFile(path("/foo/file")); Files.createSymbolicLink(path("/foo/bar"), path("/foo/file")); try { Files.createFile(path("/foo/bar/baz")); fail(); } catch (NoSuchFileException expected) { assertThat(expected.getFile()).isEqualTo("/foo/bar/baz"); } } @Test public void testCreateFile_symlinkHigherInPathPointsToNonDirectory() throws IOException { Files.createDirectory(path("/foo")); Files.createFile(path("/foo/file")); Files.createSymbolicLink(path("/foo/bar"), path("file")); try { Files.createFile(path("/foo/bar/baz/stuff")); fail(); } catch (NoSuchFileException expected) { assertThat(expected.getFile()).isEqualTo("/foo/bar/baz/stuff"); } } @Test public void testCreateFile_withInitialAttributes() throws IOException { Set permissions = PosixFilePermissions.fromString("rwxrwxrwx"); FileAttribute permissionsAttr = PosixFilePermissions.asFileAttribute(permissions); Files.createFile(path("/normal")); Files.createFile(path("/foo"), permissionsAttr); assertThatPath("/normal").attribute("posix:permissions").isNot(permissions); assertThatPath("/foo").attribute("posix:permissions").is(permissions); FileAttribute ownerAttr = new BasicFileAttribute<>( "posix:owner", fs.getUserPrincipalLookupService().lookupPrincipalByName("foo")); Files.createFile(path("/foo2"), ownerAttr, permissionsAttr); assertThatPath("/normal").attribute("owner:owner").isNot(ownerAttr.value()); assertThatPath("/foo2").attribute("owner:owner").is(ownerAttr.value()); assertThatPath("/foo2").attribute("posix:permissions").is(permissions); } @Test public void testCreateFile_withInitialAttributes_illegalInitialAttribute() throws IOException { try { Files.createFile( path("/foo"), new BasicFileAttribute<>("basic:lastModifiedTime", FileTime.fromMillis(0L))); fail(); } catch (UnsupportedOperationException expected) { } assertThatPath("/foo").doesNotExist(); try { Files.createFile(path("/foo"), new BasicFileAttribute<>("basic:noSuchAttribute", "foo")); fail(); } catch (IllegalArgumentException expected) { } assertThatPath("/foo").doesNotExist(); } @Test public void testOpenChannel_withInitialAttributes_createNewFile() throws IOException { FileAttribute> permissions = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxrwxrwx")); Files.newByteChannel(path("/foo"), ImmutableSet.of(WRITE, CREATE), permissions).close(); assertThatPath("/foo").isRegularFile() .and().attribute("posix:permissions").is(permissions.value()); } @Test public void testOpenChannel_withInitialAttributes_fileExists() throws IOException { Files.createFile(path("/foo")); FileAttribute> permissions = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxrwxrwx")); Files.newByteChannel(path("/foo"), ImmutableSet.of(WRITE, CREATE), permissions).close(); assertThatPath("/foo").isRegularFile() .and().attribute("posix:permissions").isNot(permissions.value()); } @Test public void testCreateDirectory_withInitialAttributes() throws IOException { FileAttribute> permissions = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxrwxrwx")); Files.createDirectory(path("/foo"), permissions); assertThatPath("/foo").isDirectory() .and().attribute("posix:permissions").is(permissions.value()); Files.createDirectory(path("/normal")); assertThatPath("/normal").isDirectory() .and().attribute("posix:permissions").isNot(permissions.value()); } @Test public void testCreateSymbolicLink_withInitialAttributes() throws IOException { FileAttribute> permissions = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxrwxrwx")); Files.createSymbolicLink(path("/foo"), path("bar"), permissions); assertThatPath("/foo", NOFOLLOW_LINKS).isSymbolicLink() .and().attribute("posix:permissions").is(permissions.value()); Files.createSymbolicLink(path("/normal"), path("bar")); assertThatPath("/normal", NOFOLLOW_LINKS).isSymbolicLink() .and().attribute("posix:permissions").isNot(permissions.value()); } @Test public void testCreateDirectories() throws IOException { Files.createDirectories(path("/foo/bar/baz")); assertThatPath("/foo").isDirectory(); assertThatPath("/foo/bar").isDirectory(); assertThatPath("/foo/bar/baz").isDirectory(); Files.createDirectories(path("/foo/asdf/jkl")); assertThatPath("/foo/asdf").isDirectory(); assertThatPath("/foo/asdf/jkl").isDirectory(); Files.createDirectories(path("bar/baz")); assertThatPath("bar/baz").isDirectory(); assertThatPath("/work/bar/baz").isDirectory(); } @Test public void testDirectories_newlyCreatedDirectoryHasTwoLinks() throws IOException { // one link from its parent to it; one from it to itself Files.createDirectory(path("/foo")); assertThatPath("/foo").hasLinkCount(2); } @Test public void testDirectories_creatingDirectoryAddsOneLinkToParent() throws IOException { // from the .. direntry Files.createDirectory(path("/foo")); Files.createDirectory(path("/foo/bar")); assertThatPath("/foo").hasLinkCount(3); Files.createDirectory(path("/foo/baz")); assertThatPath("/foo").hasLinkCount(4); } @Test public void testDirectories_creatingNonDirectoryDoesNotAddLinkToParent() throws IOException { Files.createDirectory(path("/foo")); Files.createFile(path("/foo/file")); Files.createSymbolicLink(path("/foo/fileSymlink"), path("file")); Files.createLink(path("/foo/link"), path("/foo/file")); Files.createSymbolicLink(path("/foo/fooSymlink"), path("/foo")); assertThatPath("/foo").hasLinkCount(2); } @Test public void testSize_forNewFile_isZero() throws IOException { Files.createFile(path("/test")); assertThatPath("/test").hasSize(0); } @Test public void testRead_forNewFile_isEmpty() throws IOException { Files.createFile(path("/test")); assertThatPath("/test").containsNoBytes(); } @Test public void testWriteFile_succeeds() throws IOException { Files.createFile(path("/test")); Files.write(path("/test"), new byte[] {0, 1, 2, 3}); } @Test public void testSize_forFileAfterWrite_isNumberOfBytesWritten() throws IOException { Files.write(path("/test"), new byte[] {0, 1, 2, 3}); assertThatPath("/test").hasSize(4); } @Test public void testRead_forFileAfterWrite_isBytesWritten() throws IOException { byte[] bytes = {0, 1, 2, 3}; Files.write(path("/test"), bytes); assertThatPath("/test").containsBytes(bytes); } @Test public void testWriteFile_withStandardOptions() throws IOException { Path test = path("/test"); byte[] bytes = {0, 1, 2, 3}; try { // CREATE and CREATE_NEW not specified Files.write(test, bytes, WRITE); fail(); } catch (NoSuchFileException expected) { assertEquals(test.toString(), expected.getMessage()); } Files.write(test, bytes, CREATE_NEW); // succeeds, file does not exist assertThatPath("/test").containsBytes(bytes); try { Files.write(test, bytes, CREATE_NEW); // CREATE_NEW requires file not exist fail(); } catch (FileAlreadyExistsException expected) { assertEquals(test.toString(), expected.getMessage()); } Files.write(test, new byte[] {4, 5}, CREATE); // succeeds, ok for file to already exist assertThatPath("/test").containsBytes(4, 5, 2, 3); // did not truncate or append, so overwrote Files.write(test, bytes, WRITE, CREATE, TRUNCATE_EXISTING); // default options assertThatPath("/test").containsBytes(bytes); Files.write(test, bytes, WRITE, APPEND); assertThatPath("/test").containsBytes(0, 1, 2, 3, 0, 1, 2, 3); Files.write(test, bytes, WRITE, CREATE, TRUNCATE_EXISTING, APPEND, SPARSE, DSYNC, SYNC); assertThatPath("/test").containsBytes(bytes); try { Files.write(test, bytes, READ, WRITE); // READ not allowed fail(); } catch (UnsupportedOperationException expected) { } } @Test public void testWriteLines_succeeds() throws IOException { Files.write(path("/test.txt"), ImmutableList.of("hello", "world"), UTF_8); } @Test public void testOpenFile_withReadAndTruncateExisting_doesNotTruncateFile() throws IOException { byte[] bytes = bytes(1, 2, 3, 4); Files.write(path("/test"), bytes); try (FileChannel channel = FileChannel.open(path("/test"), READ, TRUNCATE_EXISTING)) { // TRUNCATE_EXISTING ignored when opening for read byte[] readBytes = new byte[4]; channel.read(ByteBuffer.wrap(readBytes)); assertThat(Bytes.asList(readBytes)).isEqualTo(Bytes.asList(bytes)); } } @Test public void testRead_forFileAfterWriteLines_isLinesWritten() throws IOException { Files.write(path("/test.txt"), ImmutableList.of("hello", "world"), UTF_8); assertThatPath("/test.txt").containsLines("hello", "world"); } @Test public void testWriteLines_withStandardOptions() throws IOException { Path test = path("/test.txt"); ImmutableList lines = ImmutableList.of("hello", "world"); try { // CREATE and CREATE_NEW not specified Files.write(test, lines, UTF_8, WRITE); fail(); } catch (NoSuchFileException expected) { assertEquals(test.toString(), expected.getMessage()); } Files.write(test, lines, UTF_8, CREATE_NEW); // succeeds, file does not exist assertThatPath(test).containsLines(lines); try { Files.write(test, lines, UTF_8, CREATE_NEW); // CREATE_NEW requires file not exist fail(); } catch (FileAlreadyExistsException expected) { } // succeeds, ok for file to already exist Files.write(test, ImmutableList.of("foo"), UTF_8, CREATE); // did not truncate or append, so overwrote if (System.getProperty("line.separator").length() == 2) { // on Windows, an extra character is overwritten by the \r\n line separator assertThatPath(test).containsLines("foo", "", "world"); } else { assertThatPath(test).containsLines("foo", "o", "world"); } Files.write(test, lines, UTF_8, WRITE, CREATE, TRUNCATE_EXISTING); // default options assertThatPath(test).containsLines(lines); Files.write(test, lines, UTF_8, WRITE, APPEND); assertThatPath(test).containsLines("hello", "world", "hello", "world"); Files.write(test, lines, UTF_8, WRITE, CREATE, TRUNCATE_EXISTING, APPEND, SPARSE, DSYNC, SYNC); assertThatPath(test).containsLines(lines); try { Files.write(test, lines, UTF_8, READ, WRITE); // READ not allowed fail(); } catch (UnsupportedOperationException expected) { } } @Test public void testWrite_fileExistsButIsNotRegularFile() throws IOException { Files.createDirectory(path("/foo")); try { // non-CREATE mode Files.write(path("/foo"), preFilledBytes(10), WRITE); fail(); } catch (FileSystemException expected) { assertThat(expected.getFile()).isEqualTo("/foo"); assertThat(expected.getMessage()).contains("regular file"); } try { // CREATE mode Files.write(path("/foo"), preFilledBytes(10)); fail(); } catch (FileSystemException expected) { assertThat(expected.getFile()).isEqualTo("/foo"); assertThat(expected.getMessage()).contains("regular file"); } } @Test public void testDelete_file() throws IOException { try { Files.delete(path("/test")); fail(); } catch (NoSuchFileException expected) { assertEquals("/test", expected.getMessage()); } try { Files.delete(path("/foo/bar")); fail(); } catch (NoSuchFileException expected) { assertEquals("/foo/bar", expected.getMessage()); } assertFalse(Files.deleteIfExists(path("/test"))); assertFalse(Files.deleteIfExists(path("/foo/bar"))); Files.createFile(path("/test")); assertThatPath("/test").isRegularFile(); Files.delete(path("/test")); assertThatPath("/test").doesNotExist(); Files.createFile(path("/test")); assertTrue(Files.deleteIfExists(path("/test"))); assertThatPath("/test").doesNotExist(); } @Test public void testDelete_file_whenOpenReferencesRemain() throws IOException { // the open streams should continue to function normally despite the deletion Path foo = path("/foo"); byte[] bytes = preFilledBytes(100); Files.write(foo, bytes); InputStream in = Files.newInputStream(foo); OutputStream out = Files.newOutputStream(foo, APPEND); FileChannel channel = FileChannel.open(foo, READ, WRITE); assertThat(channel.size()).isEqualTo(100L); Files.delete(foo); assertThatPath("/foo").doesNotExist(); assertThat(channel.size()).isEqualTo(100L); ByteBuffer buf = ByteBuffer.allocate(100); while (buf.hasRemaining()) { channel.read(buf); } assertArrayEquals(bytes, buf.array()); byte[] moreBytes = {1, 2, 3, 4, 5}; out.write(moreBytes); assertThat(channel.size()).isEqualTo(105L); buf.clear(); assertThat(channel.read(buf)).isEqualTo(5); buf.flip(); byte[] b = new byte[5]; buf.get(b); assertArrayEquals(moreBytes, b); byte[] allBytes = new byte[105]; int off = 0; int read; while ((read = in.read(allBytes, off, allBytes.length - off)) != -1) { off += read; } assertArrayEquals(concat(bytes, moreBytes), allBytes); channel.close(); out.close(); in.close(); } @Test public void testDelete_directory() throws IOException { Files.createDirectories(path("/foo/bar")); assertThatPath("/foo").isDirectory(); assertThatPath("/foo/bar").isDirectory(); Files.delete(path("/foo/bar")); assertThatPath("/foo/bar").doesNotExist(); assertTrue(Files.deleteIfExists(path("/foo"))); assertThatPath("/foo").doesNotExist(); } @Test public void testDelete_pathPermutations() throws IOException { Path bar = path("/work/foo/bar"); Files.createDirectories(bar); for (Path path : permutations(bar)) { Files.createDirectories(bar); assertThatPath(path).isSameFileAs(bar); Files.delete(path); assertThatPath(bar).doesNotExist(); assertThatPath(path).doesNotExist(); } Path baz = path("/test/baz"); Files.createDirectories(baz); Path hello = baz.resolve("hello.txt"); for (Path path : permutations(hello)) { Files.createFile(hello); assertThatPath(path).isSameFileAs(hello); Files.delete(path); assertThatPath(hello).doesNotExist(); assertThatPath(path).doesNotExist(); } } @Test public void testDelete_directory_cantDeleteNonEmptyDirectory() throws IOException { Files.createDirectories(path("/foo/bar")); try { Files.delete(path("/foo")); fail(); } catch (DirectoryNotEmptyException expected) { assertThat(expected.getFile()).isEqualTo("/foo"); } try { Files.deleteIfExists(path("/foo")); fail(); } catch (DirectoryNotEmptyException expected) { assertThat(expected.getFile()).isEqualTo("/foo"); } } @Test public void testDelete_directory_canDeleteWorkingDirectoryByAbsolutePath() throws IOException { assertThatPath("/work").exists(); assertThatPath("").exists(); assertThatPath(".").exists(); Files.delete(path("/work")); assertThatPath("/work").doesNotExist(); assertThatPath("").exists(); assertThatPath(".").exists(); } @Test public void testDelete_directory_cantDeleteWorkingDirectoryByRelativePath() throws IOException { try { Files.delete(path("")); fail(); } catch (FileSystemException expected) { assertThat(expected.getFile()).isEqualTo(""); } try { Files.delete(path(".")); fail(); } catch (FileSystemException expected) { assertThat(expected.getFile()).isEqualTo("."); } try { Files.delete(path("../../work")); fail(); } catch (FileSystemException expected) { assertThat(expected.getFile()).isEqualTo("../../work"); } try { Files.delete(path("./../work/.././../work/.")); fail(); } catch (FileSystemException expected) { assertThat(expected.getFile()).isEqualTo("./../work/.././../work/."); } } @Test public void testDelete_directory_cantDeleteRoot() throws IOException { // delete working directory so that root is empty // don't want to just be testing the "can't delete when not empty" logic Files.delete(path("/work")); try { Files.delete(path("/")); fail(); } catch (IOException expected) { assertThat(expected.getMessage()).contains("root"); } Files.createDirectories(path("/foo/bar")); try { Files.delete(path("/foo/bar/../..")); fail(); } catch (IOException expected) { assertThat(expected.getMessage()).contains("root"); } try { Files.delete(path("/foo/./../foo/bar/./../bar/.././../../..")); fail(); } catch (IOException expected) { assertThat(expected.getMessage()).contains("root"); } } @Test public void testSymbolicLinks() throws IOException { Files.createSymbolicLink(path("/link.txt"), path("/file.txt")); assertThatPath("/link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("/file.txt"); assertThatPath("/link.txt").doesNotExist(); // following the link; target doesn't exist try { Files.createFile(path("/link.txt")); fail(); } catch (FileAlreadyExistsException expected) { } try { Files.readAllBytes(path("/link.txt")); fail(); } catch (NoSuchFileException expected) { } Files.createFile(path("/file.txt")); assertThatPath("/link.txt").isRegularFile(); // following the link; target does exist assertThatPath("/link.txt").containsNoBytes(); Files.createSymbolicLink(path("/foo"), path("/bar/baz")); assertThatPath("/foo", NOFOLLOW_LINKS).isSymbolicLink().withTarget("/bar/baz"); assertThatPath("/foo").doesNotExist(); // following the link; target doesn't exist Files.createDirectories(path("/bar/baz")); assertThatPath("/foo").isDirectory(); // following the link; target does exist Files.createFile(path("/bar/baz/test.txt")); assertThatPath("/foo/test.txt", NOFOLLOW_LINKS).isRegularFile(); // follow intermediate link try { Files.readSymbolicLink(path("/none")); fail(); } catch (NoSuchFileException expected) { assertEquals("/none", expected.getMessage()); } try { Files.readSymbolicLink(path("/file.txt")); fail(); } catch (NotLinkException expected) { assertEquals("/file.txt", expected.getMessage()); } } @Test public void testSymbolicLinks_symlinkCycle() throws IOException { Files.createDirectory(path("/foo")); Files.createSymbolicLink(path("/foo/bar"), path("baz")); Files.createSymbolicLink(path("/foo/baz"), path("bar")); try { Files.createFile(path("/foo/bar/file")); fail(); } catch (IOException expected) { assertThat(expected.getMessage()).contains("symbolic link"); } try { Files.write(path("/foo/bar"), preFilledBytes(10)); fail(); } catch (IOException expected) { assertThat(expected.getMessage()).contains("symbolic link"); } } @Test public void testSymbolicLinks_lookupOfAbsoluteSymlinkPathFromRelativePath() throws IOException { // relative path lookups are in the FileSystemView for the working directory // this tests that when an absolute path is encountered, the lookup handles it correctly Files.createDirectories(path("/foo/bar/baz")); Files.createFile(path("/foo/bar/baz/file")); Files.createDirectories(path("one/two/three")); Files.createSymbolicLink(path("/work/one/two/three/link"), path("/foo/bar")); assertThatPath("one/two/three/link/baz/file").isSameFileAs("/foo/bar/baz/file"); } @Test public void testLink() throws IOException { Files.createFile(path("/file.txt")); // checking link count requires "unix" attribute support, which we're using here assertThatPath("/file.txt").hasLinkCount(1); Files.createLink(path("/link.txt"), path("/file.txt")); assertThatPath("/link.txt").isSameFileAs("/file.txt"); assertThatPath("/file.txt").hasLinkCount(2); assertThatPath("/link.txt").hasLinkCount(2); assertThatPath("/file.txt").containsNoBytes(); assertThatPath("/link.txt").containsNoBytes(); byte[] bytes = {0, 1, 2, 3}; Files.write(path("/file.txt"), bytes); assertThatPath("/file.txt").containsBytes(bytes); assertThatPath("/link.txt").containsBytes(bytes); Files.write(path("/link.txt"), bytes, APPEND); assertThatPath("/file.txt").containsBytes(0, 1, 2, 3, 0, 1, 2, 3); assertThatPath("/link.txt").containsBytes(0, 1, 2, 3, 0, 1, 2, 3); Files.delete(path("/file.txt")); assertThatPath("/link.txt").hasLinkCount(1); assertThatPath("/link.txt").containsBytes(0, 1, 2, 3, 0, 1, 2, 3); } @Test public void testLink_forSymbolicLink_usesSymbolicLinkTarget() throws IOException { Files.createFile(path("/file")); Files.createSymbolicLink(path("/symlink"), path("/file")); Object key = getFileKey("/file"); Files.createLink(path("/link"), path("/symlink")); assertThatPath("/link").isRegularFile() .and().hasLinkCount(2) .and().attribute("fileKey").is(key); } @Test public void testLink_failsWhenTargetDoesNotExist() throws IOException { try { Files.createLink(path("/link"), path("/foo")); fail(); } catch (NoSuchFileException expected) { assertEquals("/foo", expected.getFile()); } Files.createSymbolicLink(path("/foo"), path("/bar")); try { Files.createLink(path("/link"), path("/foo")); fail(); } catch (NoSuchFileException expected) { assertEquals("/foo", expected.getFile()); } } @Test public void testLink_failsForNonRegularFile() throws IOException { Files.createDirectory(path("/dir")); try { Files.createLink(path("/link"), path("/dir")); fail(); } catch (FileSystemException expected) { assertEquals("/link", expected.getFile()); assertEquals("/dir", expected.getOtherFile()); } assertThatPath("/link").doesNotExist(); } @Test public void testLinks_failsWhenTargetFileAlreadyExists() throws IOException { Files.createFile(path("/file")); Files.createFile(path("/link")); try { Files.createLink(path("/link"), path("/file")); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/link", expected.getFile()); } } @Test public void testStreams() throws IOException { try (OutputStream out = Files.newOutputStream(path("/test"))) { for (int i = 0; i < 100; i++) { out.write(i); } } byte[] expected = new byte[100]; for (byte i = 0; i < 100; i++) { expected[i] = i; } try (InputStream in = Files.newInputStream(path("/test"))) { byte[] bytes = new byte[100]; ByteStreams.readFully(in, bytes); assertArrayEquals(expected, bytes); } try (Writer writer = Files.newBufferedWriter(path("/test.txt"), UTF_8)) { writer.write("hello"); } try (Reader reader = Files.newBufferedReader(path("/test.txt"), UTF_8)) { assertEquals("hello", CharStreams.toString(reader)); } try (Writer writer = Files.newBufferedWriter(path("/test.txt"), UTF_8, APPEND)) { writer.write(" world"); } try (Reader reader = Files.newBufferedReader(path("/test.txt"), UTF_8)) { assertEquals("hello world", CharStreams.toString(reader)); } } @Test public void testChannels() throws IOException { try (FileChannel channel = FileChannel.open(path("/test.txt"), CREATE_NEW, WRITE)) { ByteBuffer buf1 = UTF_8.encode("hello"); ByteBuffer buf2 = UTF_8.encode(" world"); while (buf1.hasRemaining() || buf2.hasRemaining()) { channel.write(new ByteBuffer[] {buf1, buf2}); } assertEquals(11, channel.position()); assertEquals(11, channel.size()); channel.write(UTF_8.encode("!")); assertEquals(12, channel.position()); assertEquals(12, channel.size()); } try (SeekableByteChannel channel = Files.newByteChannel(path("/test.txt"), READ)) { assertEquals(0, channel.position()); assertEquals(12, channel.size()); ByteBuffer buffer = ByteBuffer.allocate(100); while (channel.read(buffer) != -1) {} buffer.flip(); assertEquals("hello world!", UTF_8.decode(buffer).toString()); } byte[] bytes = preFilledBytes(100); Files.write(path("/test"), bytes); try (SeekableByteChannel channel = Files.newByteChannel(path("/test"), READ, WRITE)) { ByteBuffer buffer = ByteBuffer.wrap(preFilledBytes(50)); channel.position(50); channel.write(buffer); buffer.flip(); channel.write(buffer); channel.position(0); ByteBuffer readBuffer = ByteBuffer.allocate(150); while (readBuffer.hasRemaining()) { channel.read(readBuffer); } byte[] expected = Bytes.concat(preFilledBytes(50), preFilledBytes(50), preFilledBytes(50)); assertArrayEquals(expected, readBuffer.array()); } try (FileChannel channel = FileChannel.open(path("/test"), READ, WRITE)) { assertEquals(150, channel.size()); channel.truncate(10); assertEquals(10, channel.size()); ByteBuffer buffer = ByteBuffer.allocate(20); assertEquals(10, channel.read(buffer)); buffer.flip(); byte[] expected = new byte[20]; System.arraycopy(preFilledBytes(10), 0, expected, 0, 10); assertArrayEquals(expected, buffer.array()); } } @Test public void testCopy_inputStreamToFile() throws IOException { byte[] bytes = preFilledBytes(512); Files.copy(new ByteArrayInputStream(bytes), path("/test")); assertThatPath("/test").containsBytes(bytes); try { Files.copy(new ByteArrayInputStream(bytes), path("/test")); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/test", expected.getMessage()); } Files.copy(new ByteArrayInputStream(bytes), path("/test"), REPLACE_EXISTING); assertThatPath("/test").containsBytes(bytes); Files.copy(new ByteArrayInputStream(bytes), path("/foo"), REPLACE_EXISTING); assertThatPath("/foo").containsBytes(bytes); } @Test public void testCopy_fileToOutputStream() throws IOException { byte[] bytes = preFilledBytes(512); Files.write(path("/test"), bytes); ByteArrayOutputStream out = new ByteArrayOutputStream(); Files.copy(path("/test"), out); assertArrayEquals(bytes, out.toByteArray()); } @Test public void testCopy_fileToPath() throws IOException { byte[] bytes = preFilledBytes(512); Files.write(path("/foo"), bytes); assertThatPath("/bar").doesNotExist(); Files.copy(path("/foo"), path("/bar")); assertThatPath("/bar").containsBytes(bytes); byte[] moreBytes = preFilledBytes(2048); Files.write(path("/baz"), moreBytes); Files.copy(path("/baz"), path("/bar"), REPLACE_EXISTING); assertThatPath("/bar").containsBytes(moreBytes); try { Files.copy(path("/none"), path("/bar")); fail(); } catch (NoSuchFileException expected) { assertEquals("/none", expected.getMessage()); } } @Test public void testCopy_withCopyAttributes() throws IOException { Path foo = path("/foo"); Files.createFile(foo); Files.getFileAttributeView(foo, BasicFileAttributeView.class) .setTimes(FileTime.fromMillis(100), FileTime.fromMillis(1000), FileTime.fromMillis(10000)); assertThat(Files.getAttribute(foo, "lastModifiedTime")).isEqualTo(FileTime.fromMillis(100)); UserPrincipal zero = fs.getUserPrincipalLookupService().lookupPrincipalByName("zero"); Files.setAttribute(foo, "owner:owner", zero); Path bar = path("/bar"); Files.copy(foo, bar, COPY_ATTRIBUTES); BasicFileAttributes attributes = Files.readAttributes(bar, BasicFileAttributes.class); assertThat(attributes.lastModifiedTime()).isEqualTo(FileTime.fromMillis(100)); assertThat(attributes.lastAccessTime()).isEqualTo(FileTime.fromMillis(1000)); assertThat(attributes.creationTime()).isEqualTo(FileTime.fromMillis(10000)); assertThat(Files.getAttribute(bar, "owner:owner")).isEqualTo(zero); Path baz = path("/baz"); Files.copy(foo, baz); // test that attributes are not copied when COPY_ATTRIBUTES is not specified attributes = Files.readAttributes(baz, BasicFileAttributes.class); assertThat(attributes.lastModifiedTime()).isNotEqualTo(FileTime.fromMillis(100)); assertThat(attributes.lastAccessTime()).isNotEqualTo(FileTime.fromMillis(1000)); assertThat(attributes.creationTime()).isNotEqualTo(FileTime.fromMillis(10000)); assertThat(Files.getAttribute(baz, "owner:owner")).isNotEqualTo(zero); } @Test public void testCopy_doesNotSupportAtomicMove() throws IOException { try { Files.copy(path("/foo"), path("/bar"), ATOMIC_MOVE); fail(); } catch (UnsupportedOperationException expected) { } } @Test public void testCopy_directoryToPath() throws IOException { Files.createDirectory(path("/foo")); assertThatPath("/bar").doesNotExist(); Files.copy(path("/foo"), path("/bar")); assertThatPath("/bar").isDirectory(); } @Test public void testCopy_withoutReplaceExisting_failsWhenTargetExists() throws IOException { Files.createFile(path("/bar")); Files.createDirectory(path("/foo")); // dir -> file try { Files.copy(path("/foo"), path("/bar")); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/bar", expected.getMessage()); } Files.delete(path("/foo")); Files.createFile(path("/foo")); // file -> file try { Files.copy(path("/foo"), path("/bar")); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/bar", expected.getMessage()); } Files.delete(path("/bar")); Files.createDirectory(path("/bar")); // file -> dir try { Files.copy(path("/foo"), path("/bar")); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/bar", expected.getMessage()); } Files.delete(path("/foo")); Files.createDirectory(path("/foo")); // dir -> dir try { Files.copy(path("/foo"), path("/bar")); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/bar", expected.getMessage()); } } @Test public void testCopy_withReplaceExisting() throws IOException { Files.createFile(path("/bar")); Files.createDirectory(path("/test")); assertThatPath("/bar").isRegularFile(); // overwrite regular file w/ directory Files.copy(path("/test"), path("/bar"), REPLACE_EXISTING); assertThatPath("/bar").isDirectory(); byte[] bytes = {0, 1, 2, 3}; Files.write(path("/baz"), bytes); // overwrite directory w/ regular file Files.copy(path("/baz"), path("/bar"), REPLACE_EXISTING); assertThatPath("/bar").containsSameBytesAs("/baz"); } @Test public void testCopy_withReplaceExisting_cantReplaceNonEmptyDirectory() throws IOException { Files.createDirectory(path("/foo")); Files.createDirectory(path("/foo/bar")); Files.createFile(path("/foo/baz")); Files.createDirectory(path("/test")); try { Files.copy(path("/test"), path("/foo"), REPLACE_EXISTING); fail(); } catch (DirectoryNotEmptyException expected) { assertEquals("/foo", expected.getMessage()); } Files.delete(path("/test")); Files.createFile(path("/test")); try { Files.copy(path("/test"), path("/foo"), REPLACE_EXISTING); fail(); } catch (DirectoryNotEmptyException expected) { assertEquals("/foo", expected.getMessage()); } Files.delete(path("/foo/baz")); Files.delete(path("/foo/bar")); Files.copy(path("/test"), path("/foo"), REPLACE_EXISTING); assertThatPath("/foo").isRegularFile(); // replaced } @Test public void testCopy_directoryToPath_doesNotCopyDirectoryContents() throws IOException { Files.createDirectory(path("/foo")); Files.createDirectory(path("/foo/baz")); Files.createFile(path("/foo/test")); Files.copy(path("/foo"), path("/bar")); assertThatPath("/bar").hasNoChildren(); } @Test public void testCopy_symbolicLinkToPath() throws IOException { byte[] bytes = preFilledBytes(128); Files.write(path("/test"), bytes); Files.createSymbolicLink(path("/link"), path("/test")); assertThatPath("/bar").doesNotExist(); Files.copy(path("/link"), path("/bar")); assertThatPath("/bar", NOFOLLOW_LINKS).containsBytes(bytes); Files.delete(path("/bar")); Files.copy(path("/link"), path("/bar"), NOFOLLOW_LINKS); assertThatPath("/bar", NOFOLLOW_LINKS).isSymbolicLink().withTarget("/test"); assertThatPath("/bar").isRegularFile(); assertThatPath("/bar").containsBytes(bytes); Files.delete(path("/test")); assertThatPath("/bar", NOFOLLOW_LINKS).isSymbolicLink(); assertThatPath("/bar").doesNotExist(); } @Test public void testCopy_toDifferentFileSystem() throws IOException { try (FileSystem fs2 = Jimfs.newFileSystem(UNIX_CONFIGURATION)) { Path foo = fs.getPath("/foo"); byte[] bytes = {0, 1, 2, 3, 4}; Files.write(foo, bytes); Path foo2 = fs2.getPath("/foo"); Files.copy(foo, foo2); assertThatPath(foo).exists(); assertThatPath(foo2).exists() .and().containsBytes(bytes); } } @Test public void testCopy_toDifferentFileSystem_copyAttributes() throws IOException { try (FileSystem fs2 = Jimfs.newFileSystem(UNIX_CONFIGURATION)) { Path foo = fs.getPath("/foo"); byte[] bytes = {0, 1, 2, 3, 4}; Files.write(foo, bytes); Files.getFileAttributeView(foo, BasicFileAttributeView.class) .setTimes(FileTime.fromMillis(0), FileTime.fromMillis(1), FileTime.fromMillis(2)); UserPrincipal owner = fs.getUserPrincipalLookupService().lookupPrincipalByName("foobar"); Files.setOwner(foo, owner); assertThatPath(foo).attribute("owner:owner").is(owner); Path foo2 = fs2.getPath("/foo"); Files.copy(foo, foo2, COPY_ATTRIBUTES); assertThatPath(foo).exists(); // when copying with COPY_ATTRIBUTES to a different FileSystem, only basic attributes (that // is, file times) can actually be copied assertThatPath(foo2).exists() .and().attribute("lastModifiedTime").is(FileTime.fromMillis(0)) .and().attribute("lastAccessTime").is(FileTime.fromMillis(1)) .and().attribute("creationTime").is(FileTime.fromMillis(2)) .and().attribute("owner:owner").isNot(owner) .and().attribute("owner:owner") .isNot(fs2.getUserPrincipalLookupService().lookupPrincipalByName("foobar")) .and().containsBytes(bytes); // do this last; it updates the access time } } @Test public void testMove() throws IOException { byte[] bytes = preFilledBytes(100); Files.write(path("/foo"), bytes); Object fooKey = getFileKey("/foo"); Files.move(path("/foo"), path("/bar")); assertThatPath("/foo").doesNotExist() .andThat("/bar").containsBytes(bytes) .and().attribute("fileKey").is(fooKey); Files.createDirectory(path("/foo")); Files.move(path("/bar"), path("/foo/bar")); assertThatPath("/bar").doesNotExist() .andThat("/foo/bar").isRegularFile(); Files.move(path("/foo"), path("/baz")); assertThatPath("/foo").doesNotExist() .andThat("/baz").isDirectory() .andThat("/baz/bar").isRegularFile(); } @Test public void testMove_movesSymbolicLinkNotTarget() throws IOException { byte[] bytes = preFilledBytes(100); Files.write(path("/foo.txt"), bytes); Files.createSymbolicLink(path("/link"), path("foo.txt")); Files.move(path("/link"), path("/link.txt")); assertThatPath("/foo.txt").noFollowLinks().isRegularFile() .and().containsBytes(bytes); assertThatPath(path("/link")).doesNotExist(); assertThatPath(path("/link.txt")).noFollowLinks().isSymbolicLink(); assertThatPath(path("/link.txt")).isRegularFile() .and().containsBytes(bytes); } @Test public void testMove_cannotMoveDirIntoOwnSubtree() throws IOException { Files.createDirectories(path("/foo")); try { Files.move(path("/foo"), path("/foo/bar")); fail(); } catch (IOException expected) { assertThat(expected.getMessage()).contains("sub"); } Files.createDirectories(path("/foo/bar/baz/stuff")); Files.createDirectories(path("/hello/world")); Files.createSymbolicLink(path("/hello/world/link"), path("../../foo/bar/baz")); try { Files.move(path("/foo/bar"), path("/hello/world/link/bar")); fail(); } catch (IOException expected) { assertThat(expected.getMessage()).contains("sub"); } } @Test public void testMove_withoutReplaceExisting_failsWhenTargetExists() throws IOException { byte[] bytes = preFilledBytes(50); Files.write(path("/test"), bytes); Object testKey = getFileKey("/test"); Files.createFile(path("/bar")); try { Files.move(path("/test"), path("/bar"), ATOMIC_MOVE); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/bar", expected.getMessage()); } assertThatPath("/test").containsBytes(bytes) .and().attribute("fileKey").is(testKey); Files.delete(path("/bar")); Files.createDirectory(path("/bar")); try { Files.move(path("/test"), path("/bar"), ATOMIC_MOVE); fail(); } catch (FileAlreadyExistsException expected) { assertEquals("/bar", expected.getMessage()); } assertThatPath("/test").containsBytes(bytes) .and().attribute("fileKey").is(testKey); } @Test public void testMove_toDifferentFileSystem() throws IOException { try (FileSystem fs2 = Jimfs.newFileSystem(Configuration.unix())) { Path foo = fs.getPath("/foo"); byte[] bytes = {0, 1, 2, 3, 4}; Files.write(foo, bytes); Files.getFileAttributeView(foo, BasicFileAttributeView.class) .setTimes(FileTime.fromMillis(0), FileTime.fromMillis(1), FileTime.fromMillis(2)); Path foo2 = fs2.getPath("/foo"); Files.move(foo, foo2); assertThatPath(foo).doesNotExist(); assertThatPath(foo2).exists() .and().attribute("lastModifiedTime").is(FileTime.fromMillis(0)) .and().attribute("lastAccessTime").is(FileTime.fromMillis(1)) .and().attribute("creationTime").is(FileTime.fromMillis(2)) .and().containsBytes(bytes); // do this last; it updates the access time } } @Test public void testIsSameFile() throws IOException { Files.createDirectory(path("/foo")); Files.createSymbolicLink(path("/bar"), path("/foo")); Files.createFile(path("/bar/test")); assertThatPath("/foo").isSameFileAs("/foo"); assertThatPath("/bar").isSameFileAs("/bar"); assertThatPath("/foo/test").isSameFileAs("/foo/test"); assertThatPath("/bar/test").isSameFileAs("/bar/test"); assertThatPath("/foo").isNotSameFileAs("test"); assertThatPath("/bar").isNotSameFileAs("/test"); assertThatPath("/foo").isSameFileAs("/bar"); assertThatPath("/foo/test").isSameFileAs("/bar/test"); Files.createSymbolicLink(path("/baz"), path("bar")); // relative path assertThatPath("/baz").isSameFileAs("/foo"); assertThatPath("/baz/test").isSameFileAs("/foo/test"); } @Test public void testIsSameFile_forPathFromDifferentFileSystemProvider() throws IOException { Path defaultFileSystemRoot = FileSystems.getDefault() .getRootDirectories() .iterator() .next(); assertThat(Files.isSameFile(path("/"), defaultFileSystemRoot)).isFalse(); } @Test public void testPathLookups() throws IOException { assertThatPath("/").isSameFileAs("/"); assertThatPath("/..").isSameFileAs("/"); assertThatPath("/../../..").isSameFileAs("/"); assertThatPath("../../../..").isSameFileAs("/"); assertThatPath("").isSameFileAs("/work"); Files.createDirectories(path("/foo/bar/baz")); Files.createSymbolicLink(path("/foo/bar/link1"), path("../link2")); Files.createSymbolicLink(path("/foo/link2"), path("/")); assertThatPath("/foo/bar/link1/foo/bar/link1/foo").isSameFileAs("/foo"); } @Test public void testSecureDirectoryStream() throws IOException { Files.createDirectories(path("/foo/bar")); Files.createFile(path("/foo/a")); Files.createFile(path("/foo/b")); Files.createSymbolicLink(path("/foo/barLink"), path("bar")); try (DirectoryStream stream = Files.newDirectoryStream(path("/foo"))) { if (!(stream instanceof SecureDirectoryStream)) { fail("should be a secure directory stream"); } SecureDirectoryStream secureStream = (SecureDirectoryStream) stream; assertThat(ImmutableList.copyOf(secureStream)) .isEqualTo( ImmutableList.of( path("/foo/a"), path("/foo/b"), path("/foo/bar"), path("/foo/barLink"))); secureStream.deleteFile(path("b")); assertThatPath("/foo/b").doesNotExist(); secureStream.newByteChannel(path("b"), ImmutableSet.of(WRITE, CREATE_NEW)).close(); assertThatPath("/foo/b").isRegularFile(); assertThatPath("/foo").hasChildren("a", "b", "bar", "barLink"); Files.createDirectory(path("/baz")); Files.move(path("/foo"), path("/baz/stuff")); assertThatPath(path("/foo")).doesNotExist(); assertThatPath("/baz/stuff").hasChildren("a", "b", "bar", "barLink"); secureStream.deleteFile(path("b")); assertThatPath("/baz/stuff/b").doesNotExist(); assertThatPath("/baz/stuff").hasChildren("a", "bar", "barLink"); assertThat( secureStream .getFileAttributeView(BasicFileAttributeView.class) .readAttributes() .isDirectory()) .isTrue(); assertThat( secureStream .getFileAttributeView(path("a"), BasicFileAttributeView.class) .readAttributes() .isRegularFile()) .isTrue(); try { secureStream.deleteFile(path("bar")); fail(); } catch (FileSystemException expected) { assertThat(expected.getFile()).isEqualTo("bar"); } try { secureStream.deleteDirectory(path("a")); fail(); } catch (FileSystemException expected) { assertThat(expected.getFile()).isEqualTo("a"); } try (SecureDirectoryStream barStream = secureStream.newDirectoryStream(path("bar"))) { barStream.newByteChannel(path("stuff"), ImmutableSet.of(WRITE, CREATE_NEW)).close(); assertThat( barStream .getFileAttributeView(path("stuff"), BasicFileAttributeView.class) .readAttributes() .isRegularFile()) .isTrue(); assertThat( secureStream .getFileAttributeView(path("bar/stuff"), BasicFileAttributeView.class) .readAttributes() .isRegularFile()) .isTrue(); } try (SecureDirectoryStream barLinkStream = secureStream.newDirectoryStream(path("barLink"))) { assertThat( barLinkStream .getFileAttributeView(path("stuff"), BasicFileAttributeView.class) .readAttributes() .isRegularFile()) .isTrue(); assertThat( barLinkStream .getFileAttributeView(path(".."), BasicFileAttributeView.class) .readAttributes() .isDirectory()) .isTrue(); } try { secureStream.newDirectoryStream(path("barLink"), NOFOLLOW_LINKS); fail(); } catch (NotDirectoryException expected) { assertThat(expected.getFile()).isEqualTo("barLink"); } try (SecureDirectoryStream barStream = secureStream.newDirectoryStream(path("bar"))) { secureStream.move(path("a"), barStream, path("moved")); assertThatPath(path("/baz/stuff/a")).doesNotExist(); assertThatPath(path("/baz/stuff/bar/moved")).isRegularFile(); assertThat( barStream .getFileAttributeView(path("moved"), BasicFileAttributeView.class) .readAttributes() .isRegularFile()) .isTrue(); } } } @Test public void testSecureDirectoryStreamBasedOnRelativePath() throws IOException { Files.createDirectories(path("foo")); Files.createFile(path("foo/a")); Files.createFile(path("foo/b")); Files.createDirectory(path("foo/c")); Files.createFile(path("foo/c/d")); Files.createFile(path("foo/c/e")); try (DirectoryStream stream = Files.newDirectoryStream(path("foo"))) { SecureDirectoryStream secureStream = (SecureDirectoryStream) stream; assertThat(ImmutableList.copyOf(secureStream)) .containsExactly(path("foo/a"), path("foo/b"), path("foo/c")); try (DirectoryStream stream2 = secureStream.newDirectoryStream(path("c"))) { assertThat(ImmutableList.copyOf(stream2)).containsExactly(path("foo/c/d"), path("foo/c/e")); } } } @Test public void testClosedSecureDirectoryStream() throws IOException { Files.createDirectory(path("/foo")); SecureDirectoryStream stream = (SecureDirectoryStream) Files.newDirectoryStream(path("/foo")); stream.close(); try { stream.iterator(); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { stream.deleteDirectory(fs.getPath("a")); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { stream.deleteFile(fs.getPath("a")); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { stream.newByteChannel(fs.getPath("a"), ImmutableSet.of(CREATE, WRITE)); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { stream.newDirectoryStream(fs.getPath("a")); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { stream.move(fs.getPath("a"), stream, fs.getPath("b")); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { stream.getFileAttributeView(BasicFileAttributeView.class); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { stream.getFileAttributeView(fs.getPath("a"), BasicFileAttributeView.class); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } } @Test public void testClosedSecureDirectoryStreamAttributeViewAndIterator() throws IOException { Files.createDirectory(path("/foo")); Files.createDirectory(path("/foo/bar")); SecureDirectoryStream stream = (SecureDirectoryStream) Files.newDirectoryStream(path("/foo")); Iterator iter = stream.iterator(); BasicFileAttributeView view1 = stream.getFileAttributeView(BasicFileAttributeView.class); BasicFileAttributeView view2 = stream.getFileAttributeView(path("bar"), BasicFileAttributeView.class); try { stream.iterator(); fail("expected IllegalStateException"); } catch (IllegalStateException expected) { } stream.close(); try { iter.next(); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { view1.readAttributes(); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { view2.readAttributes(); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { view1.setTimes(null, null, null); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } try { view2.setTimes(null, null, null); fail("expected ClosedDirectoryStreamException"); } catch (ClosedDirectoryStreamException expected) { } } @Test public void testDirectoryAccessAndModifiedTimeUpdates() throws IOException { Files.createDirectories(path("/foo/bar")); FileTimeTester tester = new FileTimeTester(path("/foo/bar")); tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeDidNotChange(); // TODO(cgdecker): Use a Clock for file times so I can test this reliably without sleeping Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); Files.createFile(path("/foo/bar/baz.txt")); tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeChanged(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); // access time is updated by reading the full contents of the directory // not just by doing a lookup in it try (DirectoryStream stream = Files.newDirectoryStream(path("/foo/bar"))) { // iterate the stream, forcing the directory to actually be read Iterators.advance(stream.iterator(), Integer.MAX_VALUE); } tester.assertAccessTimeChanged(); tester.assertModifiedTimeDidNotChange(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); Files.move(path("/foo/bar/baz.txt"), path("/foo/bar/baz2.txt")); tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeChanged(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); Files.delete(path("/foo/bar/baz2.txt")); tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeChanged(); } @Test public void testRegularFileAccessAndModifiedTimeUpdates() throws IOException { Path foo = path("foo"); Files.createFile(foo); FileTimeTester tester = new FileTimeTester(foo); tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeDidNotChange(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); try (FileChannel channel = FileChannel.open(foo, READ)) { // opening READ channel does not change times tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeDidNotChange(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); channel.read(ByteBuffer.allocate(100)); // read call on channel does tester.assertAccessTimeChanged(); tester.assertModifiedTimeDidNotChange(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); channel.read(ByteBuffer.allocate(100)); tester.assertAccessTimeChanged(); tester.assertModifiedTimeDidNotChange(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); try { channel.write(ByteBuffer.wrap(new byte[] {0, 1, 2, 3})); } catch (NonWritableChannelException ignore) { } // failed write on non-readable channel does not change times tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeDidNotChange(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); } // closing channel does not change times tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeDidNotChange(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); try (FileChannel channel = FileChannel.open(foo, WRITE)) { // opening WRITE channel does not change times tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeDidNotChange(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); channel.write(ByteBuffer.wrap(new byte[] {0, 1, 2, 3})); // write call on channel does tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeChanged(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); channel.write(ByteBuffer.wrap(new byte[] {4, 5, 6, 7})); tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeChanged(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); try { channel.read(ByteBuffer.allocate(100)); } catch (NonReadableChannelException ignore) { } // failed read on non-readable channel does not change times tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeDidNotChange(); Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS); } // closing channel does not change times tester.assertAccessTimeDidNotChange(); tester.assertModifiedTimeDidNotChange(); } @Test public void testUnsupportedFeatures() throws IOException { FileSystem fileSystem = Jimfs.newFileSystem( Configuration.unix() .toBuilder() .setSupportedFeatures() // none .build()); Path foo = fileSystem.getPath("foo"); Path bar = foo.resolveSibling("bar"); try { Files.createLink(foo, bar); fail(); } catch (UnsupportedOperationException expected) { } try { Files.createSymbolicLink(foo, bar); fail(); } catch (UnsupportedOperationException expected) { } try { Files.readSymbolicLink(foo); fail(); } catch (UnsupportedOperationException expected) { } try { FileChannel.open(foo); fail(); } catch (UnsupportedOperationException expected) { } try { AsynchronousFileChannel.open(foo); fail(); } catch (UnsupportedOperationException expected) { } Files.createDirectory(foo); Files.createFile(bar); try (DirectoryStream stream = Files.newDirectoryStream(foo)) { assertThat(stream).isNotInstanceOf(SecureDirectoryStream.class); } try (SeekableByteChannel channel = Files.newByteChannel(bar)) { assertThat(channel).isNotInstanceOf(FileChannel.class); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/JimfsWindowsLikeFileSystemTest.java000066400000000000000000000371071265745405500317010ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Ordering; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.regex.PatternSyntaxException; /** * Tests a Windows-like file system through the public methods in {@link Files}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class JimfsWindowsLikeFileSystemTest extends AbstractJimfsIntegrationTest { @Override protected FileSystem createFileSystem() { return Jimfs.newFileSystem( "win", Configuration.windows() .toBuilder() .setRoots("C:\\", "E:\\") .setAttributeViews("basic", "owner", "dos", "acl", "user") .build()); } @Test public void testFileSystem() { assertThat(fs.getSeparator()).isEqualTo("\\"); assertThat(fs.getRootDirectories()) .containsExactlyElementsIn(ImmutableSet.of(path("C:\\"), path("E:\\"))) .inOrder(); assertThat(fs.isOpen()).isTrue(); assertThat(fs.isReadOnly()).isFalse(); assertThat(fs.supportedFileAttributeViews()) .containsExactly("basic", "owner", "dos", "acl", "user"); assertThat(fs.provider()).isInstanceOf(JimfsFileSystemProvider.class); } @Test public void testPaths() { assertThatPath("C:\\").isAbsolute() .and().hasRootComponent("C:\\") .and().hasNoNameComponents(); assertThatPath("foo").isRelative() .and().hasNameComponents("foo"); assertThatPath("foo\\bar").isRelative() .and().hasNameComponents("foo", "bar"); assertThatPath("C:\\foo\\bar\\baz").isAbsolute() .and().hasRootComponent("C:\\") .and().hasNameComponents("foo", "bar", "baz"); } @Test public void testPaths_equalityIsCaseInsensitive() { assertThatPath("C:\\").isEqualTo(path("c:\\")); assertThatPath("foo").isEqualTo(path("FOO")); } @Test public void testPaths_areSortedCaseInsensitive() { Path p1 = path("a"); Path p2 = path("B"); Path p3 = path("c"); Path p4 = path("D"); assertThat(Ordering.natural().immutableSortedCopy(Arrays.asList(p3, p4, p1, p2))) .isEqualTo(ImmutableList.of(p1, p2, p3, p4)); // would be p2, p4, p1, p3 if sorting were case sensitive } @Test public void testPaths_withSlash() { assertThatPath("foo/bar").isRelative() .and().hasNameComponents("foo", "bar") .and().isEqualTo(path("foo\\bar")); assertThatPath("C:/foo/bar/baz").isAbsolute() .and().hasRootComponent("C:\\") .and().hasNameComponents("foo", "bar", "baz") .and().isEqualTo(path("C:\\foo\\bar\\baz")); assertThatPath("C:/foo\\bar/baz").isAbsolute() .and().hasRootComponent("C:\\") .and().hasNameComponents("foo", "bar", "baz") .and().isEqualTo(path("C:\\foo\\bar\\baz")); } @Test public void testPaths_resolve() { assertThatPath(path("C:\\").resolve("foo\\bar")).isAbsolute() .and().hasRootComponent("C:\\") .and().hasNameComponents("foo", "bar"); assertThatPath(path("foo\\bar").resolveSibling("baz")).isRelative() .and().hasNameComponents("foo", "baz"); assertThatPath(path("foo\\bar").resolve("C:\\one\\two")).isAbsolute() .and().hasRootComponent("C:\\") .and().hasNameComponents("one", "two"); } @Test public void testPaths_normalize() { assertThatPath(path("foo\\bar\\..").normalize()).isRelative() .and().hasNameComponents("foo"); assertThatPath(path("foo\\.\\bar\\..\\baz\\test\\.\\..\\stuff").normalize()).isRelative() .and().hasNameComponents("foo", "baz", "stuff"); assertThatPath(path("..\\..\\foo\\.\\bar").normalize()).isRelative() .and().hasNameComponents("..", "..", "foo", "bar"); assertThatPath(path("foo\\..\\..\\bar").normalize()).isRelative() .and().hasNameComponents("..", "bar"); assertThatPath(path("..\\.\\..").normalize()).isRelative() .and().hasNameComponents("..", ".."); } @Test public void testPaths_relativize() { assertThatPath(path("C:\\foo\\bar").relativize(path("C:\\foo\\bar\\baz"))).isRelative() .and().hasNameComponents("baz"); assertThatPath(path("C:\\foo\\bar\\baz").relativize(path("C:\\foo\\bar"))).isRelative() .and().hasNameComponents(".."); assertThatPath(path("C:\\foo\\bar\\baz").relativize(path("C:\\foo\\baz\\bar"))).isRelative() .and().hasNameComponents("..", "..", "baz", "bar"); assertThatPath(path("foo\\bar").relativize(path("foo"))).isRelative() .and().hasNameComponents(".."); assertThatPath(path("foo").relativize(path("foo\\bar"))).isRelative() .and().hasNameComponents("bar"); try { Path unused = path("C:\\foo\\bar").relativize(path("bar")); fail(); } catch (IllegalArgumentException expected) { } try { Path unused = path("bar").relativize(path("C:\\foo\\bar")); fail(); } catch (IllegalArgumentException expected) { } } @Test public void testPaths_startsWith_endsWith() { assertThat(path("C:\\foo\\bar").startsWith("C:\\")).isTrue(); assertThat(path("C:\\foo\\bar").startsWith("C:\\foo")).isTrue(); assertThat(path("C:\\foo\\bar").startsWith("C:\\foo\\bar")).isTrue(); assertThat(path("C:\\foo\\bar").endsWith("bar")).isTrue(); assertThat(path("C:\\foo\\bar").endsWith("foo\\bar")).isTrue(); assertThat(path("C:\\foo\\bar").endsWith("C:\\foo\\bar")).isTrue(); assertThat(path("C:\\foo\\bar").endsWith("C:\\foo")).isFalse(); assertThat(path("C:\\foo\\bar").startsWith("foo\\bar")).isFalse(); } @Test public void testPaths_toAbsolutePath() { assertThatPath(path("C:\\foo\\bar").toAbsolutePath()).isAbsolute() .and().hasRootComponent("C:\\") .and().hasNameComponents("foo", "bar") .and().isEqualTo(path("C:\\foo\\bar")); assertThatPath(path("foo\\bar").toAbsolutePath()).isAbsolute() .and().hasRootComponent("C:\\") .and().hasNameComponents("work", "foo", "bar") .and().isEqualTo(path("C:\\work\\foo\\bar")); } @Test public void testPaths_toRealPath() throws IOException { Files.createDirectories(path("C:\\foo\\bar")); Files.createSymbolicLink(path("C:\\link"), path("C:\\")); assertThatPath(path("C:\\link\\foo\\bar").toRealPath()).isEqualTo(path("C:\\foo\\bar")); assertThatPath(path("").toRealPath()).isEqualTo(path("C:\\work")); assertThatPath(path(".").toRealPath()).isEqualTo(path("C:\\work")); assertThatPath(path("..").toRealPath()).isEqualTo(path("C:\\")); assertThatPath(path("..\\..").toRealPath()).isEqualTo(path("C:\\")); assertThatPath(path(".\\..\\.\\..").toRealPath()).isEqualTo(path("C:\\")); assertThatPath(path(".\\..\\.\\..\\.").toRealPath()).isEqualTo(path("C:\\")); } @Test public void testPaths_toUri() { assertThat(fs.getPath("C:\\").toUri()).isEqualTo(URI.create("jimfs://win/C:/")); assertThat(fs.getPath("C:\\foo").toUri()).isEqualTo(URI.create("jimfs://win/C:/foo")); assertThat(fs.getPath("C:\\foo\\bar").toUri()).isEqualTo(URI.create("jimfs://win/C:/foo/bar")); assertThat(fs.getPath("foo").toUri()).isEqualTo(URI.create("jimfs://win/C:/work/foo")); assertThat(fs.getPath("foo\\bar").toUri()).isEqualTo(URI.create("jimfs://win/C:/work/foo/bar")); assertThat(fs.getPath("").toUri()).isEqualTo(URI.create("jimfs://win/C:/work/")); assertThat(fs.getPath(".\\..\\.").toUri()).isEqualTo(URI.create("jimfs://win/C:/work/./.././")); } @Test public void testPaths_toUri_unc() { assertThat(fs.getPath("\\\\host\\share\\").toUri()) .isEqualTo(URI.create("jimfs://win//host/share/")); assertThat(fs.getPath("\\\\host\\share\\foo").toUri()) .isEqualTo(URI.create("jimfs://win//host/share/foo")); assertThat(fs.getPath("\\\\host\\share\\foo\\bar").toUri()) .isEqualTo(URI.create("jimfs://win//host/share/foo/bar")); } @Test public void testPaths_getFromUri() { assertThatPath(Paths.get(URI.create("jimfs://win/C:/"))).isEqualTo(fs.getPath("C:\\")); assertThatPath(Paths.get(URI.create("jimfs://win/C:/foo"))).isEqualTo(fs.getPath("C:\\foo")); assertThatPath(Paths.get(URI.create("jimfs://win/C:/foo%20bar"))) .isEqualTo(fs.getPath("C:\\foo bar")); assertThatPath(Paths.get(URI.create("jimfs://win/C:/foo/./bar"))) .isEqualTo(fs.getPath("C:\\foo\\.\\bar")); assertThatPath(Paths.get(URI.create("jimfs://win/C:/foo/bar/"))) .isEqualTo(fs.getPath("C:\\foo\\bar")); } @Test public void testPaths_getFromUri_unc() { assertThatPath(Paths.get(URI.create("jimfs://win//host/share/"))) .isEqualTo(fs.getPath("\\\\host\\share\\")); assertThatPath(Paths.get(URI.create("jimfs://win//host/share/foo"))) .isEqualTo(fs.getPath("\\\\host\\share\\foo")); assertThatPath(Paths.get(URI.create("jimfs://win//host/share/foo%20bar"))) .isEqualTo(fs.getPath("\\\\host\\share\\foo bar")); assertThatPath(Paths.get(URI.create("jimfs://win//host/share/foo/./bar"))) .isEqualTo(fs.getPath("\\\\host\\share\\foo\\.\\bar")); assertThatPath(Paths.get(URI.create("jimfs://win//host/share/foo/bar/"))) .isEqualTo(fs.getPath("\\\\host\\share\\foo\\bar")); } @Test public void testPathMatchers_glob() { assertThatPath("bar").matches("glob:bar"); assertThatPath("bar").matches("glob:*"); assertThatPath("C:\\foo").doesNotMatch("glob:*"); assertThatPath("C:\\foo\\bar").doesNotMatch("glob:*"); assertThatPath("C:\\foo\\bar").matches("glob:**"); assertThatPath("C:\\foo\\bar").matches("glob:C:\\\\**"); assertThatPath("foo\\bar").doesNotMatch("glob:C:\\\\**"); assertThatPath("C:\\foo\\bar\\baz\\stuff").matches("glob:C:\\\\foo\\\\**"); assertThatPath("C:\\foo\\bar\\baz\\stuff").matches("glob:C:\\\\**\\\\stuff"); assertThatPath("C:\\foo").matches("glob:C:\\\\[a-z]*"); assertThatPath("C:\\Foo").doesNotMatch("glob:C:\\\\[a-z]*"); assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**\\\\*.java"); assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**\\\\*.{java,class}"); assertThatPath("C:\\foo\\bar\\baz\\Stuff.class").matches("glob:**\\\\*.{java,class}"); assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**\\\\*.*"); try { fs.getPathMatcher("glob:**\\*.{java,class"); fail(); } catch (PatternSyntaxException expected) { } } @Test public void testPathMatchers_glob_alternateSeparators() { // only need to test / in the glob pattern; tests above check that / in a path is changed to // \ automatically assertThatPath("C:\\foo").doesNotMatch("glob:*"); assertThatPath("C:\\foo\\bar").doesNotMatch("glob:*"); assertThatPath("C:\\foo\\bar").matches("glob:**"); assertThatPath("C:\\foo\\bar").matches("glob:C:/**"); assertThatPath("foo\\bar").doesNotMatch("glob:C:/**"); assertThatPath("C:\\foo\\bar\\baz\\stuff").matches("glob:C:/foo/**"); assertThatPath("C:\\foo\\bar\\baz\\stuff").matches("glob:C:/**/stuff"); assertThatPath("C:\\foo").matches("glob:C:/[a-z]*"); assertThatPath("C:\\Foo").doesNotMatch("glob:C:/[a-z]*"); assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**/*.java"); assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**/*.{java,class}"); assertThatPath("C:\\foo\\bar\\baz\\Stuff.class").matches("glob:**/*.{java,class}"); assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**/*.*"); try { fs.getPathMatcher("glob:**/*.{java,class"); fail(); } catch (PatternSyntaxException expected) { } } @Test public void testCreateFileOrDirectory_forNonExistentRootPath_fails() throws IOException { try { Files.createDirectory(path("Z:\\")); fail(); } catch (IOException expected) { } try { Files.createFile(path("Z:\\")); fail(); } catch (IOException expected) { } try { Files.createSymbolicLink(path("Z:\\"), path("foo")); fail(); } catch (IOException expected) { } } @Test public void testCopyFile_toNonExistentRootPath_fails() throws IOException { Files.createFile(path("foo")); Files.createDirectory(path("bar")); try { Files.copy(path("foo"), path("Z:\\")); fail(); } catch (IOException expected) { } try { Files.copy(path("bar"), path("Z:\\")); fail(); } catch (IOException expected) { } } @Test public void testMoveFile_toNonExistentRootPath_fails() throws IOException { Files.createFile(path("foo")); Files.createDirectory(path("bar")); try { Files.move(path("foo"), path("Z:\\")); fail(); } catch (IOException expected) { } try { Files.move(path("bar"), path("Z:\\")); fail(); } catch (IOException expected) { } } @Test public void testDelete_directory_cantDeleteRoot() throws IOException { // test with E:\ because it is empty try { Files.delete(path("E:\\")); fail(); } catch (FileSystemException expected) { assertThat(expected.getFile()).isEqualTo("E:\\"); assertThat(expected.getMessage()).contains("root"); } } @Test public void testCreateFileOrDirectory_forExistingRootPath_fails() throws IOException { try { Files.createDirectory(path("E:\\")); fail(); } catch (IOException expected) { } try { Files.createFile(path("E:\\")); fail(); } catch (IOException expected) { } try { Files.createSymbolicLink(path("E:\\"), path("foo")); fail(); } catch (IOException expected) { } } @Test public void testCopyFile_toExistingRootPath_fails() throws IOException { Files.createFile(path("foo")); Files.createDirectory(path("bar")); try { Files.copy(path("foo"), path("E:\\"), REPLACE_EXISTING); fail(); } catch (IOException expected) { } try { Files.copy(path("bar"), path("E:\\"), REPLACE_EXISTING); fail(); } catch (IOException expected) { } } @Test public void testMoveFile_toExistingRootPath_fails() throws IOException { Files.createFile(path("foo")); Files.createDirectory(path("bar")); try { Files.move(path("foo"), path("E:\\"), REPLACE_EXISTING); fail(); } catch (IOException expected) { } try { Files.move(path("bar"), path("E:\\"), REPLACE_EXISTING); fail(); } catch (IOException expected) { } } @Test public void testMove_rootDirectory_fails() throws IOException { try { Files.move(path("E:\\"), path("Z:\\")); fail(); } catch (FileSystemException expected) { } try { Files.move(path("E:\\"), path("C:\\bar")); fail(); } catch (FileSystemException expected) { } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/NameTest.java000066400000000000000000000023261265745405500253170ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for {@link Name}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class NameTest { @Test public void testNames() { assertThat(Name.create("foo", "foo")).isEqualTo(Name.create("foo", "foo")); assertThat(Name.create("FOO", "foo")).isEqualTo(Name.create("foo", "foo")); assertThat(Name.create("FOO", "foo")).isNotEqualTo(Name.create("FOO", "FOO")); assertThat(Name.create("a", "b").toString()).isEqualTo("a"); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/OwnerAttributeProviderTest.java000066400000000000000000000044441265745405500311330ustar00rootroot00000000000000/* * 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.jimfs.UserLookupService.createUserPrincipal; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.attribute.FileOwnerAttributeView; import java.util.Set; /** * Tests for {@link OwnerAttributeProvider}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class OwnerAttributeProviderTest extends AbstractAttributeProviderTest { @Override protected OwnerAttributeProvider createProvider() { return new OwnerAttributeProvider(); } @Override protected Set createInheritedProviders() { return ImmutableSet.of(); } @Test public void testInitialAttributes() { assertThat(provider.get(file, "owner")).isEqualTo(createUserPrincipal("user")); } @Test public void testSet() { assertSetAndGetSucceeds("owner", createUserPrincipal("user")); assertSetAndGetSucceedsOnCreate("owner", createUserPrincipal("user")); // invalid type assertSetFails("owner", "root"); } @Test public void testView() throws IOException { FileOwnerAttributeView view = provider.view(fileLookup(), NO_INHERITED_VIEWS); assertThat(view).isNotNull(); assertThat(view.name()).isEqualTo("owner"); assertThat(view.getOwner()).isEqualTo(createUserPrincipal("user")); view.setOwner(createUserPrincipal("root")); assertThat(view.getOwner()).isEqualTo(createUserPrincipal("root")); assertThat(file.getAttribute("owner", "owner")).isEqualTo(createUserPrincipal("root")); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/PathNormalizationTest.java000066400000000000000000000314271265745405500301060ustar00rootroot00000000000000/* * 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.jimfs.PathNormalization.CASE_FOLD_ASCII; import static com.google.common.jimfs.PathNormalization.CASE_FOLD_UNICODE; import static com.google.common.jimfs.PathNormalization.NFC; import static com.google.common.jimfs.PathNormalization.NFD; import static com.google.common.jimfs.TestUtils.assertNotEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.regex.Pattern; /** * Tests for {@link PathNormalization}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class PathNormalizationTest { private ImmutableSet normalizations; @Test public void testNone() { normalizations = ImmutableSet.of(); assertNormalizedEqual("foo", "foo"); assertNormalizedUnequal("Foo", "foo"); assertNormalizedUnequal("\u00c5", "\u212b"); assertNormalizedUnequal("Am\u00e9lie", "Ame\u0301lie"); } private static final String[][] CASE_FOLD_TEST_DATA = { {"foo", "fOo", "foO", "Foo", "FOO"}, {"efficient", "efficient", "efficient", "Efficient", "EFFICIENT"}, {"flour", "flour", "flour", "Flour", "FLOUR"}, {"poſt", "post", "poſt", "Poſt", "POST"}, {"poſt", "post", "poſt", "Poſt", "POST"}, {"ſtop", "stop", "ſtop", "Stop", "STOP"}, {"tschüß", "tschüss", "tschüß", "Tschüß", "TSCHÜSS"}, {"weiß", "weiss", "weiß", "Weiß", "WEISS"}, {"WEIẞ", "weiss", "weiß", "Weiß", "WEIẞ"}, {"στιγμας", "στιγμασ", "στιγμας", "Στιγμας", "ΣΤΙΓΜΑΣ"}, {"ᾲ στο διάολο", "ὰι στο διάολο", "ᾲ στο διάολο", "Ὰͅ Στο Διάολο", "ᾺΙ ΣΤΟ ΔΙΆΟΛΟ"}, {"Henry Ⅷ", "henry ⅷ", "henry ⅷ", "Henry Ⅷ", "HENRY Ⅷ"}, {"I Work At Ⓚ", "i work at ⓚ", "i work at ⓚ", "I Work At Ⓚ", "I WORK AT Ⓚ"}, {"ʀᴀʀᴇ", "ʀᴀʀᴇ", "ʀᴀʀᴇ", "Ʀᴀʀᴇ", "ƦᴀƦᴇ"}, {"Ὰͅ", "ὰι", "ᾲ", "Ὰͅ", "ᾺΙ"} }; @Test public void testCaseFold() { normalizations = ImmutableSet.of(CASE_FOLD_UNICODE); for (String[] row : CASE_FOLD_TEST_DATA) { for (int i = 0; i < row.length; i++) { for (int j = i; j < row.length; j++) { assertNormalizedEqual(row[i], row[j]); } } } } @Test public void testCaseInsensitiveAscii() { normalizations = ImmutableSet.of(CASE_FOLD_ASCII); String[] row = {"foo", "FOO", "fOo", "Foo"}; for (int i = 0; i < row.length; i++) { for (int j = i; j < row.length; j++) { assertNormalizedEqual(row[i], row[j]); } } assertNormalizedUnequal("weiß", "weiss"); } private static final String[][] NORMALIZE_TEST_DATA = { {"\u00c5", "\u212b"}, // two forms of Å (one code point each) {"Am\u00e9lie", "Ame\u0301lie"} // two forms of Amélie (one composed, one decomposed) }; @Test public void testNormalizeNfc() { normalizations = ImmutableSet.of(NFC); for (String[] row : NORMALIZE_TEST_DATA) { for (int i = 0; i < row.length; i++) { for (int j = i; j < row.length; j++) { assertNormalizedEqual(row[i], row[j]); } } } } @Test public void testNormalizeNfd() { normalizations = ImmutableSet.of(NFD); for (String[] row : NORMALIZE_TEST_DATA) { for (int i = 0; i < row.length; i++) { for (int j = i; j < row.length; j++) { assertNormalizedEqual(row[i], row[j]); } } } } private static final String[][] NORMALIZE_CASE_FOLD_TEST_DATA = { {"\u00c5", "\u00e5", "\u212b"}, {"Am\u00e9lie", "Am\u00c9lie", "Ame\u0301lie", "AME\u0301LIE"} }; @Test public void testNormalizeNfcCaseFold() { normalizations = ImmutableSet.of(NFC, CASE_FOLD_UNICODE); for (String[] row : NORMALIZE_CASE_FOLD_TEST_DATA) { for (int i = 0; i < row.length; i++) { for (int j = i; j < row.length; j++) { assertNormalizedEqual(row[i], row[j]); } } } } @Test public void testNormalizeNfdCaseFold() { normalizations = ImmutableSet.of(NFD, CASE_FOLD_UNICODE); for (String[] row : NORMALIZE_CASE_FOLD_TEST_DATA) { for (int i = 0; i < row.length; i++) { for (int j = i; j < row.length; j++) { assertNormalizedEqual(row[i], row[j]); } } } } private static final String[][] NORMALIZED_CASE_INSENSITIVE_ASCII_TEST_DATA = { {"\u00e5", "\u212b"}, {"Am\u00e9lie", "AME\u0301LIE"} }; @Test public void testNormalizeNfcCaseFoldAscii() { normalizations = ImmutableSet.of(NFC, CASE_FOLD_ASCII); for (String[] row : NORMALIZED_CASE_INSENSITIVE_ASCII_TEST_DATA) { for (int i = 0; i < row.length; i++) { for (int j = i + 1; j < row.length; j++) { assertNormalizedUnequal(row[i], row[j]); } } } } @Test public void testNormalizeNfdCaseFoldAscii() { normalizations = ImmutableSet.of(NFD, CASE_FOLD_ASCII); for (String[] row : NORMALIZED_CASE_INSENSITIVE_ASCII_TEST_DATA) { for (int i = 0; i < row.length; i++) { for (int j = i + 1; j < row.length; j++) { // since decomposition happens before case folding, the strings are equal when the // decomposed ASCII letter is folded assertNormalizedEqual(row[i], row[j]); } } } } // regex patterns offer loosely similar matching, but that's all @Test public void testNone_pattern() { normalizations = ImmutableSet.of(); assertNormalizedPatternMatches("foo", "foo"); assertNormalizedPatternDoesNotMatch("foo", "FOO"); assertNormalizedPatternDoesNotMatch("FOO", "foo"); } @Test public void testCaseFold_pattern() { normalizations = ImmutableSet.of(CASE_FOLD_UNICODE); assertNormalizedPatternMatches("foo", "foo"); assertNormalizedPatternMatches("foo", "FOO"); assertNormalizedPatternMatches("FOO", "foo"); assertNormalizedPatternMatches("Am\u00e9lie", "AM\u00c9LIE"); assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE"); assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "Ame\u0301lie"); assertNormalizedPatternDoesNotMatch("AM\u00c9LIE", "AME\u0301LIE"); assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AME\u0301LIE"); } @Test public void testCaseFoldAscii_pattern() { normalizations = ImmutableSet.of(CASE_FOLD_ASCII); assertNormalizedPatternMatches("foo", "foo"); assertNormalizedPatternMatches("foo", "FOO"); assertNormalizedPatternMatches("FOO", "foo"); assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE"); assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AM\u00c9LIE"); assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "Ame\u0301lie"); assertNormalizedPatternDoesNotMatch("AM\u00c9LIE", "AME\u0301LIE"); assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AME\u0301LIE"); } @Test public void testNormalizeNfc_pattern() { normalizations = ImmutableSet.of(NFC); assertNormalizedPatternMatches("foo", "foo"); assertNormalizedPatternDoesNotMatch("foo", "FOO"); assertNormalizedPatternDoesNotMatch("FOO", "foo"); assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie"); assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AME\u0301LIE"); } @Test public void testNormalizeNfd_pattern() { normalizations = ImmutableSet.of(NFD); assertNormalizedPatternMatches("foo", "foo"); assertNormalizedPatternDoesNotMatch("foo", "FOO"); assertNormalizedPatternDoesNotMatch("FOO", "foo"); assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie"); assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AME\u0301LIE"); } @Test public void testNormalizeNfcCaseFold_pattern() { normalizations = ImmutableSet.of(NFC, CASE_FOLD_UNICODE); assertNormalizedPatternMatches("foo", "foo"); assertNormalizedPatternMatches("foo", "FOO"); assertNormalizedPatternMatches("FOO", "foo"); assertNormalizedPatternMatches("Am\u00e9lie", "AM\u00c9LIE"); assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE"); assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie"); assertNormalizedPatternMatches("AM\u00c9LIE", "AME\u0301LIE"); assertNormalizedPatternMatches("Am\u00e9lie", "AME\u0301LIE"); } @Test public void testNormalizeNfdCaseFold_pattern() { normalizations = ImmutableSet.of(NFD, CASE_FOLD_UNICODE); assertNormalizedPatternMatches("foo", "foo"); assertNormalizedPatternMatches("foo", "FOO"); assertNormalizedPatternMatches("FOO", "foo"); assertNormalizedPatternMatches("Am\u00e9lie", "AM\u00c9LIE"); assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE"); assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie"); assertNormalizedPatternMatches("AM\u00c9LIE", "AME\u0301LIE"); assertNormalizedPatternMatches("Am\u00e9lie", "AME\u0301LIE"); } @Test public void testNormalizeNfcCaseFoldAscii_pattern() { normalizations = ImmutableSet.of(NFC, CASE_FOLD_ASCII); assertNormalizedPatternMatches("foo", "foo"); assertNormalizedPatternMatches("foo", "FOO"); assertNormalizedPatternMatches("FOO", "foo"); // these are all a bit fuzzy as when CASE_INSENSITIVE is present but not UNICODE_CASE, ASCII // only strings are expected assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE"); assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AM\u00c9LIE"); assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie"); assertNormalizedPatternMatches("AM\u00c9LIE", "AME\u0301LIE"); } @Test public void testNormalizeNfdCaseFoldAscii_pattern() { normalizations = ImmutableSet.of(NFD, CASE_FOLD_ASCII); assertNormalizedPatternMatches("foo", "foo"); assertNormalizedPatternMatches("foo", "FOO"); assertNormalizedPatternMatches("FOO", "foo"); // these are all a bit fuzzy as when CASE_INSENSITIVE is present but not UNICODE_CASE, ASCII // only strings are expected assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE"); assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AM\u00c9LIE"); assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie"); assertNormalizedPatternMatches("AM\u00c9LIE", "AME\u0301LIE"); } /** * Asserts that the given strings normalize to the same string using the current normalizer. */ private void assertNormalizedEqual(String first, String second) { assertEquals( PathNormalization.normalize(first, normalizations), PathNormalization.normalize(second, normalizations)); } /** * Asserts that the given strings normalize to different strings using the current normalizer. */ private void assertNormalizedUnequal(String first, String second) { assertNotEquals( PathNormalization.normalize(first, normalizations), PathNormalization.normalize(second, normalizations)); } /** * Asserts that the given strings match when one is compiled as a regex pattern using the current * normalizer and matched against the other. */ private void assertNormalizedPatternMatches(String first, String second) { Pattern pattern = PathNormalization.compilePattern(first, normalizations); assertTrue( "pattern '" + pattern + "' does not match '" + second + "'", pattern.matcher(second).matches()); pattern = PathNormalization.compilePattern(second, normalizations); assertTrue( "pattern '" + pattern + "' does not match '" + first + "'", pattern.matcher(first).matches()); } /** * Asserts that the given strings do not match when one is compiled as a regex pattern using the * current normalizer and matched against the other. */ private void assertNormalizedPatternDoesNotMatch(String first, String second) { Pattern pattern = PathNormalization.compilePattern(first, normalizations); assertFalse( "pattern '" + pattern + "' should not match '" + second + "'", pattern.matcher(second).matches()); pattern = PathNormalization.compilePattern(second, normalizations); assertFalse( "pattern '" + pattern + "' should not match '" + first + "'", pattern.matcher(first).matches()); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/PathServiceTest.java000066400000000000000000000155371265745405500266640ustar00rootroot00000000000000/* * 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.jimfs.PathSubject.paths; import static com.google.common.truth.Truth.assertAbout; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.net.URI; import java.nio.file.FileSystem; /** * Tests for {@link PathService}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class PathServiceTest { private static final ImmutableSet NO_NORMALIZATIONS = ImmutableSet.of(); private final PathService service = fakeUnixPathService(); @Test public void testBasicProperties() { assertThat(service.getSeparator()).isEqualTo("/"); assertThat(fakeWindowsPathService().getSeparator()).isEqualTo("\\"); } @Test public void testPathCreation() { assertAbout(paths()).that(service.emptyPath()) .hasRootComponent(null) .and().hasNameComponents(""); assertAbout(paths()).that(service.createRoot(service.name("/"))) .isAbsolute() .and().hasRootComponent("/") .and().hasNoNameComponents(); assertAbout(paths()).that(service.createFileName(service.name("foo"))) .hasRootComponent(null) .and().hasNameComponents("foo"); JimfsPath relative = service.createRelativePath(service.names(ImmutableList.of("foo", "bar"))); assertAbout(paths()).that(relative) .hasRootComponent(null) .and().hasNameComponents("foo", "bar"); JimfsPath absolute = service.createPath(service.name("/"), service.names(ImmutableList.of("foo", "bar"))); assertAbout(paths()).that(absolute) .isAbsolute() .and().hasRootComponent("/") .and().hasNameComponents("foo", "bar"); } @Test public void testPathCreation_emptyPath() { // normalized to empty path with single empty string name assertAbout(paths()).that(service.createPath(null, ImmutableList.of())) .hasRootComponent(null) .and().hasNameComponents(""); } @Test public void testPathCreation_parseIgnoresEmptyString() { // if the empty string wasn't ignored, the resulting path would be "/foo" since the empty // string would be joined with foo assertAbout(paths()).that(service.parsePath("", "foo")) .hasRootComponent(null) .and().hasNameComponents("foo"); } @Test public void testToString() { // not much to test for this since it just delegates to PathType anyway JimfsPath path = new JimfsPath(service, null, ImmutableList.of(Name.simple("foo"), Name.simple("bar"))); assertThat(service.toString(path)).isEqualTo("foo/bar"); path = new JimfsPath(service, Name.simple("/"), ImmutableList.of(Name.simple("foo"))); assertThat(service.toString(path)).isEqualTo("/foo"); } @Test public void testHash_usingDisplayForm() { PathService pathService = fakePathService(PathType.unix(), false); JimfsPath path1 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("FOO", "foo"))); JimfsPath path2 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("FOO", "FOO"))); JimfsPath path3 = new JimfsPath( pathService, null, ImmutableList.of(Name.create("FOO", "9874238974897189741"))); assertThat(pathService.hash(path1)).isEqualTo(pathService.hash(path2)); assertThat(pathService.hash(path2)).isEqualTo(pathService.hash(path3)); } @Test public void testHash_usingCanonicalForm() { PathService pathService = fakePathService(PathType.unix(), true); JimfsPath path1 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("foo", "foo"))); JimfsPath path2 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("FOO", "foo"))); JimfsPath path3 = new JimfsPath( pathService, null, ImmutableList.of(Name.create("28937497189478912374897", "foo"))); assertThat(pathService.hash(path1)).isEqualTo(pathService.hash(path2)); assertThat(pathService.hash(path2)).isEqualTo(pathService.hash(path3)); } @Test public void testCompareTo_usingDisplayForm() { PathService pathService = fakePathService(PathType.unix(), false); JimfsPath path1 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("a", "z"))); JimfsPath path2 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("b", "y"))); JimfsPath path3 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("c", "x"))); assertThat(pathService.compare(path1, path2)).isEqualTo(-1); assertThat(pathService.compare(path2, path3)).isEqualTo(-1); } @Test public void testCompareTo_usingCanonicalForm() { PathService pathService = fakePathService(PathType.unix(), true); JimfsPath path1 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("a", "z"))); JimfsPath path2 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("b", "y"))); JimfsPath path3 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("c", "x"))); assertThat(pathService.compare(path1, path2)).isEqualTo(1); assertThat(pathService.compare(path2, path3)).isEqualTo(1); } @Test public void testPathMatcher() { assertThat(service.createPathMatcher("regex:foo")) .isInstanceOf(PathMatchers.RegexPathMatcher.class); assertThat(service.createPathMatcher("glob:foo")) .isInstanceOf(PathMatchers.RegexPathMatcher.class); } public static PathService fakeUnixPathService() { return fakePathService(PathType.unix(), false); } public static PathService fakeWindowsPathService() { return fakePathService(PathType.windows(), false); } public static PathService fakePathService(PathType type, boolean equalityUsesCanonicalForm) { PathService service = new PathService(type, NO_NORMALIZATIONS, NO_NORMALIZATIONS, equalityUsesCanonicalForm); service.setFileSystem(FILE_SYSTEM); return service; } private static final FileSystem FILE_SYSTEM; static { try { FILE_SYSTEM = JimfsFileSystems.newFileSystem( new JimfsFileSystemProvider(), URI.create("jimfs://foo"), Configuration.unix()); } catch (IOException e) { throw new AssertionError(e); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/PathSubject.java000066400000000000000000000322221265745405500260110ustar00rootroot00000000000000/* * 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.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; import com.google.common.io.BaseEncoding; import com.google.common.truth.FailureStrategy; import com.google.common.truth.Subject; import com.google.common.truth.SubjectFactory; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import javax.annotation.Nullable; /** * Subject for doing assertions on file system paths. * * @author Colin Decker */ public final class PathSubject extends Subject { /** * Returns the subject factory for doing assertions on paths. */ public static SubjectFactory paths() { return new PathSubjectFactory(); } private static final LinkOption[] FOLLOW_LINKS = new LinkOption[0]; private static final LinkOption[] NOFOLLOW_LINKS = {LinkOption.NOFOLLOW_LINKS}; protected LinkOption[] linkOptions = FOLLOW_LINKS; private Charset charset = UTF_8; private PathSubject(FailureStrategy failureStrategy, Path subject) { super(failureStrategy, subject); } private Path toPath(String path) { return getSubject().getFileSystem().getPath(path); } /** * Returns this, for readability of chained assertions. */ public PathSubject and() { return this; } /** * Returns a new subject for asserting against a different path or with different link options. */ // TODO(cgruber): Talk to cdecker about removing this as an anti-pattern. public PathSubject andThat(String path, LinkOption... linkOptions) { PathSubject newSubject = new PathSubject(failureStrategy, toPath(path)); if (linkOptions.length != 0) { newSubject = newSubject.noFollowLinks(); } return newSubject; } /** * Do not follow links when looking up the path. */ public PathSubject noFollowLinks() { this.linkOptions = NOFOLLOW_LINKS; return this; } /** * Set the given charset to be used when reading the file at this path as text. Default charset * if not set is UTF-8. */ public PathSubject withCharset(Charset charset) { this.charset = checkNotNull(charset); return this; } /** * Asserts that the path is absolute (it has a root component). */ public PathSubject isAbsolute() { if (!getSubject().isAbsolute()) { fail("is absolute"); } return this; } /** * Asserts that the path is relative (it has no root component). */ public PathSubject isRelative() { if (getSubject().isAbsolute()) { fail("is relative"); } return this; } /** * Asserts that the path has the given root component. */ public PathSubject hasRootComponent(@Nullable String root) { Path rootComponent = getSubject().getRoot(); if (root == null && rootComponent != null) { fail("has root component", root); } else if (root != null && !root.equals(rootComponent.toString())) { fail("has root component", root); } return this; } /** * Asserts that the path has no name components. */ public PathSubject hasNoNameComponents() { if (getSubject().getNameCount() != 0) { fail("has no name components"); } return this; } /** * Asserts that the path has the given name components. */ public PathSubject hasNameComponents(String... names) { ImmutableList.Builder builder = ImmutableList.builder(); for (Path name : getSubject()) { builder.add(name.toString()); } if (!builder.build().equals(ImmutableList.copyOf(names))) { fail("has name components", (Object[]) names); } return this; } /** * Asserts that the path matches the given syntax and pattern. */ public PathSubject matches(String syntaxAndPattern) { PathMatcher matcher = getSubject().getFileSystem().getPathMatcher(syntaxAndPattern); if (!matcher.matches(getSubject())) { fail("matches", syntaxAndPattern); } return this; } /** * Asserts that the path does not match the given syntax and pattern. */ public PathSubject doesNotMatch(String syntaxAndPattern) { PathMatcher matcher = getSubject().getFileSystem().getPathMatcher(syntaxAndPattern); if (matcher.matches(getSubject())) { fail("does not match", syntaxAndPattern); } return this; } /** * Asserts that the path exists. */ public PathSubject exists() { if (!Files.exists(getSubject(), linkOptions)) { fail("exist"); } if (Files.notExists(getSubject(), linkOptions)) { fail("exist"); } return this; } /** * Asserts that the path does not exist. */ public PathSubject doesNotExist() { if (!Files.notExists(getSubject(), linkOptions)) { fail("does not exist"); } if (Files.exists(getSubject(), linkOptions)) { fail("does not exist"); } return this; } /** * Asserts that the path is a directory. */ public PathSubject isDirectory() { exists(); // check for directoryness should imply check for existence if (!Files.isDirectory(getSubject(), linkOptions)) { fail("is directory"); } return this; } /** * Asserts that the path is a regular file. */ public PathSubject isRegularFile() { exists(); // check for regular fileness should imply check for existence if (!Files.isRegularFile(getSubject(), linkOptions)) { fail("is regular file"); } return this; } /** * Asserts that the path is a symbolic link. */ public PathSubject isSymbolicLink() { exists(); // check for symbolic linkness should imply check for existence if (!Files.isSymbolicLink(getSubject())) { fail("is symbolic link"); } return this; } /** * Asserts that the path, which is a symbolic link, has the given path as a target. */ public PathSubject withTarget(String targetPath) throws IOException { if (!Files.readSymbolicLink(getSubject()).equals(toPath(targetPath))) { fail("symbolic link target is", targetPath); } return this; } /** * Asserts that the file the path points to exists and has the given number of links to it. * Fails on a file system that does not support the "unix" view. */ public PathSubject hasLinkCount(int count) throws IOException { exists(); int linkCount = (int) Files.getAttribute(getSubject(), "unix:nlink", linkOptions); if (linkCount != count) { fail("has link count", count); } return this; } /** * Asserts that the path resolves to the same file as the given path. */ public PathSubject isSameFileAs(String path) throws IOException { return isSameFileAs(toPath(path)); } /** * Asserts that the path resolves to the same file as the given path. */ public PathSubject isSameFileAs(Path path) throws IOException { if (!Files.isSameFile(getSubject(), path)) { fail("is same file as", path); } return this; } /** * Asserts that the path does not resolve to the same file as the given path. */ public PathSubject isNotSameFileAs(String path) throws IOException { if (Files.isSameFile(getSubject(), toPath(path))) { fail("is not same file as", path); } return this; } /** * Asserts that the directory has no children. */ public PathSubject hasNoChildren() throws IOException { isDirectory(); try (DirectoryStream stream = Files.newDirectoryStream(getSubject())) { if (stream.iterator().hasNext()) { fail("has no children"); } } return this; } /** * Asserts that the directory has children with the given names, in the given order. */ public PathSubject hasChildren(String... children) throws IOException { isDirectory(); List expectedNames = new ArrayList<>(); for (String child : children) { expectedNames.add(getSubject().getFileSystem().getPath(child)); } try (DirectoryStream stream = Files.newDirectoryStream(getSubject())) { List actualNames = new ArrayList<>(); for (Path path : stream) { actualNames.add(path.getFileName()); } if (!actualNames.equals(expectedNames)) { fail("has children", (Object[]) children); } } return this; } /** * Asserts that the file has the given size. */ public PathSubject hasSize(long size) throws IOException { if (Files.size(getSubject()) != size) { fail("has size", size); } return this; } /** * Asserts that the file is a regular file containing no bytes. */ public PathSubject containsNoBytes() throws IOException { return containsBytes(new byte[0]); } /** * Asserts that the file is a regular file containing exactly the byte values of the given ints. */ public PathSubject containsBytes(int... bytes) throws IOException { byte[] realBytes = new byte[bytes.length]; for (int i = 0; i < bytes.length; i++) { realBytes[i] = (byte) bytes[i]; } return containsBytes(realBytes); } /** * Asserts that the file is a regular file containing exactly the given bytes. */ public PathSubject containsBytes(byte[] bytes) throws IOException { isRegularFile(); hasSize(bytes.length); byte[] actual = Files.readAllBytes(getSubject()); if (!Arrays.equals(bytes, actual)) { System.out.println(BaseEncoding.base16().encode(actual)); System.out.println(BaseEncoding.base16().encode(bytes)); fail("contains bytes", BaseEncoding.base16().encode(bytes)); } return this; } /** * Asserts that the file is a regular file containing the same bytes as the regular file at the * given path. */ public PathSubject containsSameBytesAs(String path) throws IOException { isRegularFile(); byte[] expectedBytes = Files.readAllBytes(toPath(path)); if (!Arrays.equals(expectedBytes, Files.readAllBytes(getSubject()))) { fail("contains same bytes as", path); } return this; } /** * Asserts that the file is a regular file containing the given lines of text. By default, the * bytes are decoded as UTF-8; for a different charset, use {@link #withCharset(Charset)}. */ public PathSubject containsLines(String... lines) throws IOException { return containsLines(Arrays.asList(lines)); } /** * Asserts that the file is a regular file containing the given lines of text. By default, the * bytes are decoded as UTF-8; for a different charset, use {@link #withCharset(Charset)}. */ public PathSubject containsLines(Iterable lines) throws IOException { isRegularFile(); List expected = ImmutableList.copyOf(lines); List actual = Files.readAllLines(getSubject(), charset); if (!expected.equals(actual)) { failWithBadResults("contains lines", expected, "contains", actual); } return this; } /** * Returns an object for making assertions about the given attribute. */ public Attribute attribute(final String attribute) { return new Attribute() { @Override public Attribute is(Object value) throws IOException { Object actualValue = Files.getAttribute(getSubject(), attribute, linkOptions); if (!Objects.equals(value, actualValue)) { fail("attribute '" + attribute + "' is", value); } return this; } @Override public Attribute isNot(Object value) throws IOException { Object actualValue = Files.getAttribute(getSubject(), attribute, linkOptions); if (Objects.equals(value, actualValue)) { fail("attribute '" + attribute + "' is not", value); } return this; } @Override public PathSubject and() { return PathSubject.this; } }; } private static class PathSubjectFactory extends SubjectFactory { @Override public PathSubject getSubject(FailureStrategy fs, Path that) { return new PathSubject(fs, that); } } /** * Interface for assertions about a file attribute. */ public interface Attribute { /** * Asserts that the value of this attribute is equal to the given value. */ Attribute is(Object value) throws IOException; /** * Asserts that the value of this attribute is not equal to the given value. */ Attribute isNot(Object value) throws IOException; /** * Returns the path subject for further chaining. */ PathSubject and(); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/PathTester.java000066400000000000000000000142221265745405500256600ustar00rootroot00000000000000/* * 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.Functions.toStringFunction; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import java.nio.file.Path; import java.util.Arrays; import java.util.List; /** * @author Colin Decker */ public final class PathTester { private final PathService pathService; private final String string; private String root; private ImmutableList names = ImmutableList.of(); public PathTester(PathService pathService, String string) { this.pathService = pathService; this.string = string; } public PathTester root(String root) { this.root = root; return this; } public PathTester names(Iterable names) { this.names = ImmutableList.copyOf(names); return this; } public PathTester names(String... names) { return names(Arrays.asList(names)); } public void test(String first, String... more) { Path path = pathService.parsePath(first, more); test(path); } public void test(Path path) { assertEquals(string, path.toString()); testRoot(path); testNames(path); testParents(path); testStartsWith(path); testEndsWith(path); testSubpaths(path); } private void testRoot(Path path) { if (root != null) { assertTrue(path + ".isAbsolute() should be true", path.isAbsolute()); assertNotNull(path + ".getRoot() should not be null", path.getRoot()); assertEquals(root, path.getRoot().toString()); } else { assertFalse(path + ".isAbsolute() should be false", path.isAbsolute()); assertNull(path + ".getRoot() should be null", path.getRoot()); } } private void testNames(Path path) { assertEquals(names.size(), path.getNameCount()); assertEquals(names, names(path)); for (int i = 0; i < names.size(); i++) { assertEquals(names.get(i), path.getName(i).toString()); // don't test individual names if this is an individual name if (names.size() > 1) { new PathTester(pathService, names.get(i)) .names(names.get(i)) .test(path.getName(i)); } } if (names.size() > 0) { String fileName = names.get(names.size() - 1); assertEquals(fileName, path.getFileName().toString()); // don't test individual names if this is an individual name if (names.size() > 1) { new PathTester(pathService, fileName) .names(fileName) .test(path.getFileName()); } } } private void testParents(Path path) { Path parent = path.getParent(); if (root != null && names.size() >= 1 || names.size() > 1) { assertNotNull(parent); } if (parent != null) { String parentName = names.size() == 1 ? root : string.substring(0, string.lastIndexOf('/')); new PathTester(pathService, parentName) .root(root) .names(names.subList(0, names.size() - 1)) .test(parent); } } private void testSubpaths(Path path) { if (path.getRoot() == null) { assertEquals(path, path.subpath(0, path.getNameCount())); } if (path.getNameCount() > 1) { String stringWithoutRoot = root == null ? string : string.substring(root.length()); // test start + 1 to end and start to end - 1 subpaths... this recursively tests all subpaths // actually tests most possible subpaths multiple times but... eh Path startSubpath = path.subpath(1, path.getNameCount()); List startNames = ImmutableList.copyOf(Splitter.on('/').split(stringWithoutRoot)) .subList(1, path.getNameCount()); new PathTester(pathService, Joiner.on('/').join(startNames)) .names(startNames).test(startSubpath); Path endSubpath = path.subpath(0, path.getNameCount() - 1); List endNames = ImmutableList.copyOf(Splitter.on('/').split(stringWithoutRoot)) .subList(0, path.getNameCount() - 1); new PathTester(pathService, Joiner.on('/').join(endNames)).names(endNames).test(endSubpath); } } private void testStartsWith(Path path) { // empty path doesn't start with any path if (root != null || !names.isEmpty()) { Path other = path; while (other != null) { assertTrue(path + ".startsWith(" + other + ") should be true", path.startsWith(other)); assertTrue( path + ".startsWith(" + other + ") should be true", path.startsWith(other.toString())); other = other.getParent(); } } } private void testEndsWith(Path path) { // empty path doesn't start with any path if (root != null || !names.isEmpty()) { Path other = path; while (other != null) { assertTrue(path + ".endsWith(" + other + ") should be true", path.endsWith(other)); assertTrue( path + ".endsWith(" + other + ") should be true", path.endsWith(other.toString())); if (other.getRoot() != null && other.getNameCount() > 0) { other = other.subpath(0, other.getNameCount()); } else if (other.getNameCount() > 1) { other = other.subpath(1, other.getNameCount()); } else { other = null; } } } } private static List names(Path path) { return FluentIterable.from(path).transform(toStringFunction()).toList(); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/PathTypeTest.java000066400000000000000000000123571265745405500262020ustar00rootroot00000000000000/* * 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.jimfs.PathType.ParseResult; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.net.URI; import javax.annotation.Nullable; /** * Tests for {@link PathType}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class PathTypeTest { private static final FakePathType type = new FakePathType(); static final URI fileSystemUri = URI.create("jimfs://foo"); @Test public void testBasicProperties() { assertThat(type.getSeparator()).isEqualTo("/"); assertThat(type.getOtherSeparators()).isEqualTo("\\"); } @Test public void testParsePath() { ParseResult path = type.parsePath("foo/bar/baz/one\\two"); assertParseResult(path, null, "foo", "bar", "baz", "one", "two"); ParseResult path2 = type.parsePath("$one//\\two"); assertParseResult(path2, "$", "one", "two"); } @Test public void testToString() { ParseResult path = type.parsePath("foo/bar\\baz"); assertThat(type.toString(path.root(), path.names())).isEqualTo("foo/bar/baz"); ParseResult path2 = type.parsePath("$/foo/bar"); assertThat(type.toString(path2.root(), path2.names())).isEqualTo("$foo/bar"); } @Test public void testToUri() { URI fileUri = type.toUri(fileSystemUri, "$", ImmutableList.of("foo", "bar"), false); assertThat(fileUri.toString()).isEqualTo("jimfs://foo/$/foo/bar"); assertThat(fileUri.getPath()).isEqualTo("/$/foo/bar"); URI directoryUri = type.toUri(fileSystemUri, "$", ImmutableList.of("foo", "bar"), true); assertThat(directoryUri.toString()).isEqualTo("jimfs://foo/$/foo/bar/"); assertThat(directoryUri.getPath()).isEqualTo("/$/foo/bar/"); URI rootUri = type.toUri(fileSystemUri, "$", ImmutableList.of(), true); assertThat(rootUri.toString()).isEqualTo("jimfs://foo/$/"); assertThat(rootUri.getPath()).isEqualTo("/$/"); } @Test public void testToUri_escaping() { URI fileUri = type.toUri(fileSystemUri, "$", ImmutableList.of("foo", "bar baz"), false); assertThat(fileUri.toString()).isEqualTo("jimfs://foo/$/foo/bar%20baz"); assertThat(fileUri.getRawPath()).isEqualTo("/$/foo/bar%20baz"); assertThat(fileUri.getPath()).isEqualTo("/$/foo/bar baz"); } @Test public void testUriRoundTrips() { assertUriRoundTripsCorrectly(type, "$"); assertUriRoundTripsCorrectly(type, "$foo"); assertUriRoundTripsCorrectly(type, "$foo/bar/baz"); assertUriRoundTripsCorrectly(type, "$foo bar"); assertUriRoundTripsCorrectly(type, "$foo/bar baz"); } static void assertParseResult(ParseResult result, @Nullable String root, String... names) { assertThat(result.root()).isEqualTo(root); assertThat(result.names()).containsExactly((Object[]) names).inOrder(); } static void assertUriRoundTripsCorrectly(PathType type, String path) { ParseResult result = type.parsePath(path); URI uri = type.toUri(fileSystemUri, result.root(), result.names(), false); ParseResult parsedUri = type.fromUri(uri); assertThat(parsedUri.root()).isEqualTo(result.root()); assertThat(parsedUri.names()).containsExactlyElementsIn(result.names()).inOrder(); } /** * Arbitrary path type with $ as the root, / as the separator and \ as an alternate separator. */ private static final class FakePathType extends PathType { protected FakePathType() { super(false, '/', '\\'); } @Override public ParseResult parsePath(String path) { String root = null; if (path.startsWith("$")) { root = "$"; path = path.substring(1); } return new ParseResult(root, splitter().split(path)); } @Override public String toString(@Nullable String root, Iterable names) { StringBuilder builder = new StringBuilder(); if (root != null) { builder.append(root); } joiner().appendTo(builder, names); return builder.toString(); } @Override public String toUriPath(String root, Iterable names, boolean directory) { StringBuilder builder = new StringBuilder(); builder.append('/').append(root); for (String name : names) { builder.append('/').append(name); } if (directory) { builder.append('/'); } return builder.toString(); } @Override public ParseResult parseUriPath(String uriPath) { checkArgument(uriPath.startsWith("/$"), "uriPath (%s) must start with /$", uriPath); return parsePath(uriPath.substring(1)); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/PollingWatchServiceTest.java000066400000000000000000000200521265745405500303470ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.jimfs.AbstractWatchService.Event; import com.google.common.jimfs.AbstractWatchService.Key; import com.google.common.util.concurrent.Runnables; import com.google.common.util.concurrent.Uninterruptibles; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.util.Arrays; import java.util.List; import java.util.UUID; /** * Tests for {@link PollingWatchService}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class PollingWatchServiceTest { private JimfsFileSystem fs; private PollingWatchService watcher; @Before public void setUp() { fs = (JimfsFileSystem) Jimfs.newFileSystem(Configuration.unix()); watcher = new PollingWatchService( fs.getDefaultView(), fs.getPathService(), new FileSystemState(Runnables.doNothing()), 4, MILLISECONDS); } @After public void tearDown() throws IOException { watcher.close(); fs.close(); watcher = null; fs = null; } @Test public void testNewWatcher() { assertThat(watcher.isOpen()).isTrue(); assertThat(watcher.isPolling()).isFalse(); } @Test public void testRegister() throws IOException { Key key = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE)); assertThat(key.isValid()).isTrue(); assertThat(watcher.isPolling()).isTrue(); } @Test public void testRegister_fileDoesNotExist() throws IOException { try { watcher.register(fs.getPath("/a/b/c"), ImmutableList.of(ENTRY_CREATE)); fail(); } catch (NoSuchFileException expected) { } } @Test public void testRegister_fileIsNotDirectory() throws IOException { Path path = fs.getPath("/a.txt"); Files.createFile(path); try { watcher.register(path, ImmutableList.of(ENTRY_CREATE)); fail(); } catch (NotDirectoryException expected) { } } @Test public void testCancellingLastKeyStopsPolling() throws IOException { Key key = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE)); key.cancel(); assertThat(key.isValid()).isFalse(); assertThat(watcher.isPolling()).isFalse(); Key key2 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE)); Key key3 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_DELETE)); assertThat(watcher.isPolling()).isTrue(); key2.cancel(); assertThat(watcher.isPolling()).isTrue(); key3.cancel(); assertThat(watcher.isPolling()).isFalse(); } @Test public void testCloseCancelsAllKeysAndStopsPolling() throws IOException { Key key1 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE)); Key key2 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_DELETE)); assertThat(key1.isValid()).isTrue(); assertThat(key2.isValid()).isTrue(); assertThat(watcher.isPolling()).isTrue(); watcher.close(); assertThat(key1.isValid()).isFalse(); assertThat(key2.isValid()).isFalse(); assertThat(watcher.isPolling()).isFalse(); } @Test(timeout = 2000) public void testWatchForOneEventType() throws IOException, InterruptedException { JimfsPath path = createDirectory(); watcher.register(path, ImmutableList.of(ENTRY_CREATE)); Files.createFile(path.resolve("foo")); assertWatcherHasEvents(new Event<>(ENTRY_CREATE, 1, fs.getPath("foo"))); Files.createFile(path.resolve("bar")); Files.createFile(path.resolve("baz")); assertWatcherHasEvents( new Event<>(ENTRY_CREATE, 1, fs.getPath("bar")), new Event<>(ENTRY_CREATE, 1, fs.getPath("baz"))); } @Test(timeout = 2000) public void testWatchForMultipleEventTypes() throws IOException, InterruptedException { JimfsPath path = createDirectory(); watcher.register(path, ImmutableList.of(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY)); Files.createDirectory(path.resolve("foo")); Files.createFile(path.resolve("bar")); assertWatcherHasEvents( new Event<>(ENTRY_CREATE, 1, fs.getPath("bar")), new Event<>(ENTRY_CREATE, 1, fs.getPath("foo"))); Files.createFile(path.resolve("baz")); Files.delete(path.resolve("bar")); Files.createFile(path.resolve("foo/bar")); assertWatcherHasEvents( new Event<>(ENTRY_CREATE, 1, fs.getPath("baz")), new Event<>(ENTRY_DELETE, 1, fs.getPath("bar")), new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo"))); Files.delete(path.resolve("foo/bar")); ensureTimeToPoll(); // watcher polls, seeing modification, then polls again, seeing delete Files.delete(path.resolve("foo")); assertWatcherHasEvents( new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo")), new Event<>(ENTRY_DELETE, 1, fs.getPath("foo"))); Files.createDirectories(path.resolve("foo/bar")); // polling here may either see just the creation of foo, or may first see the creation of foo // and then the creation of foo/bar (modification of foo) since those don't happen atomically assertWatcherHasEvents( ImmutableList.>of(new Event<>(ENTRY_CREATE, 1, fs.getPath("foo"))), // or ImmutableList.>of( new Event<>(ENTRY_CREATE, 1, fs.getPath("foo")), new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo")))); Files.delete(path.resolve("foo/bar")); Files.delete(path.resolve("foo")); // polling here may either just see the deletion of foo, or may first see the deletion of bar // (modification of foo) and then the deletion of foo assertWatcherHasEvents( ImmutableList.>of(new Event<>(ENTRY_DELETE, 1, fs.getPath("foo"))), // or ImmutableList.>of( new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo")), new Event<>(ENTRY_DELETE, 1, fs.getPath("foo")))); } private void assertWatcherHasEvents(WatchEvent... events) throws InterruptedException { assertWatcherHasEvents(Arrays.asList(events), ImmutableList.>of()); } private void assertWatcherHasEvents(List> expected, List> alternate) throws InterruptedException { ensureTimeToPoll(); // otherwise we could read 1 event but not all the events we're expecting WatchKey key = watcher.take(); List> keyEvents = key.pollEvents(); if (keyEvents.size() == expected.size() || alternate.isEmpty()) { assertThat(keyEvents).containsExactlyElementsIn(expected); } else { assertThat(keyEvents).containsExactlyElementsIn(alternate); } key.reset(); } private static void ensureTimeToPoll() { Uninterruptibles.sleepUninterruptibly(40, MILLISECONDS); } private JimfsPath createDirectory() throws IOException { JimfsPath path = fs.getPath("/" + UUID.randomUUID().toString()); Files.createDirectory(path); return path; } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/PosixAttributeProviderTest.java000066400000000000000000000111321265745405500311330ustar00rootroot00000000000000/* * 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.jimfs.UserLookupService.createGroupPrincipal; import static com.google.common.jimfs.UserLookupService.createUserPrincipal; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertNotNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.util.Set; /** * Tests for {@link PosixAttributeProvider}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class PosixAttributeProviderTest extends AbstractAttributeProviderTest { @Override protected PosixAttributeProvider createProvider() { return new PosixAttributeProvider(); } @Override protected Set createInheritedProviders() { return ImmutableSet.of(new BasicAttributeProvider(), new OwnerAttributeProvider()); } @Test public void testInitialAttributes() { assertContainsAll( file, ImmutableMap.of( "group", createGroupPrincipal("group"), "permissions", PosixFilePermissions.fromString("rw-r--r--"))); } @Test public void testSet() { assertSetAndGetSucceeds("group", createGroupPrincipal("foo")); assertSetAndGetSucceeds("permissions", PosixFilePermissions.fromString("rwxrwxrwx")); // invalid types assertSetFails("permissions", ImmutableList.of(PosixFilePermission.GROUP_EXECUTE)); assertSetFails("permissions", ImmutableSet.of("foo")); } @Test public void testSetOnCreate() { assertSetAndGetSucceedsOnCreate("permissions", PosixFilePermissions.fromString("rwxrwxrwx")); assertSetFailsOnCreate("group", createGroupPrincipal("foo")); } @Test public void testView() throws IOException { file.setAttribute("owner", "owner", createUserPrincipal("user")); PosixFileAttributeView view = provider.view( fileLookup(), ImmutableMap.of( "basic", new BasicAttributeProvider().view(fileLookup(), NO_INHERITED_VIEWS), "owner", new OwnerAttributeProvider().view(fileLookup(), NO_INHERITED_VIEWS))); assertNotNull(view); assertThat(view.name()).isEqualTo("posix"); assertThat(view.getOwner()).isEqualTo(createUserPrincipal("user")); PosixFileAttributes attrs = view.readAttributes(); assertThat(attrs.fileKey()).isEqualTo(0); assertThat(attrs.owner()).isEqualTo(createUserPrincipal("user")); assertThat(attrs.group()).isEqualTo(createGroupPrincipal("group")); assertThat(attrs.permissions()).isEqualTo(PosixFilePermissions.fromString("rw-r--r--")); view.setOwner(createUserPrincipal("root")); assertThat(view.getOwner()).isEqualTo(createUserPrincipal("root")); assertThat(file.getAttribute("owner", "owner")).isEqualTo(createUserPrincipal("root")); view.setGroup(createGroupPrincipal("root")); assertThat(view.readAttributes().group()).isEqualTo(createGroupPrincipal("root")); assertThat(file.getAttribute("posix", "group")).isEqualTo(createGroupPrincipal("root")); view.setPermissions(PosixFilePermissions.fromString("rwx------")); assertThat(view.readAttributes().permissions()) .isEqualTo(PosixFilePermissions.fromString("rwx------")); assertThat(file.getAttribute("posix", "permissions")) .isEqualTo(PosixFilePermissions.fromString("rwx------")); } @Test public void testAttributes() { PosixFileAttributes attrs = provider.readAttributes(file); assertThat(attrs.permissions()).isEqualTo(PosixFilePermissions.fromString("rw-r--r--")); assertThat(attrs.group()).isEqualTo(createGroupPrincipal("group")); assertThat(attrs.fileKey()).isEqualTo(0); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/RegexGlobMatcherTest.java000066400000000000000000000063601265745405500276230ustar00rootroot00000000000000/* * 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 org.junit.Assert.assertEquals; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.PathMatcher; import java.util.regex.Pattern; /** * Tests for {@link PathMatcher} instances created by {@link GlobToRegex}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class RegexGlobMatcherTest extends AbstractGlobMatcherTest { @Override protected PathMatcher matcher(String pattern) { return PathMatchers.getPathMatcher( "glob:" + pattern, "/", ImmutableSet.of()); } @Override protected PathMatcher realMatcher(String pattern) { FileSystem defaultFileSystem = FileSystems.getDefault(); if ("/".equals(defaultFileSystem.getSeparator())) { return defaultFileSystem.getPathMatcher("glob:" + pattern); } return null; } @Test public void testRegexTranslation() { assertGlobRegexIs("foo", "foo"); assertGlobRegexIs("/", "/"); assertGlobRegexIs("?", "[^/]"); assertGlobRegexIs("*", "[^/]*"); assertGlobRegexIs("**", ".*"); assertGlobRegexIs("/foo", "/foo"); assertGlobRegexIs("?oo", "[^/]oo"); assertGlobRegexIs("*oo", "[^/]*oo"); assertGlobRegexIs("**/*.java", ".*/[^/]*\\.java"); assertGlobRegexIs("[a-z]", "[[^/]&&[a-z]]"); assertGlobRegexIs("[!a-z]", "[[^/]&&[^a-z]]"); assertGlobRegexIs("[-a-z]", "[[^/]&&[-a-z]]"); assertGlobRegexIs("[!-a-z]", "[[^/]&&[^-a-z]]"); assertGlobRegexIs("{a,b,c}", "(a|b|c)"); assertGlobRegexIs("{?oo,[A-Z]*,foo/**}", "([^/]oo|[[^/]&&[A-Z]][^/]*|foo/.*)"); } @Test public void testRegexEscaping() { assertGlobRegexIs("(", "\\("); assertGlobRegexIs(".", "\\."); assertGlobRegexIs("^", "\\^"); assertGlobRegexIs("$", "\\$"); assertGlobRegexIs("+", "\\+"); assertGlobRegexIs("\\\\", "\\\\"); assertGlobRegexIs("]", "\\]"); assertGlobRegexIs(")", "\\)"); assertGlobRegexIs("}", "\\}"); } @Test public void testRegexTranslationWithMultipleSeparators() { assertGlobRegexIs("?", "[^\\\\/]", "\\/"); assertGlobRegexIs("*", "[^\\\\/]*", "\\/"); assertGlobRegexIs("/", "[\\\\/]", "\\/"); assertGlobRegexIs("\\\\", "[\\\\/]", "\\/"); } private static void assertGlobRegexIs(String glob, String regex) { assertGlobRegexIs(glob, regex, "/"); } private static void assertGlobRegexIs(String glob, String regex, String separators) { assertEquals(regex, GlobToRegex.toRegex(glob, separators)); Pattern.compile(regex); // ensure the regex syntax is valid } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/RegularFileBlocksTest.java000066400000000000000000000105021265745405500277710ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import com.google.common.primitives.Bytes; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for the lower-level operations dealing with the blocks of a {@link RegularFile}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class RegularFileBlocksTest { private RegularFile file; @Before public void setUp() { file = createFile(); } private static RegularFile createFile() { return RegularFile.create(-1, new HeapDisk(2, 2, 2)); } @Test public void testInitialState() { assertThat(file.blockCount()).isEqualTo(0); // no bounds checking, but there should never be a block at an index >= size assertThat(file.getBlock(0)).isNull(); } @Test public void testAddAndGet() { file.addBlock(new byte[] {1}); assertThat(file.blockCount()).isEqualTo(1); assertThat(Bytes.asList(file.getBlock(0))).isEqualTo(Bytes.asList(new byte[] {1})); assertThat(file.getBlock(1)).isNull(); file.addBlock(new byte[] {1, 2}); assertThat(file.blockCount()).isEqualTo(2); assertThat(Bytes.asList(file.getBlock(1))).isEqualTo(Bytes.asList(new byte[] {1, 2})); assertThat(file.getBlock(2)).isNull(); } @Test public void testTruncate() { file.addBlock(new byte[0]); file.addBlock(new byte[0]); file.addBlock(new byte[0]); file.addBlock(new byte[0]); assertThat(file.blockCount()).isEqualTo(4); file.truncateBlocks(2); assertThat(file.blockCount()).isEqualTo(2); assertThat(file.getBlock(2)).isNull(); assertThat(file.getBlock(3)).isNull(); assertThat(file.getBlock(0)).isNotNull(); file.truncateBlocks(0); assertThat(file.blockCount()).isEqualTo(0); assertThat(file.getBlock(0)).isNull(); } @Test public void testCopyTo() { file.addBlock(new byte[] {1}); file.addBlock(new byte[] {1, 2}); RegularFile other = createFile(); assertThat(other.blockCount()).isEqualTo(0); file.copyBlocksTo(other, 2); assertThat(other.blockCount()).isEqualTo(2); assertThat(other.getBlock(0)).isEqualTo(file.getBlock(0)); assertThat(other.getBlock(1)).isEqualTo(file.getBlock(1)); file.copyBlocksTo(other, 1); // should copy the last block assertThat(other.blockCount()).isEqualTo(3); assertThat(other.getBlock(2)).isEqualTo(file.getBlock(1)); other.copyBlocksTo(file, 3); assertThat(file.blockCount()).isEqualTo(5); assertThat(file.getBlock(2)).isEqualTo(other.getBlock(0)); assertThat(file.getBlock(3)).isEqualTo(other.getBlock(1)); assertThat(file.getBlock(4)).isEqualTo(other.getBlock(2)); } @Test public void testTransferTo() { file.addBlock(new byte[] {1}); file.addBlock(new byte[] {1, 2}); file.addBlock(new byte[] {1, 2, 3}); RegularFile other = createFile(); assertThat(file.blockCount()).isEqualTo(3); assertThat(other.blockCount()).isEqualTo(0); file.transferBlocksTo(other, 3); assertThat(file.blockCount()).isEqualTo(0); assertThat(other.blockCount()).isEqualTo(3); assertThat(file.getBlock(0)).isNull(); assertThat(Bytes.asList(other.getBlock(0))).isEqualTo(Bytes.asList(new byte[] {1})); assertThat(Bytes.asList(other.getBlock(1))).isEqualTo(Bytes.asList(new byte[] {1, 2})); assertThat(Bytes.asList(other.getBlock(2))).isEqualTo(Bytes.asList(new byte[] {1, 2, 3})); other.transferBlocksTo(file, 1); assertThat(file.blockCount()).isEqualTo(1); assertThat(other.blockCount()).isEqualTo(2); assertThat(other.getBlock(2)).isNull(); assertThat(Bytes.asList(file.getBlock(0))).isEqualTo(Bytes.asList(new byte[] {1, 2, 3})); assertThat(file.getBlock(1)).isNull(); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/RegularFileTest.java000066400000000000000000001004761265745405500266450ustar00rootroot00000000000000/* * 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.jimfs.TestUtils.buffer; import static com.google.common.jimfs.TestUtils.buffers; import static com.google.common.jimfs.TestUtils.bytes; import static com.google.common.primitives.Bytes.concat; import static org.junit.Assert.assertArrayEquals; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import junit.framework.TestCase; import junit.framework.TestSuite; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.Random; import java.util.Set; /** * Tests for {@link RegularFile} and by extension for {@link HeapDisk}. These tests test files * created by a heap disk in a number of different states. * * @author Colin Decker */ public class RegularFileTest { /** * Returns a test suite for testing file methods with a variety of {@code HeapDisk} * configurations. */ public static TestSuite suite() { TestSuite suite = new TestSuite(); for (ReuseStrategy reuseStrategy : EnumSet.allOf(ReuseStrategy.class)) { TestSuite suiteForReuseStrategy = new TestSuite(reuseStrategy.toString()); Set> sizeOptions = Sets.cartesianProduct(ImmutableList.of(BLOCK_SIZES, CACHE_SIZES)); for (List options : sizeOptions) { int blockSize = options.get(0); int cacheSize = options.get(1); if (cacheSize > 0 && cacheSize < blockSize) { // skip cases where the cache size is not -1 (all) or 0 (none) but it is < blockSize, // because this is equivalent to a cache size of 0 continue; } TestConfiguration state = new TestConfiguration(blockSize, cacheSize, reuseStrategy); TestSuite suiteForTest = new TestSuite(state.toString()); for (Method method : TEST_METHODS) { RegularFileTestRunner tester = new RegularFileTestRunner(method.getName(), state); suiteForTest.addTest(tester); } suiteForReuseStrategy.addTest(suiteForTest); } suite.addTest(suiteForReuseStrategy); } return suite; } public static final ImmutableSet BLOCK_SIZES = ImmutableSet.of(2, 8, 128, 8192); public static final ImmutableSet CACHE_SIZES = ImmutableSet.of(0, 4, 16, 128, -1); private static final ImmutableList TEST_METHODS = FluentIterable.from(Arrays.asList(RegularFileTestRunner.class.getDeclaredMethods())) .filter( new Predicate() { @Override public boolean apply(Method method) { return method.getName().startsWith("test") && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0; } }) .toList(); /** * Different strategies for handling reuse of disks and/or files between tests, intended to * ensure that {@link HeapDisk} operates properly in a variety of usage states including newly * created, having created files that have not been deleted yet, having created files that have * been deleted, and having created files some of which have been deleted and some of which have * not. */ public enum ReuseStrategy { /** * Creates a new disk for each test. */ NEW_DISK, /** * Retains files after each test, forcing new blocks to be allocated. */ KEEP_FILES, /** * Deletes files after each test, allowing caching to be used if enabled. */ DELETE_FILES, /** * Randomly keeps or deletes a file after each test. */ KEEP_OR_DELETE_FILES } /** * Configuration for a set of test cases. */ public static final class TestConfiguration { private final int blockSize; private final int cacheSize; private final ReuseStrategy reuseStrategy; private HeapDisk disk; public TestConfiguration(int blockSize, int cacheSize, ReuseStrategy reuseStrategy) { this.blockSize = blockSize; this.cacheSize = cacheSize; this.reuseStrategy = reuseStrategy; if (reuseStrategy != ReuseStrategy.NEW_DISK) { this.disk = createDisk(); } } private HeapDisk createDisk() { int maxCachedBlockCount = cacheSize == -1 ? Integer.MAX_VALUE : (cacheSize / blockSize); return new HeapDisk(blockSize, Integer.MAX_VALUE, maxCachedBlockCount); } public RegularFile createRegularFile() { if (reuseStrategy == ReuseStrategy.NEW_DISK) { disk = createDisk(); } return RegularFile.create(0, disk); } public void tearDown(RegularFile file) { switch (reuseStrategy) { case DELETE_FILES: file.deleted(); break; case KEEP_OR_DELETE_FILES: if (new Random().nextBoolean()) { file.deleted(); } break; case KEEP_FILES: break; default: break; } } @Override public String toString() { return reuseStrategy + " [" + blockSize + ", " + cacheSize + "]"; } } /** * Actual test cases for testing RegularFiles. */ public static class RegularFileTestRunner extends TestCase { private final TestConfiguration configuration; protected RegularFile file; public RegularFileTestRunner(String methodName, TestConfiguration configuration) { super(methodName); this.configuration = configuration; } @Override public String getName() { return super.getName() + " [" + configuration + "]"; } @Override public void setUp() { file = configuration.createRegularFile(); } @Override public void tearDown() { configuration.tearDown(file); } private void fillContent(String fill) throws IOException { file.write(0, buffer(fill)); } public void testEmpty() { assertEquals(0, file.size()); assertContentEquals("", file); } public void testEmpty_read_singleByte() { assertEquals(-1, file.read(0)); assertEquals(-1, file.read(1)); } public void testEmpty_read_byteArray() { byte[] array = new byte[10]; assertEquals(-1, file.read(0, array, 0, array.length)); assertArrayEquals(bytes("0000000000"), array); } public void testEmpty_read_singleBuffer() { ByteBuffer buffer = ByteBuffer.allocate(10); int read = file.read(0, buffer); assertEquals(-1, read); assertEquals(0, buffer.position()); } public void testEmpty_read_multipleBuffers() { ByteBuffer buf1 = ByteBuffer.allocate(5); ByteBuffer buf2 = ByteBuffer.allocate(5); long read = file.read(0, ImmutableList.of(buf1, buf2)); assertEquals(-1, read); assertEquals(0, buf1.position()); assertEquals(0, buf2.position()); } public void testEmpty_write_singleByte_atStart() throws IOException { file.write(0, (byte) 1); assertContentEquals("1", file); } public void testEmpty_write_byteArray_atStart() throws IOException { byte[] bytes = bytes("111111"); file.write(0, bytes, 0, bytes.length); assertContentEquals(bytes, file); } public void testEmpty_write_partialByteArray_atStart() throws IOException { byte[] bytes = bytes("2211111122"); file.write(0, bytes, 2, 6); assertContentEquals("111111", file); } public void testEmpty_write_singleBuffer_atStart() throws IOException { file.write(0, buffer("111111")); assertContentEquals("111111", file); } public void testEmpty_write_multipleBuffers_atStart() throws IOException { file.write(0, buffers("111", "111")); assertContentEquals("111111", file); } public void testEmpty_write_singleByte_atNonZeroPosition() throws IOException { file.write(5, (byte) 1); assertContentEquals("000001", file); } public void testEmpty_write_byteArray_atNonZeroPosition() throws IOException { byte[] bytes = bytes("111111"); file.write(5, bytes, 0, bytes.length); assertContentEquals("00000111111", file); } public void testEmpty_write_partialByteArray_atNonZeroPosition() throws IOException { byte[] bytes = bytes("2211111122"); file.write(5, bytes, 2, 6); assertContentEquals("00000111111", file); } public void testEmpty_write_singleBuffer_atNonZeroPosition() throws IOException { file.write(5, buffer("111")); assertContentEquals("00000111", file); } public void testEmpty_write_multipleBuffers_atNonZeroPosition() throws IOException { file.write(5, buffers("111", "222")); assertContentEquals("00000111222", file); } public void testEmpty_write_noBytesArray_atStart() throws IOException { file.write(0, bytes(), 0, 0); assertContentEquals(bytes(), file); } public void testEmpty_write_noBytesArray_atNonZeroPosition() throws IOException { file.write(5, bytes(), 0, 0); assertContentEquals(bytes("00000"), file); } public void testEmpty_write_noBytesBuffer_atStart() throws IOException { file.write(0, buffer("")); assertContentEquals(bytes(), file); } public void testEmpty_write_noBytesBuffer_atNonZeroPosition() throws IOException { ByteBuffer buffer = ByteBuffer.allocate(0); file.write(5, buffer); assertContentEquals(bytes("00000"), file); } public void testEmpty_write_noBytesBuffers_atStart() throws IOException { file.write(0, ImmutableList.of(buffer(""), buffer(""), buffer(""))); assertContentEquals(bytes(), file); } public void testEmpty_write_noBytesBuffers_atNonZeroPosition() throws IOException { file.write(5, ImmutableList.of(buffer(""), buffer(""), buffer(""))); assertContentEquals(bytes("00000"), file); } public void testEmpty_transferFrom_fromStart_countEqualsSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 0, 6); assertEquals(6, transferred); assertContentEquals("111111", file); } public void testEmpty_transferFrom_fromStart_countLessThanSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 0, 3); assertEquals(3, transferred); assertContentEquals("111", file); } public void testEmpty_transferFrom_fromStart_countGreaterThanSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 0, 12); assertEquals(6, transferred); assertContentEquals("111111", file); } public void testEmpty_transferFrom_fromBeyondStart_countEqualsSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 4, 6); assertEquals(6, transferred); assertContentEquals("0000111111", file); } public void testEmpty_transferFrom_fromBeyondStart_countLessThanSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 4, 3); assertEquals(3, transferred); assertContentEquals("0000111", file); } public void testEmpty_transferFrom_fromBeyondStart_countGreaterThanSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 4, 12); assertEquals(6, transferred); assertContentEquals("0000111111", file); } public void testEmpty_transferFrom_fromStart_noBytes_countEqualsSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("")), 0, 0); assertEquals(0, transferred); assertContentEquals(bytes(), file); } public void testEmpty_transferFrom_fromStart_noBytes_countGreaterThanSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("")), 0, 10); assertEquals(0, transferred); assertContentEquals(bytes(), file); } public void testEmpty_transferFrom_fromBeyondStart_noBytes_countEqualsSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("")), 5, 0); assertEquals(0, transferred); assertContentEquals(bytes("00000"), file); } public void testEmpty_transferFrom_fromBeyondStart_noBytes_countGreaterThanSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("")), 5, 10); assertEquals(0, transferred); assertContentEquals(bytes("00000"), file); } public void testEmpty_transferTo() throws IOException { ByteBufferChannel channel = new ByteBufferChannel(100); assertEquals(0, file.transferTo(0, 100, channel)); } public void testEmpty_copy() throws IOException { RegularFile copy = file.copyWithoutContent(1); assertContentEquals("", copy); } public void testEmpty_truncate_toZero() throws IOException { file.truncate(0); assertContentEquals("", file); } public void testEmpty_truncate_sizeUp() throws IOException { file.truncate(10); assertContentEquals("", file); } public void testNonEmpty() throws IOException { fillContent("222222"); assertContentEquals("222222", file); } public void testNonEmpty_read_singleByte() throws IOException { fillContent("123456"); assertEquals(1, file.read(0)); assertEquals(2, file.read(1)); assertEquals(6, file.read(5)); assertEquals(-1, file.read(6)); assertEquals(-1, file.read(100)); } public void testNonEmpty_read_all_byteArray() throws IOException { fillContent("222222"); byte[] array = new byte[6]; assertEquals(6, file.read(0, array, 0, array.length)); assertArrayEquals(bytes("222222"), array); } public void testNonEmpty_read_all_singleBuffer() throws IOException { fillContent("222222"); ByteBuffer buffer = ByteBuffer.allocate(6); assertEquals(6, file.read(0, buffer)); assertBufferEquals("222222", 0, buffer); } public void testNonEmpty_read_all_multipleBuffers() throws IOException { fillContent("223334"); ByteBuffer buf1 = ByteBuffer.allocate(3); ByteBuffer buf2 = ByteBuffer.allocate(3); assertEquals(6, file.read(0, ImmutableList.of(buf1, buf2))); assertBufferEquals("223", 0, buf1); assertBufferEquals("334", 0, buf2); } public void testNonEmpty_read_all_byteArray_largerThanContent() throws IOException { fillContent("222222"); byte[] array = new byte[10]; assertEquals(6, file.read(0, array, 0, array.length)); assertArrayEquals(bytes("2222220000"), array); array = new byte[10]; assertEquals(6, file.read(0, array, 2, 6)); assertArrayEquals(bytes("0022222200"), array); } public void testNonEmpty_read_all_singleBuffer_largerThanContent() throws IOException { fillContent("222222"); ByteBuffer buffer = ByteBuffer.allocate(16); assertBufferEquals("0000000000000000", 16, buffer); assertEquals(6, file.read(0, buffer)); assertBufferEquals("2222220000000000", 10, buffer); } public void testNonEmpty_read_all_multipleBuffers_largerThanContent() throws IOException { fillContent("222222"); ByteBuffer buf1 = ByteBuffer.allocate(4); ByteBuffer buf2 = ByteBuffer.allocate(8); assertEquals(6, file.read(0, ImmutableList.of(buf1, buf2))); assertBufferEquals("2222", 0, buf1); assertBufferEquals("22000000", 6, buf2); } public void testNonEmpty_read_all_multipleBuffers_extraBuffers() throws IOException { fillContent("222222"); ByteBuffer buf1 = ByteBuffer.allocate(4); ByteBuffer buf2 = ByteBuffer.allocate(8); ByteBuffer buf3 = ByteBuffer.allocate(4); assertEquals(6, file.read(0, ImmutableList.of(buf1, buf2, buf3))); assertBufferEquals("2222", 0, buf1); assertBufferEquals("22000000", 6, buf2); assertBufferEquals("0000", 4, buf3); } public void testNonEmpty_read_partial_fromStart_byteArray() throws IOException { fillContent("222222"); byte[] array = new byte[3]; assertEquals(3, file.read(0, array, 0, array.length)); assertArrayEquals(bytes("222"), array); array = new byte[10]; assertEquals(3, file.read(0, array, 1, 3)); assertArrayEquals(bytes("0222000000"), array); } public void testNonEmpty_read_partial_fromMiddle_byteArray() throws IOException { fillContent("22223333"); byte[] array = new byte[3]; assertEquals(3, file.read(3, array, 0, array.length)); assertArrayEquals(bytes("233"), array); array = new byte[10]; assertEquals(3, file.read(3, array, 1, 3)); assertArrayEquals(bytes("0233000000"), array); } public void testNonEmpty_read_partial_fromEnd_byteArray() throws IOException { fillContent("2222222222"); byte[] array = new byte[3]; assertEquals(2, file.read(8, array, 0, array.length)); assertArrayEquals(bytes("220"), array); array = new byte[10]; assertEquals(2, file.read(8, array, 1, 3)); assertArrayEquals(bytes("0220000000"), array); } public void testNonEmpty_read_partial_fromStart_singleBuffer() throws IOException { fillContent("222222"); ByteBuffer buffer = ByteBuffer.allocate(3); assertEquals(3, file.read(0, buffer)); assertBufferEquals("222", 0, buffer); } public void testNonEmpty_read_partial_fromMiddle_singleBuffer() throws IOException { fillContent("22223333"); ByteBuffer buffer = ByteBuffer.allocate(3); assertEquals(3, file.read(3, buffer)); assertBufferEquals("233", 0, buffer); } public void testNonEmpty_read_partial_fromEnd_singleBuffer() throws IOException { fillContent("2222222222"); ByteBuffer buffer = ByteBuffer.allocate(3); assertEquals(2, file.read(8, buffer)); assertBufferEquals("220", 1, buffer); } public void testNonEmpty_read_partial_fromStart_multipleBuffers() throws IOException { fillContent("12345678"); ByteBuffer buf1 = ByteBuffer.allocate(2); ByteBuffer buf2 = ByteBuffer.allocate(2); assertEquals(4, file.read(0, ImmutableList.of(buf1, buf2))); assertBufferEquals("12", 0, buf1); assertBufferEquals("34", 0, buf2); } public void testNonEmpty_read_partial_fromMiddle_multipleBuffers() throws IOException { fillContent("12345678"); ByteBuffer buf1 = ByteBuffer.allocate(2); ByteBuffer buf2 = ByteBuffer.allocate(2); assertEquals(4, file.read(3, ImmutableList.of(buf1, buf2))); assertBufferEquals("45", 0, buf1); assertBufferEquals("67", 0, buf2); } public void testNonEmpty_read_partial_fromEnd_multipleBuffers() throws IOException { fillContent("123456789"); ByteBuffer buf1 = ByteBuffer.allocate(2); ByteBuffer buf2 = ByteBuffer.allocate(2); assertEquals(3, file.read(6, ImmutableList.of(buf1, buf2))); assertBufferEquals("78", 0, buf1); assertBufferEquals("90", 1, buf2); } public void testNonEmpty_read_fromPastEnd_byteArray() throws IOException { fillContent("123"); byte[] array = new byte[3]; assertEquals(-1, file.read(3, array, 0, array.length)); assertArrayEquals(bytes("000"), array); assertEquals(-1, file.read(3, array, 0, 2)); assertArrayEquals(bytes("000"), array); } public void testNonEmpty_read_fromPastEnd_singleBuffer() throws IOException { fillContent("123"); ByteBuffer buffer = ByteBuffer.allocate(3); file.read(3, buffer); assertBufferEquals("000", 3, buffer); } public void testNonEmpty_read_fromPastEnd_multipleBuffers() throws IOException { fillContent("123"); ByteBuffer buf1 = ByteBuffer.allocate(2); ByteBuffer buf2 = ByteBuffer.allocate(2); assertEquals(-1, file.read(6, ImmutableList.of(buf1, buf2))); assertBufferEquals("00", 2, buf1); assertBufferEquals("00", 2, buf2); } public void testNonEmpty_write_partial_fromStart_singleByte() throws IOException { fillContent("222222"); assertEquals(1, file.write(0, (byte) 1)); assertContentEquals("122222", file); } public void testNonEmpty_write_partial_fromMiddle_singleByte() throws IOException { fillContent("222222"); assertEquals(1, file.write(3, (byte) 1)); assertContentEquals("222122", file); } public void testNonEmpty_write_partial_fromEnd_singleByte() throws IOException { fillContent("222222"); assertEquals(1, file.write(6, (byte) 1)); assertContentEquals("2222221", file); } public void testNonEmpty_write_partial_fromStart_byteArray() throws IOException { fillContent("222222"); assertEquals(3, file.write(0, bytes("111"), 0, 3)); assertContentEquals("111222", file); assertEquals(2, file.write(0, bytes("333333"), 0, 2)); assertContentEquals("331222", file); } public void testNonEmpty_write_partial_fromMiddle_byteArray() throws IOException { fillContent("22222222"); assertEquals(3, file.write(3, buffer("111"))); assertContentEquals("22211122", file); assertEquals(2, file.write(5, bytes("333333"), 1, 2)); assertContentEquals("22211332", file); } public void testNonEmpty_write_partial_fromBeforeEnd_byteArray() throws IOException { fillContent("22222222"); assertEquals(3, file.write(6, bytes("111"), 0, 3)); assertContentEquals("222222111", file); assertEquals(2, file.write(8, bytes("333333"), 2, 2)); assertContentEquals("2222221133", file); } public void testNonEmpty_write_partial_fromEnd_byteArray() throws IOException { fillContent("222222"); assertEquals(3, file.write(6, bytes("111"), 0, 3)); assertContentEquals("222222111", file); assertEquals(2, file.write(9, bytes("333333"), 3, 2)); assertContentEquals("22222211133", file); } public void testNonEmpty_write_partial_fromPastEnd_byteArray() throws IOException { fillContent("222222"); assertEquals(3, file.write(8, bytes("111"), 0, 3)); assertContentEquals("22222200111", file); assertEquals(2, file.write(13, bytes("333333"), 4, 2)); assertContentEquals("222222001110033", file); } public void testNonEmpty_write_partial_fromStart_singleBuffer() throws IOException { fillContent("222222"); assertEquals(3, file.write(0, buffer("111"))); assertContentEquals("111222", file); } public void testNonEmpty_write_partial_fromMiddle_singleBuffer() throws IOException { fillContent("22222222"); assertEquals(3, file.write(3, buffer("111"))); assertContentEquals("22211122", file); } public void testNonEmpty_write_partial_fromBeforeEnd_singleBuffer() throws IOException { fillContent("22222222"); assertEquals(3, file.write(6, buffer("111"))); assertContentEquals("222222111", file); } public void testNonEmpty_write_partial_fromEnd_singleBuffer() throws IOException { fillContent("222222"); assertEquals(3, file.write(6, buffer("111"))); assertContentEquals("222222111", file); } public void testNonEmpty_write_partial_fromPastEnd_singleBuffer() throws IOException { fillContent("222222"); assertEquals(3, file.write(8, buffer("111"))); assertContentEquals("22222200111", file); } public void testNonEmpty_write_partial_fromStart_multipleBuffers() throws IOException { fillContent("222222"); assertEquals(4, file.write(0, buffers("11", "33"))); assertContentEquals("113322", file); } public void testNonEmpty_write_partial_fromMiddle_multipleBuffers() throws IOException { fillContent("22222222"); assertEquals(4, file.write(2, buffers("11", "33"))); assertContentEquals("22113322", file); } public void testNonEmpty_write_partial_fromBeforeEnd_multipleBuffers() throws IOException { fillContent("22222222"); assertEquals(6, file.write(6, buffers("111", "333"))); assertContentEquals("222222111333", file); } public void testNonEmpty_write_partial_fromEnd_multipleBuffers() throws IOException { fillContent("222222"); assertEquals(6, file.write(6, buffers("111", "333"))); assertContentEquals("222222111333", file); } public void testNonEmpty_write_partial_fromPastEnd_multipleBuffers() throws IOException { fillContent("222222"); assertEquals(4, file.write(10, buffers("11", "33"))); assertContentEquals("22222200001133", file); } public void testNonEmpty_write_overwrite_sameLength() throws IOException { fillContent("2222"); assertEquals(4, file.write(0, buffer("1234"))); assertContentEquals("1234", file); } public void testNonEmpty_write_overwrite_greaterLength() throws IOException { fillContent("2222"); assertEquals(8, file.write(0, buffer("12345678"))); assertContentEquals("12345678", file); } public void testNonEmpty_transferTo_fromStart_countEqualsSize() throws IOException { fillContent("123456"); ByteBufferChannel channel = new ByteBufferChannel(10); assertEquals(6, file.transferTo(0, 6, channel)); assertBufferEquals("1234560000", 4, channel.buffer()); } public void testNonEmpty_transferTo_fromStart_countLessThanSize() throws IOException { fillContent("123456"); ByteBufferChannel channel = new ByteBufferChannel(10); assertEquals(4, file.transferTo(0, 4, channel)); assertBufferEquals("1234000000", 6, channel.buffer()); } public void testNonEmpty_transferTo_fromMiddle_countEqualsSize() throws IOException { fillContent("123456"); ByteBufferChannel channel = new ByteBufferChannel(10); assertEquals(2, file.transferTo(4, 6, channel)); assertBufferEquals("5600000000", 8, channel.buffer()); } public void testNonEmpty_transferTo_fromMiddle_countLessThanSize() throws IOException { fillContent("12345678"); ByteBufferChannel channel = new ByteBufferChannel(10); assertEquals(4, file.transferTo(3, 4, channel)); assertBufferEquals("4567000000", 6, channel.buffer()); } public void testNonEmpty_transferFrom_toStart_countEqualsSrcSize() throws IOException { fillContent("22222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("11111")); assertEquals(5, file.transferFrom(channel, 0, 5)); assertContentEquals("11111222", file); } public void testNonEmpty_transferFrom_toStart_countLessThanSrcSize() throws IOException { fillContent("22222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("11111")); assertEquals(3, file.transferFrom(channel, 0, 3)); assertContentEquals("11122222", file); } public void testNonEmpty_transferFrom_toStart_countGreaterThanSrcSize() throws IOException { fillContent("22222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("11111")); assertEquals(5, file.transferFrom(channel, 0, 10)); assertContentEquals("11111222", file); } public void testNonEmpty_transferFrom_toMiddle_countEqualsSrcSize() throws IOException { fillContent("22222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("1111")); assertEquals(4, file.transferFrom(channel, 2, 4)); assertContentEquals("22111122", file); } public void testNonEmpty_transferFrom_toMiddle_countLessThanSrcSize() throws IOException { fillContent("22222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("11111")); assertEquals(3, file.transferFrom(channel, 2, 3)); assertContentEquals("22111222", file); } public void testNonEmpty_transferFrom_toMiddle_countGreaterThanSrcSize() throws IOException { fillContent("22222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("1111")); assertEquals(4, file.transferFrom(channel, 2, 100)); assertContentEquals("22111122", file); } public void testNonEmpty_transferFrom_toMiddle_transferGoesBeyondContentSize() throws IOException { fillContent("222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("111111")); assertEquals(6, file.transferFrom(channel, 4, 6)); assertContentEquals("2222111111", file); } public void testNonEmpty_transferFrom_toEnd() throws IOException { fillContent("222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("111111")); assertEquals(6, file.transferFrom(channel, 6, 6)); assertContentEquals("222222111111", file); } public void testNonEmpty_transferFrom_toPastEnd() throws IOException { fillContent("222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("111111")); assertEquals(6, file.transferFrom(channel, 10, 6)); assertContentEquals("2222220000111111", file); } public void testNonEmpty_transferFrom_hugeOverestimateCount() throws IOException { fillContent("222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("111111")); assertEquals(6, file.transferFrom(channel, 6, 1024 * 1024 * 10)); assertContentEquals("222222111111", file); } public void testNonEmpty_copy() throws IOException { fillContent("123456"); RegularFile copy = file.copyWithoutContent(1); file.copyContentTo(copy); assertContentEquals("123456", copy); } public void testNonEmpty_copy_multipleTimes() throws IOException { fillContent("123456"); RegularFile copy = file.copyWithoutContent(1); file.copyContentTo(copy); RegularFile copy2 = copy.copyWithoutContent(2); copy.copyContentTo(copy2); assertContentEquals("123456", copy); } public void testNonEmpty_truncate_toZero() throws IOException { fillContent("123456"); file.truncate(0); assertContentEquals("", file); } public void testNonEmpty_truncate_partial() throws IOException { fillContent("12345678"); file.truncate(5); assertContentEquals("12345", file); } public void testNonEmpty_truncate_sizeUp() throws IOException { fillContent("123456"); file.truncate(12); assertContentEquals("123456", file); } public void testDeletedStoreRemainsUsableWhileOpen() throws IOException { byte[] bytes = bytes("1234567890"); file.write(0, bytes, 0, bytes.length); file.opened(); file.opened(); file.deleted(); assertContentEquals(bytes, file); byte[] moreBytes = bytes("1234"); file.write(bytes.length, moreBytes, 0, 4); byte[] totalBytes = concat(bytes, bytes("1234")); assertContentEquals(totalBytes, file); file.closed(); assertContentEquals(totalBytes, file); file.closed(); // don't check anything else; no guarantee of what if anything will happen once the file is // deleted and completely closed } private static void assertBufferEquals(String expected, ByteBuffer actual) { assertEquals(expected.length(), actual.capacity()); assertArrayEquals(bytes(expected), actual.array()); } private static void assertBufferEquals(String expected, int remaining, ByteBuffer actual) { assertBufferEquals(expected, actual); assertEquals(remaining, actual.remaining()); } private static void assertContentEquals(String expected, RegularFile actual) { assertContentEquals(bytes(expected), actual); } protected static void assertContentEquals(byte[] expected, RegularFile actual) { assertEquals(expected.length, actual.sizeWithoutLocking()); byte[] actualBytes = new byte[(int) actual.sizeWithoutLocking()]; actual.read(0, ByteBuffer.wrap(actualBytes)); assertArrayEquals(expected, actualBytes); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/TestAttributeProvider.java000066400000000000000000000130271265745405500301150ustar00rootroot00000000000000/* * 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.checkNotNull; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileTime; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; /** * @author Colin Decker */ public final class TestAttributeProvider extends AttributeProvider { private static final ImmutableSet ATTRIBUTES = ImmutableSet.of("foo", "bar", "baz"); @Override public String name() { return "test"; } @Override public ImmutableSet inherits() { return ImmutableSet.of("basic"); } @Override public ImmutableSet fixedAttributes() { return ATTRIBUTES; } @Override public ImmutableMap defaultValues(Map userDefaults) { Map result = new HashMap<>(); Long bar = 0L; Integer baz = 1; if (userDefaults.containsKey("test:bar")) { bar = checkType("test", "bar", userDefaults.get("test:bar"), Number.class).longValue(); } if (userDefaults.containsKey("test:baz")) { baz = checkType("test", "baz", userDefaults.get("test:baz"), Integer.class); } result.put("test:bar", bar); result.put("test:baz", baz); return ImmutableMap.copyOf(result); } @Override public void set(File file, String view, String attribute, Object value, boolean create) { switch (attribute) { case "bar": checkNotCreate(view, attribute, create); file.setAttribute( "test", "bar", checkType(view, attribute, value, Number.class).longValue()); break; case "baz": file.setAttribute("test", "baz", checkType(view, attribute, value, Integer.class)); break; default: throw unsettable(view, attribute); } } @Override public Object get(File file, String attribute) { if (attribute.equals("foo")) { return "hello"; } return file.getAttribute("test", attribute); } @Override public Class viewType() { return TestAttributeView.class; } @Override public TestAttributeView view( FileLookup lookup, ImmutableMap inheritedViews) { return new View(lookup, (BasicFileAttributeView) inheritedViews.get("basic")); } @Override public Class attributesType() { return TestAttributes.class; } @Override public TestAttributes readAttributes(File file) { return new Attributes(file); } static final class View implements TestAttributeView { private final FileLookup lookup; private final BasicFileAttributeView basicView; public View(FileLookup lookup, BasicFileAttributeView basicView) { this.lookup = checkNotNull(lookup); this.basicView = checkNotNull(basicView); } @Override public String name() { return "test"; } @Override public Attributes readAttributes() throws IOException { return new Attributes(lookup.lookup()); } @Override public void setTimes( @Nullable FileTime lastModifiedTime, @Nullable FileTime lastAccessTime, @Nullable FileTime createTime) throws IOException { basicView.setTimes(lastModifiedTime, lastAccessTime, createTime); } @Override public void setBar(long bar) throws IOException { lookup.lookup().setAttribute("test", "bar", bar); } @Override public void setBaz(int baz) throws IOException { lookup.lookup().setAttribute("test", "baz", baz); } } static final class Attributes implements TestAttributes { private final Long bar; private final Integer baz; public Attributes(File file) { this.bar = (Long) file.getAttribute("test", "bar"); this.baz = (Integer) file.getAttribute("test", "baz"); } @Override public String foo() { return "hello"; } @Override public long bar() { return bar; } @Override public int baz() { return baz; } // BasicFileAttributes is just implemented here because readAttributes requires a subtype of // BasicFileAttributes -- methods are not implemented @Override public FileTime lastModifiedTime() { return null; } @Override public FileTime lastAccessTime() { return null; } @Override public FileTime creationTime() { return null; } @Override public boolean isRegularFile() { return false; } @Override public boolean isDirectory() { return false; } @Override public boolean isSymbolicLink() { return false; } @Override public boolean isOther() { return false; } @Override public long size() { return 0; } @Override public Object fileKey() { return null; } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/TestAttributeView.java000066400000000000000000000017001265745405500272300ustar00rootroot00000000000000/* * 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 java.io.IOException; import java.nio.file.attribute.BasicFileAttributeView; /** * @author Colin Decker */ public interface TestAttributeView extends BasicFileAttributeView { TestAttributes readAttributes() throws IOException; void setBar(long bar) throws IOException; void setBaz(int baz) throws IOException; } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/TestAttributes.java000066400000000000000000000014711265745405500265650ustar00rootroot00000000000000/* * 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 java.nio.file.attribute.BasicFileAttributes; /** * @author Colin Decker */ public interface TestAttributes extends BasicFileAttributes { String foo(); long bar(); int baz(); } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/TestUtils.java000066400000000000000000000112741265745405500255410ustar00rootroot00000000000000/* * 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 java.nio.file.LinkOption.NOFOLLOW_LINKS; import static org.junit.Assert.assertFalse; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; /** * @author Colin Decker */ public final class TestUtils { private TestUtils() {} public static byte[] bytes(int... bytes) { byte[] result = new byte[bytes.length]; for (int i = 0; i < bytes.length; i++) { result[i] = (byte) bytes[i]; } return result; } public static byte[] bytes(String bytes) { byte[] result = new byte[bytes.length()]; for (int i = 0; i < bytes.length(); i++) { String digit = bytes.substring(i, i + 1); result[i] = Byte.parseByte(digit); } return result; } public static byte[] preFilledBytes(int length, int fillValue) { byte[] result = new byte[length]; Arrays.fill(result, (byte) fillValue); return result; } public static byte[] preFilledBytes(int length) { byte[] bytes = new byte[length]; for (int i = 0; i < length; i++) { bytes[i] = (byte) i; } return bytes; } public static ByteBuffer buffer(String bytes) { return ByteBuffer.wrap(bytes(bytes)); } public static Iterable buffers(String... bytes) { List result = new ArrayList<>(); for (String b : bytes) { result.add(buffer(b)); } return result; } /** * Returns a number of permutations of the given path that should all locate the same file. */ public static Iterable permutations(Path path) throws IOException { Path workingDir = path.getFileSystem().getPath("").toRealPath(); boolean directory = Files.isDirectory(path); Set results = new HashSet<>(); results.add(path); if (path.isAbsolute()) { results.add(workingDir.relativize(path)); } else { results.add(workingDir.resolve(path)); } if (directory) { for (Path p : ImmutableList.copyOf(results)) { results.add(p.resolve(".")); results.add(p.resolve(".").resolve(".")); Path fileName = p.getFileName(); if (fileName != null && !fileName.toString().equals(".") && !fileName.toString().equals("..")) { results.add(p.resolve("..").resolve(fileName)); results.add( p.resolve("..").resolve(".").resolve(fileName)); results.add( p.resolve("..").resolve(".").resolve(fileName).resolve(".")); results.add( p.resolve(".").resolve("..").resolve(".").resolve(fileName)); } } try (DirectoryStream stream = Files.newDirectoryStream(path)) { for (Path child : stream) { if (Files.isDirectory(child, NOFOLLOW_LINKS)) { Path childName = child.getFileName(); for (Path p : ImmutableList.copyOf(results)) { results.add(p.resolve(childName).resolve("..")); results.add( p.resolve(childName).resolve(".").resolve(".").resolve("..")); results.add( p.resolve(childName).resolve("..").resolve(".")); results.add( p.resolve(childName).resolve("..").resolve(childName).resolve(".").resolve("..")); } break; // no need to add more than one child } } } } return results; } // equivalent to the Junit 4.11 method. public static void assertNotEquals(Object unexpected, Object actual) { assertFalse( "Values should be different. Actual: " + actual, Objects.equals(unexpected, actual)); } static RegularFile regularFile(int size) { RegularFile file = RegularFile.create(0, new HeapDisk(8096, 1000, 1000)); try { file.write(0, new byte[size], 0, size); return file; } catch (IOException e) { throw new AssertionError(e); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/UnixAttributeProviderTest.java000066400000000000000000000065511265745405500307650ustar00rootroot00000000000000/* * 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.jimfs.UserLookupService.createGroupPrincipal; import static com.google.common.jimfs.UserLookupService.createUserPrincipal; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFilePermissions; import java.util.Set; /** * Tests for {@link UnixAttributeProvider}. * * @author Colin Decker */ @RunWith(JUnit4.class) @SuppressWarnings("OctalInteger") public class UnixAttributeProviderTest extends AbstractAttributeProviderTest { @Override protected UnixAttributeProvider createProvider() { return new UnixAttributeProvider(); } @Override protected Set createInheritedProviders() { return ImmutableSet.of( new BasicAttributeProvider(), new OwnerAttributeProvider(), new PosixAttributeProvider()); } @Test public void testInitialAttributes() { // unix provider relies on other providers to set their initial attributes file.setAttribute("owner", "owner", createUserPrincipal("foo")); file.setAttribute("posix", "group", createGroupPrincipal("bar")); file.setAttribute( "posix", "permissions", ImmutableSet.copyOf(PosixFilePermissions.fromString("rw-r--r--"))); // these are pretty much meaningless here since they aren't properties this // file system actually has, so don't really care about the exact value of these assertThat(provider.get(file, "uid")).isInstanceOf(Integer.class); assertThat(provider.get(file, "gid")).isInstanceOf(Integer.class); assertThat(provider.get(file, "rdev")).isEqualTo(0L); assertThat(provider.get(file, "dev")).isEqualTo(1L); assertThat(provider.get(file, "ino")).isInstanceOf(Integer.class); // these have logical origins in attributes from other views assertThat(provider.get(file, "mode")).isEqualTo(0644); // rw-r--r-- assertThat(provider.get(file, "ctime")).isEqualTo(FileTime.fromMillis(file.getCreationTime())); // this is based on a property this file system does actually have assertThat(provider.get(file, "nlink")).isEqualTo(1); file.incrementLinkCount(); assertThat(provider.get(file, "nlink")).isEqualTo(2); file.decrementLinkCount(); assertThat(provider.get(file, "nlink")).isEqualTo(1); } @Test public void testSet() { assertSetFails("unix:uid", 1); assertSetFails("unix:gid", 1); assertSetFails("unix:rdev", 1L); assertSetFails("unix:dev", 1L); assertSetFails("unix:ino", 1); assertSetFails("unix:mode", 1); assertSetFails("unix:ctime", 1L); assertSetFails("unix:nlink", 1); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/UnixPathTypeTest.java000066400000000000000000000075501265745405500270450ustar00rootroot00000000000000/* * 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.jimfs.PathTypeTest.assertParseResult; import static com.google.common.jimfs.PathTypeTest.assertUriRoundTripsCorrectly; import static com.google.common.jimfs.PathTypeTest.fileSystemUri; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.net.URI; import java.nio.file.InvalidPathException; /** * Tests for {@link UnixPathType}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class UnixPathTypeTest { @Test public void testUnix() { PathType unix = PathType.unix(); assertThat(unix.getSeparator()).isEqualTo("/"); assertThat(unix.getOtherSeparators()).isEqualTo(""); // "//foo/bar" is what will be passed to parsePath if "/", "foo", "bar" is passed to getPath PathType.ParseResult path = unix.parsePath("//foo/bar"); assertParseResult(path, "/", "foo", "bar"); assertThat(unix.toString(path.root(), path.names())).isEqualTo("/foo/bar"); PathType.ParseResult path2 = unix.parsePath("foo/bar/"); assertParseResult(path2, null, "foo", "bar"); assertThat(unix.toString(path2.root(), path2.names())).isEqualTo("foo/bar"); } @Test public void testUnix_toUri() { URI fileUri = PathType.unix().toUri(fileSystemUri, "/", ImmutableList.of("foo", "bar"), false); assertThat(fileUri.toString()).isEqualTo("jimfs://foo/foo/bar"); assertThat(fileUri.getPath()).isEqualTo("/foo/bar"); URI directoryUri = PathType.unix().toUri(fileSystemUri, "/", ImmutableList.of("foo", "bar"), true); assertThat(directoryUri.toString()).isEqualTo("jimfs://foo/foo/bar/"); assertThat(directoryUri.getPath()).isEqualTo("/foo/bar/"); URI rootUri = PathType.unix().toUri(fileSystemUri, "/", ImmutableList.of(), true); assertThat(rootUri.toString()).isEqualTo("jimfs://foo/"); assertThat(rootUri.getPath()).isEqualTo("/"); } @Test public void testUnix_toUri_escaping() { URI uri = PathType.unix().toUri(fileSystemUri, "/", ImmutableList.of("foo bar"), false); assertThat(uri.toString()).isEqualTo("jimfs://foo/foo%20bar"); assertThat(uri.getRawPath()).isEqualTo("/foo%20bar"); assertThat(uri.getPath()).isEqualTo("/foo bar"); } @Test public void testUnix_uriRoundTrips() { assertUriRoundTripsCorrectly(PathType.unix(), "/"); assertUriRoundTripsCorrectly(PathType.unix(), "/foo"); assertUriRoundTripsCorrectly(PathType.unix(), "/foo/bar/baz"); assertUriRoundTripsCorrectly(PathType.unix(), "/foo/bar baz/one/two"); assertUriRoundTripsCorrectly(PathType.unix(), "/foo bar"); assertUriRoundTripsCorrectly(PathType.unix(), "/foo bar/"); assertUriRoundTripsCorrectly(PathType.unix(), "/foo bar/baz/one"); } @Test public void testUnix_illegalCharacters() { try { PathType.unix().parsePath("/foo/bar\0"); fail(); } catch (InvalidPathException expected) { assertEquals(8, expected.getIndex()); } try { PathType.unix().parsePath("/\u00001/foo"); fail(); } catch (InvalidPathException expected) { assertEquals(1, expected.getIndex()); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/UrlTest.java000066400000000000000000000105611265745405500252010ustar00rootroot00000000000000/* * Copyright 2015 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.StandardSystemProperty.LINE_SEPARATOR; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; import com.google.common.io.Resources; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.FileTime; /** * Tests that {@link URL} instances can be created and used from jimfs URIs. * * @author Colin Decker */ @RunWith(JUnit4.class) public class UrlTest { private final FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); private Path path = fs.getPath("foo"); @Test public void creatUrl() throws MalformedURLException { URL url = path.toUri().toURL(); assertThat(url).isNotNull(); } @Test public void readFromUrl() throws IOException { Files.write(path, ImmutableList.of("Hello World"), UTF_8); URL url = path.toUri().toURL(); assertThat(Resources.asCharSource(url, UTF_8).read()) .isEqualTo("Hello World" + LINE_SEPARATOR.value()); } @Test public void readDirectoryContents() throws IOException { Files.createDirectory(path); Files.createFile(path.resolve("a.txt")); Files.createFile(path.resolve("b.txt")); Files.createDirectory(path.resolve("c")); URL url = path.toUri().toURL(); assertThat(Resources.asCharSource(url, UTF_8).read()) .isEqualTo("a.txt\nb.txt\nc\n"); } @Test public void headers() throws IOException { byte[] bytes = {1, 2, 3}; Files.write(path, bytes); FileTime lastModified = Files.getLastModifiedTime(path); URL url = path.toUri().toURL(); URLConnection conn = url.openConnection(); // read header fields directly assertThat(conn.getHeaderFields()).containsEntry("content-length", ImmutableList.of("3")); assertThat(conn.getHeaderFields()) .containsEntry("content-type", ImmutableList.of("application/octet-stream")); if (lastModified != null) { assertThat(conn.getHeaderFields()).containsKey("last-modified"); assertThat(conn.getHeaderFields()).hasSize(3); } else { assertThat(conn.getHeaderFields()).hasSize(2); } // use the specific methods for reading the expected headers assertThat(conn.getContentLengthLong()).isEqualTo(Files.size(path)); assertThat(conn.getContentType()).isEqualTo("application/octet-stream"); if (lastModified != null) { // The HTTP date format does not include milliseconds, which means that the last modified time // returned from the connection may not be exactly the same as that of the file system itself. // The difference should less than 1000ms though, and should never be greater. long difference = lastModified.toMillis() - conn.getLastModified(); assertThat(difference).isIn(Range.closedOpen(0L, 1000L)); } else { assertThat(conn.getLastModified()).isEqualTo(0L); } } @Test public void contentType() throws IOException { path = fs.getPath("foo.txt"); Files.write(path, ImmutableList.of("Hello World"), UTF_8); URL url = path.toUri().toURL(); URLConnection conn = url.openConnection(); // Should be text/plain, but this is entirely dependent on the installed FileTypeDetectors String detectedContentType = Files.probeContentType(path); if (detectedContentType == null) { assertThat(conn.getContentType()).isEqualTo("application/octet-stream"); } else { assertThat(conn.getContentType()).isEqualTo(detectedContentType); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/UserDefinedAttributeProviderTest.java000066400000000000000000000076261265745405500322430ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.attribute.UserDefinedFileAttributeView; import java.util.Arrays; import java.util.Set; /** * Tests for {@link UserDefinedAttributeProvider}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class UserDefinedAttributeProviderTest extends AbstractAttributeProviderTest { @Override protected UserDefinedAttributeProvider createProvider() { return new UserDefinedAttributeProvider(); } @Override protected Set createInheritedProviders() { return ImmutableSet.of(); } @Test public void testInitialAttributes() { // no initial attributes assertThat(ImmutableList.copyOf(file.getAttributeKeys())).isEmpty(); assertThat(provider.attributes(file)).isEmpty(); } @Test public void testGettingAndSetting() { byte[] bytes = {0, 1, 2, 3}; provider.set(file, "user", "one", bytes, false); provider.set(file, "user", "two", ByteBuffer.wrap(bytes), false); byte[] one = (byte[]) provider.get(file, "one"); byte[] two = (byte[]) provider.get(file, "two"); assertThat(Arrays.equals(one, bytes)).isTrue(); assertThat(Arrays.equals(two, bytes)).isTrue(); assertSetFails("foo", "hello"); assertThat(provider.attributes(file)).containsExactly("one", "two"); } @Test public void testSetOnCreate() { assertSetFailsOnCreate("anything", new byte[0]); } @Test public void testView() throws IOException { UserDefinedFileAttributeView view = provider.view(fileLookup(), NO_INHERITED_VIEWS); assertNotNull(view); assertThat(view.name()).isEqualTo("user"); assertThat(view.list()).isEmpty(); byte[] b1 = {0, 1, 2}; byte[] b2 = {0, 1, 2, 3, 4}; view.write("b1", ByteBuffer.wrap(b1)); view.write("b2", ByteBuffer.wrap(b2)); assertThat(view.list()).containsAllOf("b1", "b2"); assertThat(file.getAttributeKeys()).containsExactly("user:b1", "user:b2"); assertThat(view.size("b1")).isEqualTo(3); assertThat(view.size("b2")).isEqualTo(5); ByteBuffer buf1 = ByteBuffer.allocate(view.size("b1")); ByteBuffer buf2 = ByteBuffer.allocate(view.size("b2")); view.read("b1", buf1); view.read("b2", buf2); assertThat(Arrays.equals(b1, buf1.array())).isTrue(); assertThat(Arrays.equals(b2, buf2.array())).isTrue(); view.delete("b2"); assertThat(view.list()).containsExactly("b1"); assertThat(file.getAttributeKeys()).containsExactly("user:b1"); try { view.size("b2"); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("not set"); } try { view.read("b2", ByteBuffer.allocate(10)); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("not set"); } view.write("b1", ByteBuffer.wrap(b2)); assertThat(view.size("b1")).isEqualTo(5); view.delete("b2"); // succeeds } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/UserLookupServiceTest.java000066400000000000000000000043211265745405500300650ustar00rootroot00000000000000/* * 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.truth.Truth.assertThat; import static org.junit.Assert.fail; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.attribute.GroupPrincipal; import java.nio.file.attribute.UserPrincipal; import java.nio.file.attribute.UserPrincipalLookupService; import java.nio.file.attribute.UserPrincipalNotFoundException; /** * Tests for {@link UserLookupService}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class UserLookupServiceTest { @Test public void testUserLookupService() throws IOException { UserPrincipalLookupService service = new UserLookupService(true); UserPrincipal bob1 = service.lookupPrincipalByName("bob"); UserPrincipal bob2 = service.lookupPrincipalByName("bob"); UserPrincipal alice = service.lookupPrincipalByName("alice"); assertThat(bob1).isEqualTo(bob2); assertThat(bob1).isNotEqualTo(alice); GroupPrincipal group1 = service.lookupPrincipalByGroupName("group"); GroupPrincipal group2 = service.lookupPrincipalByGroupName("group"); GroupPrincipal foo = service.lookupPrincipalByGroupName("foo"); assertThat(group1).isEqualTo(group2); assertThat(group1).isNotEqualTo(foo); } @Test public void testServiceNotSupportingGroups() throws IOException { UserPrincipalLookupService service = new UserLookupService(false); try { service.lookupPrincipalByGroupName("group"); fail(); } catch (UserPrincipalNotFoundException expected) { assertThat(expected.getName()).isEqualTo("group"); } } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/WatchServiceConfigurationTest.java000066400000000000000000000050241265745405500315540ustar00rootroot00000000000000/* * Copyright 2016 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.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.truth.Truth; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.IOException; import java.nio.file.WatchService; import java.util.concurrent.TimeUnit; /** * Tests for {@link WatchServiceConfiguration}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class WatchServiceConfigurationTest { private JimfsFileSystem fs; @Before public void setUp() { // kind of putting the cart before the horse maybe, but it's the easiest way to get valid // instances of both a FileSystemView and a PathService fs = (JimfsFileSystem) Jimfs.newFileSystem(); } @After public void tearDown() throws IOException { fs.close(); fs = null; } @Test public void testPollingConfig() { WatchServiceConfiguration polling = WatchServiceConfiguration.polling(50, MILLISECONDS); WatchService watchService = polling.newWatchService(fs.getDefaultView(), fs.getPathService()); assertThat(watchService).isInstanceOf(PollingWatchService.class); PollingWatchService pollingWatchService = (PollingWatchService) watchService; assertThat(pollingWatchService.interval).isEqualTo(50); assertThat(pollingWatchService.timeUnit).isEqualTo(MILLISECONDS); } @Test public void testDefaultConfig() { WatchService watchService = WatchServiceConfiguration.DEFAULT .newWatchService(fs.getDefaultView(), fs.getPathService()); assertThat(watchService).isInstanceOf(PollingWatchService.class); PollingWatchService pollingWatchService = (PollingWatchService) watchService; assertThat(pollingWatchService.interval).isEqualTo(5); assertThat(pollingWatchService.timeUnit).isEqualTo(SECONDS); } } jimfs-1.1/jimfs/src/test/java/com/google/common/jimfs/WindowsPathTypeTest.java000066400000000000000000000170421265745405500275510ustar00rootroot00000000000000/* * 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.jimfs.PathType.windows; import static com.google.common.jimfs.PathTypeTest.assertParseResult; import static com.google.common.jimfs.PathTypeTest.assertUriRoundTripsCorrectly; import static com.google.common.jimfs.PathTypeTest.fileSystemUri; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.net.URI; import java.nio.file.InvalidPathException; /** * Tests for {@link WindowsPathType}. * * @author Colin Decker */ @RunWith(JUnit4.class) public class WindowsPathTypeTest { @Test public void testWindows() { PathType windows = PathType.windows(); assertThat(windows.getSeparator()).isEqualTo("\\"); assertThat(windows.getOtherSeparators()).isEqualTo("/"); // "C:\\foo\bar" results from "C:\", "foo", "bar" passed to getPath PathType.ParseResult path = windows.parsePath("C:\\\\foo\\bar"); assertParseResult(path, "C:\\", "foo", "bar"); assertThat(windows.toString(path.root(), path.names())).isEqualTo("C:\\foo\\bar"); PathType.ParseResult path2 = windows.parsePath("foo/bar/"); assertParseResult(path2, null, "foo", "bar"); assertThat(windows.toString(path2.root(), path2.names())).isEqualTo("foo\\bar"); PathType.ParseResult path3 = windows.parsePath("hello world/foo/bar"); assertParseResult(path3, null, "hello world", "foo", "bar"); assertThat(windows.toString(null, path3.names())).isEqualTo("hello world\\foo\\bar"); } @Test public void testWindows_relativePathsWithDriveRoot_unsupported() { try { windows().parsePath("C:"); fail(); } catch (InvalidPathException expected) { } try { windows().parsePath("C:foo\\bar"); fail(); } catch (InvalidPathException expected) { } } @Test public void testWindows_absolutePathOnCurrentDrive_unsupported() { try { windows().parsePath("\\foo\\bar"); fail(); } catch (InvalidPathException expected) { } try { windows().parsePath("\\"); fail(); } catch (InvalidPathException expected) { } } @Test public void testWindows_uncPaths() { PathType windows = PathType.windows(); PathType.ParseResult path = windows.parsePath("\\\\host\\share"); assertParseResult(path, "\\\\host\\share\\"); path = windows.parsePath("\\\\HOST\\share\\foo\\bar"); assertParseResult(path, "\\\\HOST\\share\\", "foo", "bar"); try { windows.parsePath("\\\\"); fail(); } catch (InvalidPathException expected) { assertThat(expected.getInput()).isEqualTo("\\\\"); assertThat(expected.getReason()).isEqualTo("UNC path is missing hostname"); } try { windows.parsePath("\\\\host"); fail(); } catch (InvalidPathException expected) { assertThat(expected.getInput()).isEqualTo("\\\\host"); assertThat(expected.getReason()).isEqualTo("UNC path is missing sharename"); } try { windows.parsePath("\\\\host\\"); fail(); } catch (InvalidPathException expected) { assertThat(expected.getInput()).isEqualTo("\\\\host\\"); assertThat(expected.getReason()).isEqualTo("UNC path is missing sharename"); } try { windows.parsePath("//host"); fail(); } catch (InvalidPathException expected) { assertThat(expected.getInput()).isEqualTo("//host"); assertThat(expected.getReason()).isEqualTo("UNC path is missing sharename"); } } @Test public void testWindows_illegalNames() { try { windows().parsePath("fooof(), true); assertThat(rootUri.toString()).isEqualTo("jimfs://foo/C:/"); assertThat(rootUri.getPath()).isEqualTo("/C:/"); } @Test public void testWindows_toUri_unc() { URI fileUri = PathType.windows() .toUri(fileSystemUri, "\\\\host\\share\\", ImmutableList.of("foo", "bar"), false); assertThat(fileUri.toString()).isEqualTo("jimfs://foo//host/share/foo/bar"); assertThat(fileUri.getPath()).isEqualTo("//host/share/foo/bar"); URI rootUri = PathType.windows() .toUri(fileSystemUri, "\\\\host\\share\\", ImmutableList.of(), true); assertThat(rootUri.toString()).isEqualTo("jimfs://foo//host/share/"); assertThat(rootUri.getPath()).isEqualTo("//host/share/"); } @Test public void testWindows_toUri_escaping() { URI uri = PathType.windows() .toUri(fileSystemUri, "C:\\", ImmutableList.of("Users", "foo", "My Documents"), true); assertThat(uri.toString()).isEqualTo("jimfs://foo/C:/Users/foo/My%20Documents/"); assertThat(uri.getRawPath()).isEqualTo("/C:/Users/foo/My%20Documents/"); assertThat(uri.getPath()).isEqualTo("/C:/Users/foo/My Documents/"); } @Test public void testWindows_uriRoundTrips_normal() { assertUriRoundTripsCorrectly(PathType.windows(), "C:\\"); assertUriRoundTripsCorrectly(PathType.windows(), "C:\\foo"); assertUriRoundTripsCorrectly(PathType.windows(), "C:\\foo\\bar\\baz"); assertUriRoundTripsCorrectly(PathType.windows(), "C:\\Users\\foo\\My Documents\\"); assertUriRoundTripsCorrectly(PathType.windows(), "C:\\foo bar"); assertUriRoundTripsCorrectly(PathType.windows(), "C:\\foo bar\\baz"); } @Test public void testWindows_uriRoundTrips_unc() { assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share"); assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\"); assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\foo"); assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\foo\\bar\\baz"); assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\Users\\foo\\My Documents\\"); assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\foo bar"); assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\foo bar\\baz"); } } jimfs-1.1/pom.xml000066400000000000000000000161621265745405500140230ustar00rootroot00000000000000 4.0.0 org.sonatype.oss oss-parent 7 com.google.jimfs jimfs-parent pom 1.1 jimfs Jimfs Parent Jimfs is an in-memory implementation of Java 7's java.nio.file abstract file system API. https://github.com/google/jimfs 2013 Google Inc. http://www.google.com/ The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo cgdecker Colin Decker cgdecker@google.com Google Inc. http://www.google.com/ owner developer -5 http://github.com/google/jimfs/ scm:git:git://github.com/google/jimfs.git scm:git:ssh://git@github.com/google/jimfs.git v1.1 GitHub Issues http://github.com/google/jimfs/issues 3.0.3 UTF-8 1.7 18.0 true com.google.guava guava ${guava.version} com.ibm.icu icu4j 51.2 com.google.auto.service auto-service 1.0-rc2 com.google.code.findbugs jsr305 2.0.1 junit junit 4.12 test com.google.guava guava-testlib ${guava.version} test com.google.truth truth 0.27 test maven-compiler-plugin 3.1 maven-source-plugin 2.1.2 maven-javadoc-plugin 2.8 maven-gpg-plugin 1.4 org.apache.felix maven-bundle-plugin 2.4.0 maven-compiler-plugin ${java.version} ${java.version} javac-with-errorprone true com.google.errorprone error_prone_core 1.1.1 org.codehaus.plexus plexus-compiler-javac 2.4 org.codehaus.plexus plexus-compiler-javac-errorprone 2.4 maven-gpg-plugin sign-artifacts verify sign jdk8 [1.8,) org.apache.maven.plugins maven-javadoc-plugin -Xdoclint:-html org.apache.maven.plugins maven-javadoc-plugin -Xdoclint:-html jimfs-1.1/util/000077500000000000000000000000001265745405500134555ustar00rootroot00000000000000jimfs-1.1/util/deploy_snapshot.sh000077500000000000000000000006421265745405500172310ustar00rootroot00000000000000#!/bin/bash # see https://coderwall.com/p/9b_lfq set -e -u if [ "$TRAVIS_REPO_SLUG" == "google/jimfs" ] && \ [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \ [ "$TRAVIS_PULL_REQUEST" == "false" ] && \ [ "$TRAVIS_BRANCH" == "master" ]; then echo "Publishing Maven snapshot..." mvn clean source:jar javadoc:jar deploy --settings="util/settings.xml" -DskipTests=true echo "Maven snapshot published." fi jimfs-1.1/util/settings.xml000066400000000000000000000007011265745405500160350ustar00rootroot00000000000000 sonatype-nexus-snapshots ${env.CI_DEPLOY_USERNAME} ${env.CI_DEPLOY_PASSWORD} jimfs-1.1/util/update_snapshot_docs.sh000077500000000000000000000012741265745405500202310ustar00rootroot00000000000000#!/bin/bash # see http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ for details set -e -u if [ "$TRAVIS_REPO_SLUG" == "google/jimfs" ] && \ [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \ [ "$TRAVIS_PULL_REQUEST" == "false" ] && \ [ "$TRAVIS_BRANCH" == "master" ]; then echo "Publishing Javadoc and JDiff..." cd $HOME git clone -q -b gh-pages https://${GH_TOKEN}@github.com/google/jimfs gh-pages > /dev/null cd gh-pages git config --global user.email "travis@travis-ci.org" git config --global user.name "travis-ci" ./updaterelease.sh snapshot git push -fq origin gh-pages > /dev/null echo "Javadoc published to gh-pages." fi