diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b76b8957..8519eb9a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,8 +3,18 @@ updates: - package-ecosystem: "maven" directory: "/" schedule: - interval: "daily" + interval: "weekly" + groups: + dependencies: + applies-to: version-updates + patterns: + - "*" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" + groups: + github-actions: + applies-to: version-updates + patterns: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a88c954f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,106 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + test: + name: "JDK ${{ matrix.java }} on ${{ matrix.os }}" + strategy: + matrix: + os: [ ubuntu-latest ] + java: [ 21, 11, 8 ] + # Only test on macos and windows with a single recent JDK to avoid a + # combinatorial explosion of test configurations. + # Most OS-specific issues are not specific to a particular JDK version. + include: + - os: macos-latest + java: 21 + - os: windows-latest + java: 21 + runs-on: ${{ matrix.os }} + steps: + # Cancel any previous runs for the same branch that are still running. + - name: 'Cancel previous runs' + uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa + with: + access_token: ${{ github.token }} + - name: 'Check out repository' + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - name: 'Cache local Maven repository' + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: 'Set up JDK ${{ matrix.java }}' + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + with: + java-version: ${{ matrix.java }} + distribution: 'zulu' + - name: 'Install' + shell: bash + run: mvn -B install -U -DskipTests=true + - name: 'Test' + shell: bash + run: mvn -B verify -U -Dmaven.javadoc.skip=true + + publish_snapshot: + name: 'Publish snapshot' + needs: test + if: github.event_name == 'push' && github.repository == 'google/jimfs' + runs-on: ubuntu-latest + steps: + - name: 'Check out repository' + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - name: 'Cache local Maven repository' + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: 'Set up JDK 8' + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + with: + java-version: 8 + distribution: 'zulu' + server-id: sonatype-nexus-snapshots + server-username: CI_DEPLOY_USERNAME + server-password: CI_DEPLOY_PASSWORD + - name: 'Publish' + env: + CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} + CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} + run: mvn -B clean source:jar javadoc:jar deploy -DskipTests=true + + generate_docs: + name: 'Generate latest docs' + needs: test + if: github.event_name == 'push' && github.repository == 'google/jimfs' + runs-on: ubuntu-latest + steps: + - name: 'Check out repository' + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - name: 'Cache local Maven repository' + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: 'Set up JDK 11' + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + with: + java-version: 11 + distribution: 'zulu' + - name: 'Generate latest docs' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./util/update_snapshot_docs.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 338c662e..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -sudo: false - -language: java - -jdk: - - openjdk8 - - openjdk11 - -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: - only: - - master - - /^release.*$/ diff --git a/README.md b/README.md index d9759589..beee1f87 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,27 @@ Jimfs ===== -Jimfs is an in-memory file system for Java 7 and above, implementing the +Jimfs is an in-memory file system for Java 8 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) +[![Build Status](https://github.com/google/jimfs/workflows/CI/badge.svg?branch=master)](https://github.com/google/jimfs/actions) [![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). +The latest release is +[1.3.0](https://github.com/google/jimfs/releases/tag/v1.3.0). 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): +[com.google.jimfs:jimfs:1.3.0](http://search.maven.org/#artifactdetails%7Ccom.google.jimfs%7Cjimfs%7C1.3.0%7Cjar): ```xml com.google.jimfs jimfs - 1.1 + 1.3.0 ``` diff --git a/jimfs/pom.xml b/jimfs/pom.xml index d4b5ba1e..017387be 100644 --- a/jimfs/pom.xml +++ b/jimfs/pom.xml @@ -62,7 +62,7 @@ org.checkerframework - checker-compat-qual + checker-qual true @@ -83,19 +83,6 @@ - - maven-source-plugin - - - attach-sources - post-integration-test - - jar-no-fork - - - - - maven-javadoc-plugin diff --git a/jimfs/src/main/java/com/google/common/jimfs/AbstractWatchService.java b/jimfs/src/main/java/com/google/common/jimfs/AbstractWatchService.java index 6b4326d0..2ccaa1b2 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/AbstractWatchService.java +++ b/jimfs/src/main/java/com/google/common/jimfs/AbstractWatchService.java @@ -24,6 +24,7 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.nio.file.ClosedWatchServiceException; import java.nio.file.WatchEvent; @@ -41,7 +42,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Abstract implementation of {@link WatchService}. Provides the means for registering and managing @@ -90,16 +91,14 @@ ImmutableList queuedKeys() { return ImmutableList.copyOf(queue); } - @NullableDecl @Override - public WatchKey poll() { + public @Nullable WatchKey poll() { checkOpen(); return check(queue.poll()); } - @NullableDecl @Override - public WatchKey poll(long timeout, TimeUnit unit) throws InterruptedException { + public @Nullable WatchKey poll(long timeout, TimeUnit unit) throws InterruptedException { checkOpen(); return check(queue.poll(timeout, unit)); } @@ -111,8 +110,7 @@ public WatchKey take() throws InterruptedException { } /** Returns the given key, throwing an exception if it's the poison. */ - @NullableDecl - private WatchKey check(@NullableDecl WatchKey key) { + private @Nullable WatchKey check(@Nullable WatchKey key) { if (key == poison) { // ensure other blocking threads get the poison queue.offer(poison); @@ -142,9 +140,9 @@ static final class Event implements WatchEvent { private final Kind kind; private final int count; - @NullableDecl private final T context; + private final @Nullable T context; - public Event(Kind kind, int count, @NullableDecl 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; @@ -161,9 +159,8 @@ public int count() { return count; } - @NullableDecl @Override - public T context() { + public @Nullable T context() { return context; } @@ -214,7 +211,7 @@ private static WatchEvent overflowEvent(int count) { public Key( AbstractWatchService watcher, - @NullableDecl Watchable watchable, + @Nullable Watchable watchable, Iterable> subscribedTypes) { this.watcher = checkNotNull(watcher); this.watchable = watchable; // nullable for Watcher poison @@ -271,6 +268,7 @@ public List> pollEvents() { return Collections.unmodifiableList(result); } + @CanIgnoreReturnValue @Override public boolean reset() { // calling reset() multiple times without polling events would cause key to be placed in diff --git a/jimfs/src/main/java/com/google/common/jimfs/AclAttributeProvider.java b/jimfs/src/main/java/com/google/common/jimfs/AclAttributeProvider.java index 1fa0f155..9b543d31 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/AclAttributeProvider.java +++ b/jimfs/src/main/java/com/google/common/jimfs/AclAttributeProvider.java @@ -29,7 +29,7 @@ import java.nio.file.attribute.UserPrincipal; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Attribute provider that provides the {@link AclFileAttributeView} ("acl"). @@ -71,9 +71,8 @@ public ImmutableSet fixedAttributes() { return ImmutableMap.of("acl:acl", acl); } - @NullableDecl @Override - public Object get(File file, String attribute) { + public @Nullable Object get(File file, String attribute) { if (attribute.equals("acl")) { return file.getAttribute("acl", "acl"); } diff --git a/jimfs/src/main/java/com/google/common/jimfs/AttributeProvider.java b/jimfs/src/main/java/com/google/common/jimfs/AttributeProvider.java index f5cade23..b975c985 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/AttributeProvider.java +++ b/jimfs/src/main/java/com/google/common/jimfs/AttributeProvider.java @@ -24,7 +24,7 @@ import java.nio.file.attribute.FileAttributeView; import java.util.Arrays; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Abstract provider for handling a specific file attribute view. @@ -87,8 +87,7 @@ public ImmutableSet attributes(File file) { * Returns the value of the given attribute in the given file or null if the attribute is not * supported by this provider. */ - @NullableDecl - public abstract Object get(File file, String attribute); + public abstract @Nullable Object get(File file, String attribute); /** * Sets the value of the given attribute in the given file object. The {@code create} parameter @@ -108,8 +107,7 @@ public ImmutableSet attributes(File file) { * Returns the type of file attributes object this provider supports, or null if it doesn't * support reading its attributes as an object. */ - @NullableDecl - public Class attributesType() { + public @Nullable Class attributesType() { return null; } diff --git a/jimfs/src/main/java/com/google/common/jimfs/AttributeService.java b/jimfs/src/main/java/com/google/common/jimfs/AttributeService.java index 333a4973..3bae38d7 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/AttributeService.java +++ b/jimfs/src/main/java/com/google/common/jimfs/AttributeService.java @@ -36,7 +36,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Service providing all attribute related operations for a file store. One piece of the file store @@ -204,8 +204,7 @@ public Object getAttribute(File file, String view, String attribute) { return value; } - @NullableDecl - private Object getAttributeInternal(File file, String view, String attribute) { + private @Nullable Object getAttributeInternal(File file, String view, String attribute) { AttributeProvider provider = providersByName.get(view); if (provider == null) { return null; @@ -259,8 +258,8 @@ private void setAttributeInternal( * if the view type is not supported. */ @SuppressWarnings("unchecked") - @NullableDecl - public V getFileAttributeView(FileLookup lookup, Class type) { + public @Nullable V getFileAttributeView( + FileLookup lookup, Class type) { AttributeProvider provider = providersByViewType.get(type); if (provider != null) { diff --git a/jimfs/src/main/java/com/google/common/jimfs/BasicAttributeProvider.java b/jimfs/src/main/java/com/google/common/jimfs/BasicAttributeProvider.java index 6315ab70..43272ee3 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/BasicAttributeProvider.java +++ b/jimfs/src/main/java/com/google/common/jimfs/BasicAttributeProvider.java @@ -23,7 +23,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileTime; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Attribute provider that provides attributes common to all file systems, the {@link @@ -57,7 +57,7 @@ public ImmutableSet fixedAttributes() { } @Override - public Object get(File file, String attribute) { + public @Nullable Object get(File file, String attribute) { switch (attribute) { case "size": return file.size(); @@ -72,11 +72,11 @@ public Object get(File file, String attribute) { case "isOther": return !file.isDirectory() && !file.isRegularFile() && !file.isSymbolicLink(); case "creationTime": - return FileTime.fromMillis(file.getCreationTime()); + return file.getCreationTime(); case "lastAccessTime": - return FileTime.fromMillis(file.getLastAccessTime()); + return file.getLastAccessTime(); case "lastModifiedTime": - return FileTime.fromMillis(file.getLastModifiedTime()); + return file.getLastModifiedTime(); default: return null; } @@ -87,15 +87,15 @@ public void set(File file, String view, String attribute, Object value, boolean switch (attribute) { case "creationTime": checkNotCreate(view, attribute, create); - file.setCreationTime(checkType(view, attribute, value, FileTime.class).toMillis()); + file.setCreationTime(checkType(view, attribute, value, FileTime.class)); break; case "lastAccessTime": checkNotCreate(view, attribute, create); - file.setLastAccessTime(checkType(view, attribute, value, FileTime.class).toMillis()); + file.setLastAccessTime(checkType(view, attribute, value, FileTime.class)); break; case "lastModifiedTime": checkNotCreate(view, attribute, create); - file.setLastModifiedTime(checkType(view, attribute, value, FileTime.class).toMillis()); + file.setLastModifiedTime(checkType(view, attribute, value, FileTime.class)); break; case "size": case "fileKey": @@ -148,22 +148,22 @@ public BasicFileAttributes readAttributes() throws IOException { @Override public void setTimes( - @NullableDecl FileTime lastModifiedTime, - @NullableDecl FileTime lastAccessTime, - @NullableDecl FileTime createTime) + @Nullable FileTime lastModifiedTime, + @Nullable FileTime lastAccessTime, + @Nullable FileTime createTime) throws IOException { File file = lookupFile(); if (lastModifiedTime != null) { - file.setLastModifiedTime(lastModifiedTime.toMillis()); + file.setLastModifiedTime(lastModifiedTime); } if (lastAccessTime != null) { - file.setLastAccessTime(lastAccessTime.toMillis()); + file.setLastAccessTime(lastAccessTime); } if (createTime != null) { - file.setCreationTime(createTime.toMillis()); + file.setCreationTime(createTime); } } } @@ -181,9 +181,9 @@ static class Attributes implements BasicFileAttributes { 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.lastModifiedTime = file.getLastModifiedTime(); + this.lastAccessTime = file.getLastAccessTime(); + this.creationTime = file.getCreationTime(); this.regularFile = file.isRegularFile(); this.directory = file.isDirectory(); this.symbolicLink = file.isSymbolicLink(); diff --git a/jimfs/src/main/java/com/google/common/jimfs/Configuration.java b/jimfs/src/main/java/com/google/common/jimfs/Configuration.java index 06630eb7..7801ea7e 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/Configuration.java +++ b/jimfs/src/main/java/com/google/common/jimfs/Configuration.java @@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.nio.channels.FileChannel; import java.nio.file.FileSystem; import java.nio.file.InvalidPathException; @@ -44,7 +45,7 @@ import java.util.Map; import java.util.Set; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Immutable configuration for an in-memory file system. A {@code Configuration} is passed to a @@ -233,6 +234,7 @@ public static Builder builder(PathType pathType) { final ImmutableSet attributeViews; final ImmutableSet attributeProviders; final ImmutableMap defaultAttributeValues; + final FileTimeSource fileTimeSource; // Watch service final WatchServiceConfiguration watchServiceConfig; @@ -261,6 +263,7 @@ private Configuration(Builder builder) { builder.defaultAttributeValues == null ? ImmutableMap.of() : ImmutableMap.copyOf(builder.defaultAttributeValues); + this.fileTimeSource = builder.fileTimeSource; this.watchServiceConfig = builder.watchServiceConfig; this.roots = builder.roots; this.workingDirectory = builder.workingDirectory; @@ -301,6 +304,7 @@ public String toString() { if (!defaultAttributeValues.isEmpty()) { helper.add("defaultAttributeValues", defaultAttributeValues); } + helper.add("fileTimeSource", fileTimeSource); if (watchServiceConfig != WatchServiceConfiguration.DEFAULT) { helper.add("watchServiceConfig", watchServiceConfig); } @@ -341,6 +345,7 @@ public static final class Builder { private ImmutableSet attributeViews = ImmutableSet.of(); private Set attributeProviders = null; private Map defaultAttributeValues; + private FileTimeSource fileTimeSource = SystemFileTimeSource.INSTANCE; // Watch service private WatchServiceConfiguration watchServiceConfig = WatchServiceConfiguration.DEFAULT; @@ -372,6 +377,7 @@ private Builder(Configuration configuration) { configuration.defaultAttributeValues.isEmpty() ? null : new HashMap<>(configuration.defaultAttributeValues); + this.fileTimeSource = configuration.fileTimeSource; this.watchServiceConfig = configuration.watchServiceConfig; this.roots = configuration.roots; this.workingDirectory = configuration.workingDirectory; @@ -383,6 +389,7 @@ private Builder(Configuration configuration) { * Sets the normalizations that will be applied to the display form of filenames. The display * form is used in the {@code toString()} of {@code Path} objects. */ + @CanIgnoreReturnValue public Builder setNameDisplayNormalization(PathNormalization first, PathNormalization... more) { this.nameDisplayNormalization = checkNormalizations(Lists.asList(first, more)); return this; @@ -393,6 +400,7 @@ public Builder setNameDisplayNormalization(PathNormalization first, PathNormaliz * file system. The canonical form is used to determine the equality of two filenames when * performing a file lookup. */ + @CanIgnoreReturnValue public Builder setNameCanonicalNormalization( PathNormalization first, PathNormalization... more) { this.nameCanonicalNormalization = checkNormalizations(Lists.asList(first, more)); @@ -434,7 +442,7 @@ private ImmutableSet checkNormalizations( } private static void checkNormalizationNotSet( - PathNormalization n, @NullableDecl PathNormalization set) { + PathNormalization n, @Nullable PathNormalization set) { if (set != null) { throw new IllegalArgumentException( "can't set normalization " + n + ": normalization " + set + " already set"); @@ -447,6 +455,7 @@ private static void checkNormalizationNotSet( * *

The default is false. */ + @CanIgnoreReturnValue public Builder setPathEqualityUsesCanonicalForm(boolean useCanonicalForm) { this.pathEqualityUsesCanonicalForm = useCanonicalForm; return this; @@ -458,6 +467,7 @@ public Builder setPathEqualityUsesCanonicalForm(boolean useCanonicalForm) { * *

The default is 8192 bytes (8 KB). */ + @CanIgnoreReturnValue public Builder setBlockSize(int blockSize) { checkArgument(blockSize > 0, "blockSize (%s) must be positive", blockSize); this.blockSize = blockSize; @@ -478,6 +488,7 @@ public Builder setBlockSize(int blockSize) { * *

The default is 4 GB. */ + @CanIgnoreReturnValue public Builder setMaxSize(long maxSize) { checkArgument(maxSize > 0, "maxSize (%s) must be positive", maxSize); this.maxSize = maxSize; @@ -497,6 +508,7 @@ public Builder setMaxSize(long maxSize) { *

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. */ + @CanIgnoreReturnValue public Builder setMaxCacheSize(long maxCacheSize) { checkArgument(maxCacheSize >= 0, "maxCacheSize (%s) may not be negative", maxCacheSize); this.maxCacheSize = maxCacheSize; @@ -553,12 +565,14 @@ public Builder setMaxCacheSize(long maxCacheSize) { *

If any other views should be supported, attribute providers for those views must be * {@linkplain #addAttributeProvider(AttributeProvider) added}. */ + @CanIgnoreReturnValue 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. */ + @CanIgnoreReturnValue public Builder addAttributeProvider(AttributeProvider provider) { checkNotNull(provider); if (attributeProviders == null) { @@ -614,6 +628,7 @@ public Builder addAttributeProvider(AttributeProvider provider) { * * */ + @CanIgnoreReturnValue public Builder setDefaultAttributeValue(String attribute, Object value) { checkArgument( ATTRIBUTE_PATTERN.matcher(attribute).matches(), @@ -631,6 +646,17 @@ public Builder setDefaultAttributeValue(String attribute, Object value) { private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("[^:]+:[^:]+"); + /** + * Sets the {@link FileTimeSource} that will supply the current time for this file system. + * + * @since 1.3 + */ + @CanIgnoreReturnValue + public Builder setFileTimeSource(FileTimeSource source) { + this.fileTimeSource = checkNotNull(source); + return this; + } + /** * Sets the roots for the file system. * @@ -639,6 +665,7 @@ public Builder setDefaultAttributeValue(String attribute, Object value) { * @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 */ + @CanIgnoreReturnValue public Builder setRoots(String first, String... more) { List roots = Lists.asList(first, more); for (String root : roots) { @@ -657,6 +684,7 @@ public Builder setRoots(String first, String... more) { * @throws IllegalArgumentException if the given path is valid for this builder's path type but * is not an absolute path */ + @CanIgnoreReturnValue public Builder setWorkingDirectory(String workingDirectory) { PathType.ParseResult parseResult = pathType.parsePath(workingDirectory); checkArgument( @@ -671,6 +699,7 @@ public Builder setWorkingDirectory(String workingDirectory) { * Sets the given features to be supported by the file system. Any features not provided here * will not be supported. */ + @CanIgnoreReturnValue public Builder setSupportedFeatures(Feature... features) { supportedFeatures = Sets.immutableEnumSet(Arrays.asList(features)); return this; @@ -682,11 +711,13 @@ public Builder setSupportedFeatures(Feature... features) { * * @since 1.1 */ + @CanIgnoreReturnValue public Builder setWatchServiceConfiguration(WatchServiceConfiguration config) { this.watchServiceConfig = checkNotNull(config); return this; } + @CanIgnoreReturnValue private Builder setDisplayName(String displayName) { this.displayName = checkNotNull(displayName); return this; diff --git a/jimfs/src/main/java/com/google/common/jimfs/Directory.java b/jimfs/src/main/java/com/google/common/jimfs/Directory.java index aaab83b6..1d341c86 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/Directory.java +++ b/jimfs/src/main/java/com/google/common/jimfs/Directory.java @@ -19,8 +19,10 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableSortedSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.nio.file.attribute.FileTime; import java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A table of {@linkplain DirectoryEntry directory entries}. @@ -32,23 +34,23 @@ 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 normal directory with the given ID and creation time. */ + public static Directory create(int id, FileTime creationTime) { + return new Directory(id, creationTime); } - /** Creates a new root directory with the given ID and name. */ - public static Directory createRoot(int id, Name name) { - return new Directory(id, name); + /** Creates a new root directory with the given ID, creation time, and name. */ + public static Directory createRoot(int id, FileTime creationTime, Name name) { + return new Directory(id, creationTime, name); } - private Directory(int id) { - super(id); + private Directory(int id, FileTime creationTime) { + super(id, creationTime); put(new DirectoryEntry(this, Name.SELF, this)); } - private Directory(int id, Name rootName) { - this(id); + private Directory(int id, FileTime creationTime, Name rootName) { + this(id, creationTime); linked(new DirectoryEntry(this, rootName, this)); } @@ -57,8 +59,8 @@ private Directory(int id, Name rootName) { * this directory. */ @Override - Directory copyWithoutContent(int id) { - return Directory.create(id); + Directory copyWithoutContent(int id, FileTime creationTime) { + return Directory.create(id, creationTime); } /** @@ -103,8 +105,7 @@ public boolean isEmpty() { } /** Returns the entry for the given name in this table or null if no such entry exists. */ - @NullableDecl - public DirectoryEntry get(Name name) { + public @Nullable DirectoryEntry get(Name name) { int index = bucketIndex(name, table.length); DirectoryEntry entry = table[index]; @@ -303,6 +304,7 @@ private static void addToBucket( * * @throws IllegalArgumentException if there is no entry with the given name in the directory */ + @CanIgnoreReturnValue @VisibleForTesting DirectoryEntry remove(Name name) { int index = bucketIndex(name, table.length); @@ -334,7 +336,7 @@ DirectoryEntry remove(Name name) { public Iterator iterator() { return new AbstractIterator() { int index; - @NullableDecl DirectoryEntry entry; + @Nullable DirectoryEntry entry; @Override protected DirectoryEntry computeNext() { diff --git a/jimfs/src/main/java/com/google/common/jimfs/DirectoryEntry.java b/jimfs/src/main/java/com/google/common/jimfs/DirectoryEntry.java index 5bff50f5..6492a59b 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/DirectoryEntry.java +++ b/jimfs/src/main/java/com/google/common/jimfs/DirectoryEntry.java @@ -20,13 +20,14 @@ import static com.google.common.base.Preconditions.checkState; import com.google.common.base.MoreObjects; +import com.google.errorprone.annotations.CanIgnoreReturnValue; 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 org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Entry in a directory, containing references to the directory itself, the file the entry links to @@ -40,11 +41,11 @@ final class DirectoryEntry { private final Directory directory; private final Name name; - @NullableDecl private final File file; + private final @Nullable File file; - @NullableDecl DirectoryEntry next; // for use in Directory + @Nullable DirectoryEntry next; // for use in Directory - DirectoryEntry(Directory directory, Name name, @NullableDecl File file) { + DirectoryEntry(Directory directory, Name name, @Nullable File file) { this.directory = checkNotNull(directory); this.name = checkNotNull(name); this.file = file; @@ -61,6 +62,7 @@ public boolean exists() { * @return this * @throws NoSuchFileException if this entry does not exist */ + @CanIgnoreReturnValue public DirectoryEntry requireExists(Path pathForException) throws NoSuchFileException { if (!exists()) { throw new NoSuchFileException(pathForException.toString()); @@ -74,6 +76,7 @@ public DirectoryEntry requireExists(Path pathForException) throws NoSuchFileExce * @return this * @throws FileAlreadyExistsException if this entry does not exist */ + @CanIgnoreReturnValue public DirectoryEntry requireDoesNotExist(Path pathForException) throws FileAlreadyExistsException { if (exists()) { @@ -89,6 +92,7 @@ public DirectoryEntry requireDoesNotExist(Path pathForException) * @throws NoSuchFileException if this entry does not exist * @throws NotDirectoryException if this entry does not link to a directory */ + @CanIgnoreReturnValue public DirectoryEntry requireDirectory(Path pathForException) throws NoSuchFileException, NotDirectoryException { requireExists(pathForException); @@ -105,6 +109,7 @@ public DirectoryEntry requireDirectory(Path pathForException) * @throws NoSuchFileException if this entry does not exist * @throws NotLinkException if this entry does not link to a symbolic link */ + @CanIgnoreReturnValue public DirectoryEntry requireSymbolicLink(Path pathForException) throws NoSuchFileException, NotLinkException { requireExists(pathForException); @@ -135,8 +140,7 @@ public File file() { } /** Returns the file this entry links to or {@code null} if the file does not exist */ - @NullableDecl - public File fileOrNull() { + public @Nullable File fileOrNull() { return file; } diff --git a/jimfs/src/main/java/com/google/common/jimfs/DosAttributeProvider.java b/jimfs/src/main/java/com/google/common/jimfs/DosAttributeProvider.java index 51bf96bd..9a8ed189 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/DosAttributeProvider.java +++ b/jimfs/src/main/java/com/google/common/jimfs/DosAttributeProvider.java @@ -27,7 +27,7 @@ import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileTime; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Attribute provider that provides the {@link DosFileAttributeView} ("dos") and allows the reading @@ -75,9 +75,8 @@ private static Boolean getDefaultValue(String attribute, Map userProv return false; } - @NullableDecl @Override - public Object get(File file, String attribute) { + public @Nullable Object get(File file, String attribute) { if (ATTRIBUTES.contains(attribute)) { return file.getAttribute("dos", attribute); } diff --git a/jimfs/src/main/java/com/google/common/jimfs/DowngradedSeekableByteChannel.java b/jimfs/src/main/java/com/google/common/jimfs/DowngradedSeekableByteChannel.java index 5d4db8be..65dd133d 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/DowngradedSeekableByteChannel.java +++ b/jimfs/src/main/java/com/google/common/jimfs/DowngradedSeekableByteChannel.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; @@ -53,6 +54,7 @@ public long position() throws IOException { } @Override + @CanIgnoreReturnValue public SeekableByteChannel position(long newPosition) throws IOException { channel.position(newPosition); return this; @@ -64,6 +66,7 @@ public long size() throws IOException { } @Override + @CanIgnoreReturnValue public SeekableByteChannel truncate(long size) throws IOException { channel.truncate(size); return this; diff --git a/jimfs/src/main/java/com/google/common/jimfs/File.java b/jimfs/src/main/java/com/google/common/jimfs/File.java index ce1cc001..71b25016 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/File.java +++ b/jimfs/src/main/java/com/google/common/jimfs/File.java @@ -24,8 +24,9 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Table; import java.io.IOException; +import java.nio.file.attribute.FileTime; import java.util.concurrent.locks.ReadWriteLock; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A file object, containing both the file's metadata and content. @@ -38,20 +39,19 @@ public abstract class File { private int links; - private long creationTime; - private long lastAccessTime; - private long lastModifiedTime; + private FileTime creationTime; + private FileTime lastAccessTime; + private FileTime lastModifiedTime; - @NullableDecl // null when only the basic view is used (default) - private Table attributes; + // null when only the basic view is used (default) + private @Nullable Table attributes; - File(int id) { + File(int id, FileTime creationTime) { this.id = id; - long now = System.currentTimeMillis(); // TODO(cgdecker): Use a Clock - this.creationTime = now; - this.lastAccessTime = now; - this.lastModifiedTime = now; + this.creationTime = creationTime; + this.lastAccessTime = creationTime; + this.lastModifiedTime = creationTime; } /** Returns the ID of this file. */ @@ -83,11 +83,11 @@ public final boolean isSymbolicLink() { } /** - * 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. + * Creates a new file of the same type as this file with the given ID and creation time. 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); + abstract File copyWithoutContent(int id, FileTime creationTime); /** * Copies the content of this file to the given file. The given file must be the same type of file @@ -102,8 +102,7 @@ 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. */ - @NullableDecl - ReadWriteLock contentLock() { + @Nullable ReadWriteLock contentLock() { return null; } @@ -155,48 +154,35 @@ final synchronized void decrementLinkCount() { } /** Gets the creation time of the file. */ - @SuppressWarnings("GoodTime") // should return a java.time.Instant - public final synchronized long getCreationTime() { + public final synchronized FileTime getCreationTime() { return creationTime; } /** Gets the last access time of the file. */ - @SuppressWarnings("GoodTime") // should return a java.time.Instant - public final synchronized long getLastAccessTime() { + public final synchronized FileTime getLastAccessTime() { return lastAccessTime; } /** Gets the last modified time of the file. */ - @SuppressWarnings("GoodTime") // should return a java.time.Instant - public final synchronized long getLastModifiedTime() { + public final synchronized FileTime getLastModifiedTime() { return lastModifiedTime; } /** Sets the creation time of the file. */ - final synchronized void setCreationTime(long creationTime) { + final synchronized void setCreationTime(FileTime creationTime) { this.creationTime = creationTime; } /** Sets the last access time of the file. */ - final synchronized void setLastAccessTime(long lastAccessTime) { + final synchronized void setLastAccessTime(FileTime lastAccessTime) { this.lastAccessTime = lastAccessTime; } /** Sets the last modified time of the file. */ - final synchronized void setLastModifiedTime(long lastModifiedTime) { + final synchronized void setLastModifiedTime(FileTime 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. @@ -223,8 +209,7 @@ final synchronized ImmutableSet getAttributeKeys() { } /** Gets the value of the given attribute in the given view. */ - @NullableDecl - public final synchronized Object getAttribute(String view, String attribute) { + public final synchronized @Nullable Object getAttribute(String view, String attribute) { if (attributes == null) { return null; } @@ -252,7 +237,7 @@ final synchronized void copyBasicAttributes(File target) { } private synchronized void setFileTimes( - long creationTime, long lastModifiedTime, long lastAccessTime) { + FileTime creationTime, FileTime lastModifiedTime, FileTime lastAccessTime) { this.creationTime = creationTime; this.lastModifiedTime = lastModifiedTime; this.lastAccessTime = lastAccessTime; @@ -264,7 +249,7 @@ final synchronized void copyAttributes(File target) { target.putAll(attributes); } - private synchronized void putAll(@NullableDecl Table attributes) { + private synchronized void putAll(@Nullable Table attributes) { if (attributes != null && this.attributes != attributes) { if (this.attributes == null) { this.attributes = HashBasedTable.create(); diff --git a/jimfs/src/main/java/com/google/common/jimfs/FileFactory.java b/jimfs/src/main/java/com/google/common/jimfs/FileFactory.java index e26d41de..b4363937 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/FileFactory.java +++ b/jimfs/src/main/java/com/google/common/jimfs/FileFactory.java @@ -33,10 +33,14 @@ final class FileFactory { private final AtomicInteger idGenerator = new AtomicInteger(); private final HeapDisk disk; + private final FileTimeSource fileTimeSource; - /** Creates a new file factory using the given disk for regular files. */ - public FileFactory(HeapDisk disk) { + /** + * Creates a new file factory using the given disk for regular files and the given time source. + */ + public FileFactory(HeapDisk disk, FileTimeSource fileTimeSource) { this.disk = checkNotNull(disk); + this.fileTimeSource = checkNotNull(fileTimeSource); } private int nextFileId() { @@ -45,29 +49,29 @@ private int nextFileId() { /** Creates a new directory. */ public Directory createDirectory() { - return Directory.create(nextFileId()); + return Directory.create(nextFileId(), fileTimeSource.now()); } /** Creates a new root directory with the given name. */ public Directory createRootDirectory(Name name) { - return Directory.createRoot(nextFileId(), name); + return Directory.createRoot(nextFileId(), fileTimeSource.now(), name); } /** Creates a new regular file. */ @VisibleForTesting RegularFile createRegularFile() { - return RegularFile.create(nextFileId(), disk); + return RegularFile.create(nextFileId(), fileTimeSource.now(), disk); } /** Creates a new symbolic link referencing the given target path. */ @VisibleForTesting SymbolicLink createSymbolicLink(JimfsPath target) { - return SymbolicLink.create(nextFileId(), target); + return SymbolicLink.create(nextFileId(), fileTimeSource.now(), target); } /** Creates and returns a copy of the given file. */ public File copyWithoutContent(File file) throws IOException { - return file.copyWithoutContent(nextFileId()); + return file.copyWithoutContent(nextFileId(), fileTimeSource.now()); } // suppliers to act as file creation callbacks diff --git a/jimfs/src/main/java/com/google/common/jimfs/FileSystemState.java b/jimfs/src/main/java/com/google/common/jimfs/FileSystemState.java index f15a5ff0..31120fe3 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/FileSystemState.java +++ b/jimfs/src/main/java/com/google/common/jimfs/FileSystemState.java @@ -17,12 +17,15 @@ package com.google.common.jimfs; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Throwables.throwIfInstanceOf; +import static com.google.common.base.Throwables.throwIfUnchecked; -import com.google.common.base.Throwables; import com.google.common.collect.Sets; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Closeable; import java.io.IOException; import java.nio.file.ClosedFileSystemException; +import java.nio.file.attribute.FileTime; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -37,6 +40,7 @@ final class FileSystemState implements Closeable { private final Set resources = Sets.newConcurrentHashSet(); + private final FileTimeSource fileTimeSource; private final Runnable onClose; private final AtomicBoolean open = new AtomicBoolean(true); @@ -44,7 +48,8 @@ final class FileSystemState implements Closeable { /** Count of resources currently in the process of being registered. */ private final AtomicInteger registering = new AtomicInteger(); - FileSystemState(Runnable onClose) { + FileSystemState(FileTimeSource fileTimeSource, Runnable onClose) { + this.fileTimeSource = checkNotNull(fileTimeSource); this.onClose = checkNotNull(onClose); } @@ -66,6 +71,7 @@ public void checkOpen() { * Registers the given resource to be closed when the file system is closed. Should be called when * the resource is opened. */ + @CanIgnoreReturnValue 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 @@ -90,6 +96,11 @@ public void unregister(Closeable resource) { resources.remove(resource); } + /** Returns the current {@link FileTime}. */ + public FileTime now() { + return fileTimeSource.now(); + } + /** * Closes the file system, runs the {@code onClose} callback and closes all registered resources. */ @@ -123,7 +134,10 @@ public void close() throws IOException { // 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); + if (thrown != null) { + throwIfInstanceOf(thrown, IOException.class); + throwIfUnchecked(thrown); + } } } } diff --git a/jimfs/src/main/java/com/google/common/jimfs/FileSystemView.java b/jimfs/src/main/java/com/google/common/jimfs/FileSystemView.java index 62e8739c..702326ff 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/FileSystemView.java +++ b/jimfs/src/main/java/com/google/common/jimfs/FileSystemView.java @@ -28,6 +28,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Lists; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.nio.file.CopyOption; import java.nio.file.DirectoryNotEmptyException; @@ -42,13 +43,14 @@ import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.FileTime; 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 org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * View of a file system with a specific working directory. As all file system operations need to @@ -86,6 +88,10 @@ public FileSystemState state() { return store.state(); } + private FileTime now() { + return state().now(); + } + /** * 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. @@ -135,7 +141,7 @@ public ImmutableSortedSet snapshotWorkingDirectoryEntries() { store.readLock().lock(); try { ImmutableSortedSet names = workingDirectory.snapshot(); - workingDirectory.updateAccessTime(); + workingDirectory.setLastAccessTime(now()); return names; } finally { store.readLock().unlock(); @@ -146,8 +152,8 @@ public ImmutableSortedSet snapshotWorkingDirectoryEntries() { * 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(); + public ImmutableMap snapshotModifiedTimes(JimfsPath path) throws IOException { + ImmutableMap.Builder modifiedTimes = ImmutableMap.builder(); store.readLock().lock(); try { @@ -221,6 +227,7 @@ public JimfsPath toRealPath( * Creates a new directory at the given path. The given attributes will be set on the new file if * possible. */ + @CanIgnoreReturnValue public Directory createDirectory(JimfsPath path, FileAttribute... attrs) throws IOException { return (Directory) createFile(path, store.directoryCreator(), true, attrs); } @@ -229,6 +236,7 @@ public Directory createDirectory(JimfsPath path, FileAttribute... attrs) thro * 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. */ + @CanIgnoreReturnValue public SymbolicLink createSymbolicLink( JimfsPath path, JimfsPath target, FileAttribute... attrs) throws IOException { if (!store.supportsFeature(Feature.SYMBOLIC_LINKS)) { @@ -271,7 +279,7 @@ private File createFile( File newFile = fileCreator.get(); store.setInitialAttributes(newFile, attrs); parent.link(path.name(), newFile); - parent.updateModifiedTime(); + parent.setLastModifiedTime(now()); return newFile; } finally { store.writeLock().unlock(); @@ -305,8 +313,7 @@ public RegularFile getOrCreateRegularFile( * 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. */ - @NullableDecl - private RegularFile lookUpRegularFile(JimfsPath path, Set options) + private @Nullable RegularFile lookUpRegularFile(JimfsPath path, Set options) throws IOException { store.readLock().lock(); try { @@ -423,7 +430,7 @@ public void link(JimfsPath link, FileSystemView existingView, JimfsPath existing lookUp(link, Options.NOFOLLOW_LINKS).requireDoesNotExist(link).directory(); linkParent.link(linkName, existingFile); - linkParent.updateModifiedTime(); + linkParent.setLastModifiedTime(now()); } finally { store.writeLock().unlock(); } @@ -448,7 +455,7 @@ private void delete(DirectoryEntry entry, DeleteMode deleteMode, JimfsPath pathF checkDeletable(file, deleteMode, pathForException); parent.unlink(entry.name()); - parent.updateModifiedTime(); + parent.setLastModifiedTime(now()); file.deleted(); } @@ -545,10 +552,10 @@ public void copy( if (move && sameFileSystem) { // Real move on the same file system. sourceParent.unlink(source.name()); - sourceParent.updateModifiedTime(); + sourceParent.setLastModifiedTime(now()); destParent.link(dest.name(), sourceFile); - destParent.updateModifiedTime(); + destParent.setLastModifiedTime(now()); } else { // Doing a copy OR a move to a different file system, which must be implemented by copy and // delete. @@ -571,7 +578,7 @@ public void copy( // 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(); + destParent.setLastModifiedTime(now()); // 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 @@ -692,14 +699,13 @@ private void unlockSourceAndCopy(File sourceFile, File copyFile) { } /** Returns a file attribute view using the given lookup callback. */ - @NullableDecl - public V getFileAttributeView(FileLookup lookup, Class type) { + public @Nullable V getFileAttributeView( + FileLookup lookup, Class type) { return store.getFileAttributeView(lookup, type); } /** Returns a file attribute view for the given path in this view. */ - @NullableDecl - public V getFileAttributeView( + public @Nullable V getFileAttributeView( final JimfsPath path, Class type, final Set options) { return store.getFileAttributeView( new FileLookup() { diff --git a/jimfs/src/main/java/com/google/common/jimfs/FileTimeSource.java b/jimfs/src/main/java/com/google/common/jimfs/FileTimeSource.java new file mode 100644 index 00000000..bbc12928 --- /dev/null +++ b/jimfs/src/main/java/com/google/common/jimfs/FileTimeSource.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021 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.FileTime; + +/** + * A source of the current time as a {@link FileTime}, to enable fake time sources for testing. + * + * @since 1.3 + */ +public interface FileTimeSource { + /** Returns the current time according to this source as a {@link FileTime}. */ + FileTime now(); +} diff --git a/jimfs/src/main/java/com/google/common/jimfs/FileTree.java b/jimfs/src/main/java/com/google/common/jimfs/FileTree.java index c4809420..f0dd4260 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/FileTree.java +++ b/jimfs/src/main/java/com/google/common/jimfs/FileTree.java @@ -27,7 +27,7 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * The tree of directories and files for the file system. Contains the file system root directories @@ -63,8 +63,7 @@ public ImmutableSortedSet getRootDirectoryNames() { * Gets the directory entry for the root with the given name or {@code null} if no such root * exists. */ - @NullableDecl - public DirectoryEntry getRoot(Name name) { + public @Nullable DirectoryEntry getRoot(Name name) { Directory dir = roots.get(name); return dir == null ? null : dir.entryInParent(); } @@ -83,8 +82,7 @@ public DirectoryEntry lookUp( return result; } - @NullableDecl - private DirectoryEntry lookUp( + private @Nullable DirectoryEntry lookUp( File dir, JimfsPath path, Set options, int linkDepth) throws IOException { ImmutableList names = path.names(); @@ -114,8 +112,7 @@ private DirectoryEntry lookUp( * Looks up the given names against the given base file. If the file is not a directory, the * lookup fails. */ - @NullableDecl - private DirectoryEntry lookUp( + private @Nullable DirectoryEntry lookUp( File dir, Iterable names, Set options, int linkDepth) throws IOException { Iterator nameIterator = names.iterator(); @@ -151,9 +148,8 @@ private DirectoryEntry lookUp( } /** Looks up the last element of a path. */ - @NullableDecl - private DirectoryEntry lookUpLast( - @NullableDecl File dir, Name name, Set options, int linkDepth) + private @Nullable DirectoryEntry lookUpLast( + @Nullable File dir, Name name, Set options, int linkDepth) throws IOException { Directory directory = toDirectory(dir); if (directory == null) { @@ -177,8 +173,7 @@ private DirectoryEntry lookUpLast( * Returns the directory entry located by the target path of the given symbolic link, resolved * relative to the given directory. */ - @NullableDecl - private DirectoryEntry followSymbolicLink(File dir, SymbolicLink link, int linkDepth) + private @Nullable 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"); @@ -196,8 +191,7 @@ private DirectoryEntry followSymbolicLink(File dir, SymbolicLink link, int linkD * we find an entry [bar -> "." -> bar], we instead return the entry for bar in its parent, [foo * -> "bar" -> bar]. */ - @NullableDecl - private DirectoryEntry getRealEntry(DirectoryEntry entry) { + private @Nullable DirectoryEntry getRealEntry(DirectoryEntry entry) { Name name = entry.name(); if (name.equals(Name.SELF) || name.equals(Name.PARENT)) { @@ -209,8 +203,7 @@ private DirectoryEntry getRealEntry(DirectoryEntry entry) { } } - @NullableDecl - private Directory toDirectory(@NullableDecl File file) { + private @Nullable Directory toDirectory(@Nullable File file) { return file == null || !file.isDirectory() ? null : (Directory) file; } diff --git a/jimfs/src/main/java/com/google/common/jimfs/Handler.java b/jimfs/src/main/java/com/google/common/jimfs/Handler.java index fd4ab74a..18c305b6 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/Handler.java +++ b/jimfs/src/main/java/com/google/common/jimfs/Handler.java @@ -23,6 +23,7 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; +import org.checkerframework.checker.nullness.qual.Nullable; /** * {@link URLStreamHandler} implementation for jimfs. Named {@code Handler} so that the class can be @@ -81,7 +82,7 @@ protected URLConnection openConnection(URL url) throws IOException { @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") // no need to synchronize to return null - protected InetAddress getHostAddress(URL url) { + protected @Nullable InetAddress getHostAddress(URL url) { // jimfs uses the URI host to specify the name of the file system being used. // In the default implementation of getHostAddress(URL), a non-null host would cause an attempt // to look up the IP address, causing a slowdown on calling equals/hashCode methods on the URL diff --git a/jimfs/src/main/java/com/google/common/jimfs/HeapDisk.java b/jimfs/src/main/java/com/google/common/jimfs/HeapDisk.java index ab06933f..81328698 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/HeapDisk.java +++ b/jimfs/src/main/java/com/google/common/jimfs/HeapDisk.java @@ -69,7 +69,8 @@ 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); + maxCachedBlockCount >= 0, "maxCachedBlockCount (%s) must be non-negative", + maxCachedBlockCount); this.blockSize = blockSize; this.maxBlockCount = maxBlockCount; this.maxCachedBlockCount = maxCachedBlockCount; @@ -82,7 +83,14 @@ private static int toBlockCount(long size, int blockSize) { } private RegularFile createBlockCache(int maxCachedBlockCount) { - return new RegularFile(-1, this, new byte[Math.min(maxCachedBlockCount, 8192)][], 0, 0); + // This file is just for holding blocks so things like the creation time don't matter + return new RegularFile( + -1, + SystemFileTimeSource.INSTANCE.now(), + this, + new byte[Math.min(maxCachedBlockCount, 8192)][], + 0, + 0); } /** Returns the size of blocks created by this disk. */ diff --git a/jimfs/src/main/java/com/google/common/jimfs/Java8Compatibility.java b/jimfs/src/main/java/com/google/common/jimfs/Java8Compatibility.java new file mode 100644 index 00000000..522f5444 --- /dev/null +++ b/jimfs/src/main/java/com/google/common/jimfs/Java8Compatibility.java @@ -0,0 +1,29 @@ +/* + * Copyright 2020 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.Buffer; + +/** + * Wrappers around {@link Buffer} methods that are covariantly overridden in Java 9+. See + * https://github.com/google/guava/issues/3990 + */ +final class Java8Compatibility { + static void clear(Buffer b) { + b.clear(); + } + + private Java8Compatibility() {} +} diff --git a/jimfs/src/main/java/com/google/common/jimfs/Jimfs.java b/jimfs/src/main/java/com/google/common/jimfs/Jimfs.java index a04ce46d..6719913d 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/Jimfs.java +++ b/jimfs/src/main/java/com/google/common/jimfs/Jimfs.java @@ -33,7 +33,7 @@ import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Static factory methods for creating new Jimfs file systems. File systems may either be created @@ -174,7 +174,7 @@ static FileSystem newFileSystem(URI uri, Configuration config) { * The system-loaded instance of {@code SystemJimfsFileSystemProvider}, or {@code null} if it * could not be found or loaded. */ - @NullableDecl static final FileSystemProvider systemProvider = getSystemJimfsProvider(); + static final @Nullable FileSystemProvider systemProvider = getSystemJimfsProvider(); /** * Returns the system-loaded instance of {@code SystemJimfsFileSystemProvider} or {@code null} if @@ -188,8 +188,7 @@ static FileSystem newFileSystem(URI uri, Configuration config) { * same class loader) as the class whose static cache a {@code JimfsFileSystem} instance will be * placed in when {@code FileSystems.newFileSystem} is called in {@code Jimfs.newFileSystem}. */ - @NullableDecl - private static FileSystemProvider getSystemJimfsProvider() { + private static @Nullable FileSystemProvider getSystemJimfsProvider() { try { for (FileSystemProvider provider : FileSystemProvider.installedProviders()) { if (provider.getScheme().equals(URI_SCHEME)) { diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsAsynchronousFileChannel.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsAsynchronousFileChannel.java index c59522c6..36c0af32 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/JimfsAsynchronousFileChannel.java +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsAsynchronousFileChannel.java @@ -19,10 +19,12 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.util.concurrent.Futures; 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 com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; @@ -32,7 +34,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * {@link AsynchronousFileChannel} implementation that delegates to a {@link JimfsFileChannel}. @@ -55,13 +57,12 @@ public long size() throws IOException { } private void addCallback( - ListenableFuture future, - CompletionHandler handler, - @NullableDecl A attachment) { + ListenableFuture future, CompletionHandler handler, @Nullable A attachment) { future.addListener(new CompletionHandlerCallback<>(future, handler, attachment), executor); } @Override + @CanIgnoreReturnValue public AsynchronousFileChannel truncate(long size) throws IOException { channel.truncate(size); return this; @@ -77,7 +78,7 @@ public void lock( long position, long size, boolean shared, - @NullableDecl A attachment, + @Nullable A attachment, CompletionHandler handler) { checkNotNull(handler); addCallback(lock(position, size, shared), handler, attachment); @@ -122,7 +123,7 @@ public FileLock tryLock(long position, long size, boolean shared) throws IOExcep public void read( ByteBuffer dst, long position, - @NullableDecl A attachment, + @Nullable A attachment, CompletionHandler handler) { addCallback(read(dst, position), handler, attachment); } @@ -148,7 +149,7 @@ public Integer call() throws IOException { public void write( ByteBuffer src, long position, - @NullableDecl A attachment, + @Nullable A attachment, CompletionHandler handler) { addCallback(write(src, position), handler, attachment); } @@ -191,12 +192,12 @@ private static final class CompletionHandlerCallback implements Runnable { private final ListenableFuture future; private final CompletionHandler completionHandler; - @NullableDecl private final A attachment; + private final @Nullable A attachment; private CompletionHandlerCallback( ListenableFuture future, CompletionHandler completionHandler, - @NullableDecl A attachment) { + @Nullable A attachment) { this.future = checkNotNull(future); this.completionHandler = checkNotNull(completionHandler); this.attachment = attachment; @@ -206,13 +207,11 @@ private CompletionHandlerCallback( public void run() { R result; try { - result = future.get(); + result = Futures.getDone(future); } 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 + } catch (RuntimeException | Error e) { onFailure(e); return; } diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileChannel.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileChannel.java index 95863cc5..ee40a996 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileChannel.java +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileChannel.java @@ -22,6 +22,7 @@ import static java.nio.file.StandardOpenOption.READ; import static java.nio.file.StandardOpenOption.WRITE; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; @@ -158,7 +159,7 @@ public int read(ByteBuffer dst) throws IOException { if (read != -1) { position += read; } - file.updateAccessTime(); + file.setLastAccessTime(fileSystemState.now()); completed = true; } finally { file.readLock().unlock(); @@ -195,7 +196,7 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { if (read != -1) { position += read; } - file.updateAccessTime(); + file.setLastAccessTime(fileSystemState.now()); completed = true; } finally { file.readLock().unlock(); @@ -228,7 +229,7 @@ public int read(ByteBuffer dst, long position) throws IOException { file.readLock().lockInterruptibly(); try { read = file.read(position, dst); - file.updateAccessTime(); + file.setLastAccessTime(fileSystemState.now()); completed = true; } finally { file.readLock().unlock(); @@ -263,7 +264,7 @@ public int write(ByteBuffer src) throws IOException { } written = file.write(position, src); position += written; - file.updateModifiedTime(); + file.setLastModifiedTime(fileSystemState.now()); completed = true; } finally { file.writeLock().unlock(); @@ -301,7 +302,7 @@ public long write(ByteBuffer[] srcs, int offset, int length) throws IOException } written = file.write(position, buffers); position += written; - file.updateModifiedTime(); + file.setLastModifiedTime(fileSystemState.now()); completed = true; } finally { file.writeLock().unlock(); @@ -339,7 +340,7 @@ public int write(ByteBuffer src, long position) throws IOException { position = file.sizeWithoutLocking(); written = file.write(position, src); this.position = position + written; - file.updateModifiedTime(); + file.setLastModifiedTime(fileSystemState.now()); completed = true; } finally { file.writeLock().unlock(); @@ -360,7 +361,7 @@ public int write(ByteBuffer src, long position) throws IOException { file.writeLock().lockInterruptibly(); try { written = file.write(position, src); - file.updateModifiedTime(); + file.setLastModifiedTime(fileSystemState.now()); completed = true; } finally { file.writeLock().unlock(); @@ -399,6 +400,7 @@ public long position() throws IOException { } @Override + @CanIgnoreReturnValue public FileChannel position(long newPosition) throws IOException { Util.checkNotNegative(newPosition, "newPosition"); checkOpen(); @@ -448,6 +450,7 @@ public long size() throws IOException { } @Override + @CanIgnoreReturnValue public FileChannel truncate(long size) throws IOException { Util.checkNotNegative(size, "size"); checkOpen(); @@ -465,7 +468,7 @@ public FileChannel truncate(long size) throws IOException { if (position > size) { position = size; } - file.updateModifiedTime(); + file.setLastModifiedTime(fileSystemState.now()); completed = true; } finally { file.writeLock().unlock(); @@ -514,7 +517,7 @@ public long transferTo(long position, long count, WritableByteChannel target) th file.readLock().lockInterruptibly(); try { transferred = file.transferTo(position, count, target); - file.updateAccessTime(); + file.setLastAccessTime(fileSystemState.now()); completed = true; } finally { file.readLock().unlock(); @@ -552,7 +555,7 @@ public long transferFrom(ReadableByteChannel src, long position, long count) thr position = file.sizeWithoutLocking(); transferred = file.transferFrom(src, position, count); this.position = position + transferred; - file.updateModifiedTime(); + file.setLastModifiedTime(fileSystemState.now()); completed = true; } finally { file.writeLock().unlock(); @@ -573,7 +576,7 @@ public long transferFrom(ReadableByteChannel src, long position, long count) thr file.writeLock().lockInterruptibly(); try { transferred = file.transferFrom(src, position, count); - file.updateModifiedTime(); + file.setLastModifiedTime(fileSystemState.now()); completed = true; } finally { file.writeLock().unlock(); diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileStore.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileStore.java index 910d231e..08ae3e36 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileStore.java +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileStore.java @@ -34,7 +34,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * {@link FileStore} implementation which provides methods for file creation, lookup and attribute @@ -102,8 +102,7 @@ ImmutableSortedSet getRootDirectoryNames() { } /** Returns the root directory with the given name or {@code null} if no such directory exists. */ - @NullableDecl - Directory getRoot(Name name) { + @Nullable Directory getRoot(Name name) { DirectoryEntry entry = tree.getRoot(name); return entry == null ? null : (Directory) entry.file(); } @@ -170,8 +169,7 @@ void setInitialAttributes(File file, FileAttribute... 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. */ - @NullableDecl - V getFileAttributeView(FileLookup lookup, Class type) { + @Nullable V getFileAttributeView(FileLookup lookup, Class type) { state.checkOpen(); return attributes.getFileAttributeView(lookup, type); } diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystem.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystem.java index dd72146f..4cc0d20b 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystem.java +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystem.java @@ -33,7 +33,7 @@ import java.nio.file.attribute.UserPrincipalLookupService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * {@link FileSystem} implementation for Jimfs. Most behavior for the file system is implemented by @@ -285,7 +285,7 @@ public WatchService newWatchService() throws IOException { return watchServiceConfig.newWatchService(defaultView, pathService); } - @NullableDecl private ExecutorService defaultThreadPool; + private @Nullable ExecutorService defaultThreadPool; /** * Returns a default thread pool to use for asynchronous file channels when users do not provide diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java index 8d487dd7..915bc344 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java @@ -48,7 +48,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * {@link FileSystemProvider} implementation for Jimfs. This provider implements the actual file @@ -170,7 +170,7 @@ public SeekableByteChannel newByteChannel( public AsynchronousFileChannel newAsynchronousFileChannel( Path path, Set options, - @NullableDecl ExecutorService executor, + @Nullable ExecutorService executor, FileAttribute... attrs) throws IOException { // call newFileChannel and cast so that FileChannel support is checked there @@ -320,9 +320,8 @@ public void checkAccess(Path path, AccessMode... modes) throws IOException { getDefaultView(checkedPath).checkAccess(checkedPath); } - @NullableDecl @Override - public V getFileAttributeView( + public @Nullable V getFileAttributeView( Path path, Class type, LinkOption... options) { JimfsPath checkedPath = checkPath(path); return getDefaultView(checkedPath) diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystems.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystems.java index bd36c8f2..7143dae6 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystems.java +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystems.java @@ -68,7 +68,8 @@ private static Runnable removeFileSystemRunnable(URI uri) { public static JimfsFileSystem newFileSystem( JimfsFileSystemProvider provider, URI uri, Configuration config) throws IOException { PathService pathService = new PathService(config); - FileSystemState state = new FileSystemState(removeFileSystemRunnable(uri)); + FileSystemState state = + new FileSystemState(config.fileTimeSource, removeFileSystemRunnable(uri)); JimfsFileStore fileStore = createFileStore(config, pathService, state); FileSystemView defaultView = createDefaultView(config, fileStore, pathService); @@ -87,7 +88,7 @@ private static JimfsFileStore createFileStore( AttributeService attributeService = new AttributeService(config); HeapDisk disk = new HeapDisk(config); - FileFactory fileFactory = new FileFactory(disk); + FileFactory fileFactory = new FileFactory(disk, config.fileTimeSource); Map roots = new HashMap<>(); diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsInputStream.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsInputStream.java index 750530c5..41bcc911 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/JimfsInputStream.java +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsInputStream.java @@ -63,7 +63,7 @@ public synchronized int read() throws IOException { if (b == -1) { finished = true; } else { - file.updateAccessTime(); + file.setLastAccessTime(fileSystemState.now()); } return b; } finally { @@ -97,7 +97,7 @@ private synchronized int readInternal(byte[] b, int off, int len) throws IOExcep pos += read; } - file.updateAccessTime(); + file.setLastAccessTime(fileSystemState.now()); return read; } finally { file.readLock().unlock(); diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsOutputStream.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsOutputStream.java index 0b88046e..654857e4 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/JimfsOutputStream.java +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsOutputStream.java @@ -58,7 +58,7 @@ public synchronized void write(int b) throws IOException { } file.write(pos++, (byte) b); - file.updateModifiedTime(); + file.setLastModifiedTime(fileSystemState.now()); } finally { file.writeLock().unlock(); } @@ -85,7 +85,7 @@ private synchronized void writeInternal(byte[] b, int off, int len) throws IOExc } pos += file.write(pos, b, off, len); - file.updateModifiedTime(); + file.setLastModifiedTime(fileSystemState.now()); } finally { file.writeLock().unlock(); } diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java index 9d18837a..4c454b86 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java @@ -41,7 +41,7 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Jimfs implementation of {@link Path}. Creation of new {@code Path} objects is delegated to the @@ -49,21 +49,21 @@ * * @author Colin Decker */ +@SuppressWarnings("ShouldNotSubclass") // I know what I'm doing I promise final class JimfsPath implements Path { - @NullableDecl private final Name root; + private final @Nullable Name root; private final ImmutableList names; private final PathService pathService; - public JimfsPath(PathService pathService, @NullableDecl Name root, Iterable names) { + 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. */ - @NullableDecl - public Name root() { + public @Nullable Name root() { return root; } @@ -76,8 +76,7 @@ public ImmutableList 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. */ - @NullableDecl - public Name name() { + public @Nullable Name name() { if (!names.isEmpty()) { return Iterables.getLast(names); } @@ -112,7 +111,7 @@ public boolean isAbsolute() { } @Override - public JimfsPath getRoot() { + public @Nullable JimfsPath getRoot() { if (root == null) { return null; } @@ -120,12 +119,12 @@ public JimfsPath getRoot() { } @Override - public JimfsPath getFileName() { + public @Nullable JimfsPath getFileName() { return names.isEmpty() ? null : getName(names.size() - 1); } @Override - public JimfsPath getParent() { + public @Nullable JimfsPath getParent() { if (names.isEmpty() || (names.size() == 1 && root == null)) { return null; } @@ -417,7 +416,7 @@ public int compareTo(Path other) { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { return obj instanceof JimfsPath && compareTo((JimfsPath) obj) == 0; } @@ -431,8 +430,7 @@ public String toString() { return pathService.toString(this); } - @NullableDecl - private JimfsPath checkPath(Path other) { + private @Nullable JimfsPath checkPath(Path other) { if (checkNotNull(other) instanceof JimfsPath && other.getFileSystem().equals(getFileSystem())) { return (JimfsPath) other; } diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsSecureDirectoryStream.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsSecureDirectoryStream.java index e3391b67..db72f046 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/JimfsSecureDirectoryStream.java +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsSecureDirectoryStream.java @@ -35,7 +35,7 @@ import java.nio.file.attribute.FileAttributeView; import java.util.Iterator; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Secure directory stream implementation that uses a {@link FileSystemView} with the stream's @@ -87,7 +87,7 @@ protected synchronized void checkOpen() { private final class DirectoryIterator extends AbstractIterator { - @NullableDecl private Iterator fileNames; + private @Nullable Iterator fileNames; @Override protected synchronized Path computeNext() { diff --git a/jimfs/src/main/java/com/google/common/jimfs/Name.java b/jimfs/src/main/java/com/google/common/jimfs/Name.java index 327be751..885c5628 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/Name.java +++ b/jimfs/src/main/java/com/google/common/jimfs/Name.java @@ -21,7 +21,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.collect.Ordering; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Immutable representation of a file name. Used both for the name components of paths and as the @@ -77,7 +77,7 @@ private Name(String display, String canonical) { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof Name) { Name other = (Name) obj; return canonical.equals(other.canonical); diff --git a/jimfs/src/main/java/com/google/common/jimfs/OwnerAttributeProvider.java b/jimfs/src/main/java/com/google/common/jimfs/OwnerAttributeProvider.java index 34086392..156be1b3 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/OwnerAttributeProvider.java +++ b/jimfs/src/main/java/com/google/common/jimfs/OwnerAttributeProvider.java @@ -26,7 +26,7 @@ import java.nio.file.attribute.FileOwnerAttributeView; import java.nio.file.attribute.UserPrincipal; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Attribute provider that provides the {@link FileOwnerAttributeView} ("owner"). @@ -65,9 +65,8 @@ public ImmutableSet fixedAttributes() { return ImmutableMap.of("owner:owner", owner); } - @NullableDecl @Override - public Object get(File file, String attribute) { + public @Nullable Object get(File file, String attribute) { if (attribute.equals("owner")) { return file.getAttribute("owner", "owner"); } diff --git a/jimfs/src/main/java/com/google/common/jimfs/PathService.java b/jimfs/src/main/java/com/google/common/jimfs/PathService.java index 2bd11a72..89e9290e 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/PathService.java +++ b/jimfs/src/main/java/com/google/common/jimfs/PathService.java @@ -38,7 +38,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Service for creating {@link JimfsPath} instances and handling other path-related operations. @@ -162,7 +162,7 @@ public JimfsPath createRelativePath(Iterable names) { } /** Returns a path with the given root (or no root, if null) and the given names. */ - public JimfsPath createPath(@NullableDecl Name root, Iterable 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 @@ -173,7 +173,7 @@ public JimfsPath createPath(@NullableDecl Name root, Iterable names) { } /** Returns a path with the given root (or no root, if null) and the given names. */ - protected final JimfsPath createPathInternal(@NullableDecl Name root, Iterable names) { + protected final JimfsPath createPathInternal(@Nullable Name root, Iterable names) { return new JimfsPath(this, root, names); } diff --git a/jimfs/src/main/java/com/google/common/jimfs/PathType.java b/jimfs/src/main/java/com/google/common/jimfs/PathType.java index 4e4d30e2..1ac75336 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/PathType.java +++ b/jimfs/src/main/java/com/google/common/jimfs/PathType.java @@ -26,7 +26,7 @@ import java.net.URISyntaxException; import java.nio.file.InvalidPathException; import java.util.Arrays; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * An object defining a specific type of path. Knows how to parse strings to a path and how to @@ -164,7 +164,7 @@ public String toString() { } /** Returns the string form of the given path. */ - public abstract String toString(@NullableDecl String root, Iterable names); + 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 @@ -211,10 +211,10 @@ public final ParseResult fromUri(URI uri) { /** Simple result of parsing a path. */ public static final class ParseResult { - @NullableDecl private final String root; + private final @Nullable String root; private final Iterable names; - public ParseResult(@NullableDecl String root, Iterable names) { + public ParseResult(@Nullable String root, Iterable names) { this.root = root; this.names = checkNotNull(names); } @@ -230,8 +230,7 @@ public boolean isRoot() { } /** Returns the parsed root element, or null if there was no root. */ - @NullableDecl - public String root() { + public @Nullable String root() { return root; } diff --git a/jimfs/src/main/java/com/google/common/jimfs/PathURLConnection.java b/jimfs/src/main/java/com/google/common/jimfs/PathURLConnection.java index 4f71d33b..6626657b 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/PathURLConnection.java +++ b/jimfs/src/main/java/com/google/common/jimfs/PathURLConnection.java @@ -43,6 +43,7 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import org.checkerframework.checker.nullness.qual.Nullable; /** * {@code URLConnection} implementation. @@ -134,7 +135,7 @@ public Map> getHeaderFields() { } @Override - public String getHeaderField(String name) { + public @Nullable String getHeaderField(String name) { try { connect(); } catch (IOException e) { diff --git a/jimfs/src/main/java/com/google/common/jimfs/PollingWatchService.java b/jimfs/src/main/java/com/google/common/jimfs/PollingWatchService.java index d1f5b898..30fe39f6 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/PollingWatchService.java +++ b/jimfs/src/main/java/com/google/common/jimfs/PollingWatchService.java @@ -26,11 +26,13 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.errorprone.annotations.CanIgnoreReturnValue; 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.nio.file.attribute.FileTime; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -91,6 +93,7 @@ final class PollingWatchService extends AbstractWatchService { } @Override + @CanIgnoreReturnValue public Key register(Watchable watchable, Iterable> eventTypes) throws IOException { JimfsPath path = checkWatchable(watchable); @@ -198,9 +201,9 @@ private Snapshot takeSnapshot(JimfsPath path) throws IOException { private final class Snapshot { /** Maps directory entry names to last modified times. */ - private final ImmutableMap modifiedTimes; + private final ImmutableMap modifiedTimes; - Snapshot(Map modifiedTimes) { + Snapshot(Map modifiedTimes) { this.modifiedTimes = ImmutableMap.copyOf(modifiedTimes); } @@ -232,11 +235,11 @@ boolean postChanges(Snapshot newState, Key key) { } if (key.subscribesTo(ENTRY_MODIFY)) { - for (Map.Entry entry : modifiedTimes.entrySet()) { + for (Map.Entry entry : modifiedTimes.entrySet()) { Name name = entry.getKey(); - Long modifiedTime = entry.getValue(); + FileTime modifiedTime = entry.getValue(); - Long newModifiedTime = newState.modifiedTimes.get(name); + FileTime newModifiedTime = newState.modifiedTimes.get(name); if (newModifiedTime != null && !modifiedTime.equals(newModifiedTime)) { key.post(new Event<>(ENTRY_MODIFY, 1, pathService.createFileName(name))); changesPosted = true; diff --git a/jimfs/src/main/java/com/google/common/jimfs/PosixAttributeProvider.java b/jimfs/src/main/java/com/google/common/jimfs/PosixAttributeProvider.java index 9dcd887e..490ef82b 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/PosixAttributeProvider.java +++ b/jimfs/src/main/java/com/google/common/jimfs/PosixAttributeProvider.java @@ -35,7 +35,7 @@ import java.nio.file.attribute.UserPrincipal; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Attribute provider that provides the {@link PosixFileAttributeView} ("posix") and allows reading @@ -114,9 +114,8 @@ public ImmutableSet fixedAttributes() { "posix:permissions", permissions); } - @NullableDecl @Override - public Object get(File file, String attribute) { + public @Nullable Object get(File file, String attribute) { switch (attribute) { case "group": return file.getAttribute("posix", "group"); diff --git a/jimfs/src/main/java/com/google/common/jimfs/RegularFile.java b/jimfs/src/main/java/com/google/common/jimfs/RegularFile.java index b8bb6888..57ea576d 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/RegularFile.java +++ b/jimfs/src/main/java/com/google/common/jimfs/RegularFile.java @@ -23,11 +23,13 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.UnsignedBytes; +import com.google.errorprone.annotations.CanIgnoreReturnValue; 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.nio.file.attribute.FileTime; import java.util.Arrays; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; @@ -53,12 +55,18 @@ final class RegularFile extends File { 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); + public static RegularFile create(int id, FileTime creationTime, HeapDisk disk) { + return new RegularFile(id, creationTime, disk, new byte[32][], 0, 0); + } + + RegularFile( + int id, + FileTime creationTime, + HeapDisk disk, + byte[][] blocks, + int blockCount, + long size) { + super(id, creationTime); this.disk = checkNotNull(disk); this.blocks = checkNotNull(blocks); this.blockCount = blockCount; @@ -150,9 +158,9 @@ public long size() { } @Override - RegularFile copyWithoutContent(int id) { + RegularFile copyWithoutContent(int id, FileTime creationTime) { byte[][] copyBlocks = new byte[Math.max(blockCount * 2, 32)][]; - return new RegularFile(id, disk, copyBlocks, 0, size); + return new RegularFile(id, creationTime, disk, copyBlocks, 0, size); } @Override @@ -217,6 +225,7 @@ private void deleteContents() { * nothing. Returns {@code true} if this file was modified by the call (its size changed) and * {@code false} otherwise. */ + @CanIgnoreReturnValue public boolean truncate(long size) { if (size >= this.size) { return false; @@ -274,6 +283,7 @@ private void prepareForWrite(long pos, long len) throws IOException { * * @throws IOException if the file needs more blocks but the disk is full */ + @CanIgnoreReturnValue public int write(long pos, byte b) throws IOException { prepareForWrite(pos, 1); @@ -296,6 +306,7 @@ public int write(long pos, byte b) throws IOException { * * @throws IOException if the file needs more blocks but the disk is full */ + @CanIgnoreReturnValue public int write(long pos, byte[] b, int off, int len) throws IOException { prepareForWrite(pos, len); @@ -337,6 +348,7 @@ public int write(long pos, byte[] b, int off, int len) throws IOException { * * @throws IOException if the file needs more blocks but the disk is full */ + @CanIgnoreReturnValue public int write(long pos, ByteBuffer buf) throws IOException { int len = buf.remaining(); @@ -374,6 +386,7 @@ public int write(long pos, ByteBuffer buf) throws IOException { * * @throws IOException if the file needs more blocks but the disk is full */ + @CanIgnoreReturnValue public long write(long pos, Iterable bufs) throws IOException { long start = pos; for (ByteBuffer buf : bufs) { @@ -385,70 +398,66 @@ public long write(long pos, Iterable bufs) throws IOException { /** * 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. + * current size of this file, then no bytes are transferred. * * @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) { + public long transferFrom(ReadableByteChannel src, long startPos, long count) throws IOException { + if (count == 0 + // Unlike the write() methods, attempting to transfer to a position that is greater than the + // current file size simply does nothing. + || startPos > size) { return 0; } long remaining = count; + long currentPos = startPos; - int blockIndex = blockIndex(pos); - byte[] block = blockForWrite(blockIndex); - int off = offsetInBlock(pos); - - ByteBuffer buf = ByteBuffer.wrap(block, off, length(off, remaining)); + int blockIndex = blockIndex(startPos); + int off = offsetInBlock(startPos); - 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); + outer: + while (remaining > 0) { + byte[] block = blockForWrite(blockIndex); - buf = ByteBuffer.wrap(block, 0, length(remaining)); - while (buf.hasRemaining()) { - read = src.read(buf); - if (read == -1) { - break outer; + ByteBuffer buf = ByteBuffer.wrap(block, off, length(off, remaining)); + while (buf.hasRemaining()) { + int read = src.read(buf); + // Note: we stop if we read 0 bytes from the src; even though the src is not at EOF, the + // spec of transferFrom is to stop immediately when reading from a non-blocking channel that + // has no bytes available rather than continuing until it reaches EOF. This makes sense + // because we'd otherwise just spin attempting to read bytes from the src repeatedly. + if (read < 1) { + if (currentPos >= size && buf.position() == 0) { + // The current position is at or beyond the end of file (prior to transfer start) and + // the current buffer position is 0. This means that a new block must have just been + // allocated to hold any potential transferred bytes, but no bytes were transferred to + // it because the src had no remaining bytes. So we need to de-allocate that block. + // It's possible that it would be preferable to always transfer to a temporary block + // first and then copy that block to a newly allocated block when it's full or src + // doesn't have any further bytes. Then if we hadn't read anything into the temporary + // block, we could simply discard it. But I think this scenario is likely rare enough + // that it's fine to temporarily allocate a block that _might_ not get used. + disk.free(this, 1); } - - currentPos += read; - remaining -= read; + break outer; } - if (currentPos > size) { - size = currentPos; - } + currentPos += read; + remaining -= read; } + + // Current write block is full + blockIndex++; + off = 0; } if (currentPos > size) { size = currentPos; } - return currentPos - pos; + return currentPos - startPos; } /** @@ -570,7 +579,7 @@ public long transferTo(long pos, long count, WritableByteChannel dest) throws IO while (buf.hasRemaining()) { remaining -= dest.write(buf); } - buf.clear(); + Java8Compatibility.clear(buf); while (remaining > 0) { int index = ++blockIndex; @@ -580,7 +589,7 @@ public long transferTo(long pos, long count, WritableByteChannel dest) throws IO while (buf.hasRemaining()) { remaining -= dest.write(buf); } - buf.clear(); + Java8Compatibility.clear(buf); } } @@ -638,10 +647,9 @@ private static int put(byte[] block, int offset, byte[] b, int off, int 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) { + private static void put(byte[] block, int offset, ByteBuffer buf) { int len = Math.min(block.length - offset, buf.remaining()); buf.get(block, offset, len); - return len; } /** diff --git a/jimfs/src/main/java/com/google/common/jimfs/StandardAttributeProviders.java b/jimfs/src/main/java/com/google/common/jimfs/StandardAttributeProviders.java index 973c6bb4..264a8990 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/StandardAttributeProviders.java +++ b/jimfs/src/main/java/com/google/common/jimfs/StandardAttributeProviders.java @@ -17,7 +17,7 @@ package com.google.common.jimfs; import com.google.common.collect.ImmutableMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Static registry of {@link AttributeProvider} implementations for the standard set of file @@ -43,8 +43,7 @@ private StandardAttributeProviders() {} * Returns the attribute provider for the given view, or {@code null} if the given view is not one * of the attribute views this supports. */ - @NullableDecl - public static AttributeProvider get(String view) { + public static @Nullable AttributeProvider get(String view) { AttributeProvider provider = PROVIDERS.get(view); if (provider == null && view.equals("unix")) { diff --git a/jimfs/src/main/java/com/google/common/jimfs/SymbolicLink.java b/jimfs/src/main/java/com/google/common/jimfs/SymbolicLink.java index 29f4aa55..c563d6a4 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/SymbolicLink.java +++ b/jimfs/src/main/java/com/google/common/jimfs/SymbolicLink.java @@ -18,6 +18,8 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.nio.file.attribute.FileTime; + /** * A symbolic link file, containing a {@linkplain JimfsPath path}. * @@ -28,12 +30,12 @@ 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); + public static SymbolicLink create(int id, FileTime creationTime, JimfsPath target) { + return new SymbolicLink(id, creationTime, target); } - private SymbolicLink(int id, JimfsPath target) { - super(id); + private SymbolicLink(int id, FileTime creationTime, JimfsPath target) { + super(id, creationTime); this.target = checkNotNull(target); } @@ -43,7 +45,7 @@ JimfsPath target() { } @Override - File copyWithoutContent(int id) { - return SymbolicLink.create(id, target); + File copyWithoutContent(int id, FileTime creationTime) { + return SymbolicLink.create(id, creationTime, target); } } diff --git a/jimfs/src/main/java/com/google/common/jimfs/SystemFileTimeSource.java b/jimfs/src/main/java/com/google/common/jimfs/SystemFileTimeSource.java new file mode 100644 index 00000000..05d71f08 --- /dev/null +++ b/jimfs/src/main/java/com/google/common/jimfs/SystemFileTimeSource.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021 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.FileTime; +import java.time.Instant; + +/** Implementation of of {@link FileTimeSource} that gets the current time from the system. */ +enum SystemFileTimeSource implements FileTimeSource { + INSTANCE; + + @Override + public FileTime now() { + return FileTime.from(Instant.now()); + } + + @Override + public String toString() { + return "SystemFileTimeSource"; + } +} diff --git a/jimfs/src/main/java/com/google/common/jimfs/SystemJimfsFileSystemProvider.java b/jimfs/src/main/java/com/google/common/jimfs/SystemJimfsFileSystemProvider.java index dcf3d02c..25bb4c95 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/SystemJimfsFileSystemProvider.java +++ b/jimfs/src/main/java/com/google/common/jimfs/SystemJimfsFileSystemProvider.java @@ -173,7 +173,7 @@ private static Path toPath(FileSystem fileSystem, URI uri) { Method toPath = fileSystem.getClass().getDeclaredMethod("toPath", URI.class); return (Path) toPath.invoke(fileSystem, uri); } catch (NoSuchMethodException e) { - throw new IllegalArgumentException("invalid file system: " + fileSystem); + throw new IllegalArgumentException("invalid file system: " + fileSystem, e); } catch (InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } diff --git a/jimfs/src/main/java/com/google/common/jimfs/UnixAttributeProvider.java b/jimfs/src/main/java/com/google/common/jimfs/UnixAttributeProvider.java index e3146434..6f793d7b 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/UnixAttributeProvider.java +++ b/jimfs/src/main/java/com/google/common/jimfs/UnixAttributeProvider.java @@ -21,7 +21,6 @@ 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; @@ -29,6 +28,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Attribute provider that provides the "unix" attribute view. @@ -96,7 +96,7 @@ private Integer getUniqueId(Object object) { @SuppressWarnings("unchecked") @Override - public Object get(File file, String attribute) { + public @Nullable Object get(File file, String attribute) { switch (attribute) { case "uid": UserPrincipal user = (UserPrincipal) file.getAttribute("owner", "owner"); @@ -109,7 +109,7 @@ public Object get(File file, String attribute) { (Set) file.getAttribute("posix", "permissions"); return toMode(permissions); case "ctime": - return FileTime.fromMillis(file.getCreationTime()); + return file.getCreationTime(); case "rdev": return 0L; case "dev": diff --git a/jimfs/src/main/java/com/google/common/jimfs/UnixPathType.java b/jimfs/src/main/java/com/google/common/jimfs/UnixPathType.java index 76f13395..35b7bcf7 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/UnixPathType.java +++ b/jimfs/src/main/java/com/google/common/jimfs/UnixPathType.java @@ -19,7 +19,7 @@ import static com.google.common.base.Preconditions.checkArgument; import java.nio.file.InvalidPathException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Unix-style path type. @@ -55,7 +55,7 @@ private static void checkValid(String path) { } @Override - public String toString(@NullableDecl String root, Iterable names) { + public String toString(@Nullable String root, Iterable names) { StringBuilder builder = new StringBuilder(); if (root != null) { builder.append(root); diff --git a/jimfs/src/main/java/com/google/common/jimfs/UserDefinedAttributeProvider.java b/jimfs/src/main/java/com/google/common/jimfs/UserDefinedAttributeProvider.java index 51cbfa69..1da028a1 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/UserDefinedAttributeProvider.java +++ b/jimfs/src/main/java/com/google/common/jimfs/UserDefinedAttributeProvider.java @@ -25,6 +25,7 @@ import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.UserDefinedFileAttributeView; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Attribute provider that provides the {@link UserDefinedFileAttributeView} ("user"). Unlike most @@ -69,7 +70,7 @@ private static ImmutableSet userDefinedAttributes(File file) { } @Override - public Object get(File file, String attribute) { + public @Nullable Object get(File file, String attribute) { Object value = file.getAttribute("user", attribute); if (value instanceof byte[]) { byte[] bytes = (byte[]) value; diff --git a/jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java b/jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java index 7cdf0c40..bfd92eac 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java +++ b/jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java @@ -20,7 +20,7 @@ import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Windows-style path type. @@ -134,8 +134,7 @@ private String parseUncRoot(String path, String original) { private static final Pattern DRIVE_LETTER_ROOT = Pattern.compile("^[a-zA-Z]:\\\\"); /** Parses a normal drive-letter root, e.g. "C:\". */ - @NullableDecl - private String parseDriveRoot(String path) { + private @Nullable String parseDriveRoot(String path) { Matcher drivePathMatcher = DRIVE_LETTER_ROOT.matcher(path); if (drivePathMatcher.find()) { return path.substring(drivePathMatcher.start(), drivePathMatcher.end()); @@ -160,7 +159,7 @@ private static boolean isReserved(char c) { } @Override - public String toString(@NullableDecl String root, Iterable names) { + public String toString(@Nullable String root, Iterable names) { StringBuilder builder = new StringBuilder(); if (root != null) { builder.append(root); diff --git a/jimfs/src/main/java/com/google/common/jimfs/package-info.java b/jimfs/src/main/java/com/google/common/jimfs/package-info.java index 47a75b0a..939aa876 100644 --- a/jimfs/src/main/java/com/google/common/jimfs/package-info.java +++ b/jimfs/src/main/java/com/google/common/jimfs/package-info.java @@ -19,7 +19,9 @@ * use the {@link com.google.common.jimfs.Jimfs Jimfs} and {@link * com.google.common.jimfs.Configuration Configuration} classes. */ +@CheckReturnValue @ParametersAreNonnullByDefault package com.google.common.jimfs; +import com.google.errorprone.annotations.CheckReturnValue; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/jimfs/src/test/java/com/google/common/jimfs/AbstractAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/AbstractAttributeProviderTest.java index 7e2bdf95..1b9a639b 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/AbstractAttributeProviderTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/AbstractAttributeProviderTest.java @@ -36,6 +36,8 @@ public abstract class AbstractAttributeProviderTest

protected static final ImmutableMap NO_INHERITED_VIEWS = ImmutableMap.of(); + protected final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); + protected P provider; protected File file; @@ -57,7 +59,7 @@ public File lookup() throws IOException { @Before public void setUp() { this.provider = createProvider(); - this.file = Directory.create(0); + this.file = Directory.create(0, fileTimeSource.now()); Map defaultValues = createDefaultValues(); setDefaultValues(file, provider, defaultValues); diff --git a/jimfs/src/test/java/com/google/common/jimfs/AbstractPathMatcherTest.java b/jimfs/src/test/java/com/google/common/jimfs/AbstractPathMatcherTest.java index 70ac0e99..141cc133 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/AbstractPathMatcherTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/AbstractPathMatcherTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.File; import java.io.IOException; import java.net.URI; @@ -33,7 +34,7 @@ import java.nio.file.WatchService; import java.util.Iterator; import java.util.regex.PatternSyntaxException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Abstract base class for tests of {@link PathMatcher} implementations. @@ -48,8 +49,7 @@ public abstract class AbstractPathMatcherTest { protected abstract PathMatcher matcher(String pattern); /** Override to return a real matcher for the given pattern. */ - @NullableDecl - protected PathMatcher realMatcher(String pattern) { + protected @Nullable PathMatcher realMatcher(String pattern) { return null; } @@ -77,13 +77,14 @@ protected final class PatternAsserter { private final PathMatcher matcher; - @NullableDecl private final PathMatcher realMatcher; + private final @Nullable PathMatcher realMatcher; PatternAsserter(String pattern) { this.matcher = matcher(pattern); this.realMatcher = realMatcher(pattern); } + @CanIgnoreReturnValue PatternAsserter matches(String... paths) { for (String path : paths) { assertTrue( @@ -98,6 +99,7 @@ PatternAsserter matches(String... paths) { return this; } + @CanIgnoreReturnValue PatternAsserter doesNotMatch(String... paths) { for (String path : paths) { assertFalse( diff --git a/jimfs/src/test/java/com/google/common/jimfs/AttributeServiceTest.java b/jimfs/src/test/java/com/google/common/jimfs/AttributeServiceTest.java index 80b01910..c7a12f4f 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/AttributeServiceTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/AttributeServiceTest.java @@ -43,6 +43,8 @@ public class AttributeServiceTest { private AttributeService service; + private final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); + @Before public void setUp() { ImmutableSet providers = @@ -53,6 +55,10 @@ public void setUp() { service = new AttributeService(providers, ImmutableMap.of()); } + private File createFile() { + return Directory.create(0, fileTimeSource.now()); + } + @Test public void testSupportedFileAttributeViews() { assertThat(service.supportedFileAttributeViews()) @@ -68,7 +74,7 @@ public void testSupportsFileAttributeView() { @Test public void testSetInitialAttributes() { - File file = Directory.create(0); + File file = createFile(); service.setInitialAttributes(file); assertThat(file.getAttributeNames("test")).containsExactly("bar", "baz"); @@ -81,7 +87,7 @@ public void testSetInitialAttributes() { @Test public void testGetAttribute() { - File file = Directory.create(0); + File file = createFile(); service.setInitialAttributes(file); assertThat(service.getAttribute(file, "test:foo")).isEqualTo("hello"); @@ -93,7 +99,7 @@ public void testGetAttribute() { @Test public void testGetAttribute_fromInheritedProvider() { - File file = Directory.create(0); + File file = createFile(); assertThat(service.getAttribute(file, "test:isRegularFile")).isEqualTo(false); assertThat(service.getAttribute(file, "test:isDirectory")).isEqualTo(true); assertThat(service.getAttribute(file, "test", "fileKey")).isEqualTo(0); @@ -101,7 +107,7 @@ public void testGetAttribute_fromInheritedProvider() { @Test public void testGetAttribute_failsForAttributesNotDefinedByProvider() { - File file = Directory.create(0); + File file = createFile(); try { service.getAttribute(file, "test:blah"); fail(); @@ -118,7 +124,7 @@ public void testGetAttribute_failsForAttributesNotDefinedByProvider() { @Test public void testSetAttribute() { - File file = Directory.create(0); + File file = createFile(); service.setAttribute(file, "test:bar", 10L, false); assertThat(file.getAttribute("test", "bar")).isEqualTo(10L); @@ -128,7 +134,7 @@ public void testSetAttribute() { @Test public void testSetAttribute_forInheritedProvider() { - File file = Directory.create(0); + File file = createFile(); service.setAttribute(file, "test:lastModifiedTime", FileTime.fromMillis(0), false); assertThat(file.getAttribute("test", "lastModifiedTime")).isNull(); assertThat(service.getAttribute(file, "basic:lastModifiedTime")) @@ -137,7 +143,7 @@ public void testSetAttribute_forInheritedProvider() { @Test public void testSetAttribute_withAlternateAcceptedType() { - File file = Directory.create(0); + File file = createFile(); service.setAttribute(file, "test:bar", 10F, false); assertThat(file.getAttribute("test", "bar")).isEqualTo(10L); @@ -147,14 +153,14 @@ public void testSetAttribute_withAlternateAcceptedType() { @Test public void testSetAttribute_onCreate() { - File file = Directory.create(0); + File file = createFile(); 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); + File file = createFile(); service.setInitialAttributes(file); try { @@ -175,7 +181,7 @@ public void testSetAttribute_failsForAttributesNotDefinedByProvider() { @Test public void testSetAttribute_failsForArgumentThatIsNotOfCorrectType() { - File file = Directory.create(0); + File file = createFile(); service.setInitialAttributes(file); try { service.setAttribute(file, "test:bar", "wrong", false); @@ -188,7 +194,7 @@ public void testSetAttribute_failsForArgumentThatIsNotOfCorrectType() { @Test public void testSetAttribute_failsForNullArgument() { - File file = Directory.create(0); + File file = createFile(); service.setInitialAttributes(file); try { service.setAttribute(file, "test:bar", null, false); @@ -201,7 +207,7 @@ public void testSetAttribute_failsForNullArgument() { @Test public void testSetAttribute_failsForAttributeThatIsNotSettable() { - File file = Directory.create(0); + File file = createFile(); try { service.setAttribute(file, "test:foo", "world", false); fail(); @@ -213,7 +219,7 @@ public void testSetAttribute_failsForAttributeThatIsNotSettable() { @Test public void testSetAttribute_onCreate_failsForAttributeThatIsNotSettableOnCreate() { - File file = Directory.create(0); + File file = createFile(); try { service.setInitialAttributes(file, new BasicFileAttribute<>("test:foo", "world")); fail(); @@ -232,7 +238,7 @@ public void testSetAttribute_onCreate_failsForAttributeThatIsNotSettableOnCreate @SuppressWarnings("ConstantConditions") @Test public void testGetFileAttributeView() throws IOException { - final File file = Directory.create(0); + final File file = createFile(); service.setInitialAttributes(file); FileLookup fileLookup = @@ -255,7 +261,7 @@ public File lookup() throws IOException { @Test public void testGetFileAttributeView_isNullForUnsupportedView() { - final File file = Directory.create(0); + final File file = createFile(); FileLookup fileLookup = new FileLookup() { @Override @@ -268,13 +274,13 @@ public File lookup() throws IOException { @Test public void testReadAttributes_asMap() { - File file = Directory.create(0); + File file = createFile(); 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"); + FileTime time = fileTimeSource.now(); map = service.readAttributes(file, "test:*"); assertThat(map) @@ -312,7 +318,7 @@ public void testReadAttributes_asMap() { @Test public void testReadAttributes_asMap_failsForInvalidAttributes() { - File file = Directory.create(0); + File file = createFile(); try { service.readAttributes(file, "basic:fileKey,isOther,*,creationTime"); fail(); @@ -330,7 +336,7 @@ public void testReadAttributes_asMap_failsForInvalidAttributes() { @Test public void testReadAttributes_asObject() { - File file = Directory.create(0); + File file = createFile(); service.setInitialAttributes(file); BasicFileAttributes basicAttrs = service.readAttributes(file, BasicFileAttributes.class); @@ -349,7 +355,7 @@ public void testReadAttributes_asObject() { @Test public void testReadAttributes_failsForUnsupportedAttributesType() { - File file = Directory.create(0); + File file = createFile(); try { service.readAttributes(file, PosixFileAttributes.class); fail(); @@ -359,7 +365,7 @@ public void testReadAttributes_failsForUnsupportedAttributesType() { @Test public void testIllegalAttributeFormats() { - File file = Directory.create(0); + File file = createFile(); try { service.getAttribute(file, ":bar"); fail(); diff --git a/jimfs/src/test/java/com/google/common/jimfs/BasicAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/BasicAttributeProviderTest.java index a101b789..5c72eeb6 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/BasicAttributeProviderTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/BasicAttributeProviderTest.java @@ -64,10 +64,10 @@ public void testSupportedAttributes() { @Test public void testInitialAttributes() { - long time = file.getCreationTime(); - assertThat(time).isNotEqualTo(0L); - assertThat(time).isEqualTo(file.getLastAccessTime()); - assertThat(time).isEqualTo(file.getLastModifiedTime()); + FileTime expected = fileTimeSource.now(); + assertThat(file.getCreationTime()).isEqualTo(expected); + assertThat(file.getLastAccessTime()).isEqualTo(expected); + assertThat(file.getLastModifiedTime()).isEqualTo(expected); assertContainsAll( file, @@ -121,22 +121,22 @@ public void testView() throws IOException { BasicFileAttributes attrs = view.readAttributes(); assertThat(attrs.fileKey()).isEqualTo(0); - FileTime time = attrs.creationTime(); - assertThat(attrs.lastAccessTime()).isEqualTo(time); - assertThat(attrs.lastModifiedTime()).isEqualTo(time); + FileTime initial = fileTimeSource.now(); + assertThat(attrs.creationTime()).isEqualTo(initial); + assertThat(attrs.lastAccessTime()).isEqualTo(initial); + assertThat(attrs.lastModifiedTime()).isEqualTo(initial); view.setTimes(null, null, null); - attrs = view.readAttributes(); - assertThat(attrs.creationTime()).isEqualTo(time); - assertThat(attrs.lastAccessTime()).isEqualTo(time); - assertThat(attrs.lastModifiedTime()).isEqualTo(time); + assertThat(attrs.creationTime()).isEqualTo(initial); + assertThat(attrs.lastAccessTime()).isEqualTo(initial); + assertThat(attrs.lastModifiedTime()).isEqualTo(initial); view.setTimes(FileTime.fromMillis(0L), null, null); attrs = view.readAttributes(); - assertThat(attrs.creationTime()).isEqualTo(time); - assertThat(attrs.lastAccessTime()).isEqualTo(time); + assertThat(attrs.creationTime()).isEqualTo(initial); + assertThat(attrs.lastAccessTime()).isEqualTo(initial); assertThat(attrs.lastModifiedTime()).isEqualTo(FileTime.fromMillis(0L)); } diff --git a/jimfs/src/test/java/com/google/common/jimfs/ByteBufferChannel.java b/jimfs/src/test/java/com/google/common/jimfs/ByteBufferChannel.java index 74289757..0041318e 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/ByteBufferChannel.java +++ b/jimfs/src/test/java/com/google/common/jimfs/ByteBufferChannel.java @@ -16,6 +16,7 @@ package com.google.common.jimfs; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; @@ -72,6 +73,7 @@ public long position() throws IOException { } @Override + @CanIgnoreReturnValue public SeekableByteChannel position(long newPosition) throws IOException { buffer.position((int) newPosition); return this; @@ -83,6 +85,7 @@ public long size() throws IOException { } @Override + @CanIgnoreReturnValue public SeekableByteChannel truncate(long size) throws IOException { buffer.limit((int) size); return this; diff --git a/jimfs/src/test/java/com/google/common/jimfs/ConfigurationTest.java b/jimfs/src/test/java/com/google/common/jimfs/ConfigurationTest.java index 0404f57f..19898c90 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/ConfigurationTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/ConfigurationTest.java @@ -70,6 +70,7 @@ public void testDefaultUnixConfiguration() { assertThat(config.attributeViews).containsExactly("basic"); assertThat(config.attributeProviders).isEmpty(); assertThat(config.defaultAttributeValues).isEmpty(); + assertThat(config.fileTimeSource).isEqualTo(SystemFileTimeSource.INSTANCE); } @Test @@ -104,6 +105,7 @@ public void testDefaultOsXConfiguration() { assertThat(config.attributeViews).containsExactly("basic"); assertThat(config.attributeProviders).isEmpty(); assertThat(config.defaultAttributeValues).isEmpty(); + assertThat(config.fileTimeSource).isEqualTo(SystemFileTimeSource.INSTANCE); } @Test @@ -143,6 +145,7 @@ public void testDefaultWindowsConfiguration() { assertThat(config.attributeViews).containsExactly("basic"); assertThat(config.attributeProviders).isEmpty(); assertThat(config.defaultAttributeValues).isEmpty(); + assertThat(config.fileTimeSource).isEqualTo(SystemFileTimeSource.INSTANCE); } @Test @@ -170,6 +173,7 @@ public void testFileSystemForDefaultWindowsConfiguration() throws IOException { public void testBuilder() { AttributeProvider unixProvider = StandardAttributeProviders.get("unix"); + FileTimeSource fileTimeSource = new FakeFileTimeSource(); Configuration config = Configuration.builder(PathType.unix()) .setRoots("/") @@ -184,6 +188,7 @@ public void testBuilder() { .addAttributeProvider(unixProvider) .setDefaultAttributeValue( "posix:permissions", PosixFilePermissions.fromString("---------")) + .setFileTimeSource(fileTimeSource) .build(); assertThat(config.pathType).isEqualTo(PathType.unix()); @@ -199,10 +204,12 @@ public void testBuilder() { assertThat(config.attributeProviders).containsExactly(unixProvider); assertThat(config.defaultAttributeValues) .containsEntry("posix:permissions", PosixFilePermissions.fromString("---------")); + assertThat(config.fileTimeSource).isEqualTo(fileTimeSource); } @Test public void testFileSystemForCustomConfiguration() throws IOException { + FileTimeSource fileTimeSource = new FakeFileTimeSource(); Configuration config = Configuration.builder(PathType.unix()) .setRoots("/") @@ -216,6 +223,7 @@ public void testFileSystemForCustomConfiguration() throws IOException { .setAttributeViews("unix") .setDefaultAttributeValue( "posix:permissions", PosixFilePermissions.fromString("---------")) + .setFileTimeSource(fileTimeSource) .build(); FileSystem fs = Jimfs.newFileSystem(config); @@ -230,6 +238,8 @@ public void testFileSystemForCustomConfiguration() throws IOException { Files.createFile(fs.getPath("/foo")); assertThat(Files.getAttribute(fs.getPath("/foo"), "posix:permissions")) .isEqualTo(PosixFilePermissions.fromString("---------")); + assertThat(Files.getAttribute(fs.getPath("/foo"), "creationTime")) + .isEqualTo(fileTimeSource.now()); try { Files.createFile(fs.getPath("/FOO")); diff --git a/jimfs/src/test/java/com/google/common/jimfs/DirectoryTest.java b/jimfs/src/test/java/com/google/common/jimfs/DirectoryTest.java index 1fee1e5c..ed097d8e 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/DirectoryTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/DirectoryTest.java @@ -28,7 +28,7 @@ import com.google.common.collect.Iterables; import java.util.HashSet; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,17 +42,23 @@ @RunWith(JUnit4.class) public class DirectoryTest { + private final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); + private Directory root; private Directory dir; @Before public void setUp() { - root = Directory.createRoot(0, Name.simple("/")); + root = Directory.createRoot(0, fileTimeSource.now(), Name.simple("/")); - dir = Directory.create(1); + dir = createDirectory(1); root.link(Name.simple("foo"), dir); } + private Directory createDirectory(int id) { + return Directory.create(id, fileTimeSource.now()); + } + @Test public void testRootDirectory() { assertThat(root.entryCount()).isEqualTo(3); // two for parent/self, one for dir @@ -82,7 +88,7 @@ public void testGet() { public void testLink() { assertThat(dir.get(Name.simple("bar"))).isNull(); - File bar = Directory.create(2); + File bar = createDirectory(2); dir.link(Name.simple("bar"), bar); assertThat(dir.get(Name.simple("bar"))).isEqualTo(entry(dir, "bar", bar)); @@ -91,7 +97,7 @@ public void testLink() { @Test public void testLink_existingNameFails() { try { - root.link(Name.simple("foo"), Directory.create(2)); + root.link(Name.simple("foo"), createDirectory(2)); fail(); } catch (IllegalArgumentException expected) { } @@ -100,13 +106,13 @@ public void testLink_existingNameFails() { @Test public void testLink_parentAndSelfNameFails() { try { - dir.link(Name.simple("."), Directory.create(2)); + dir.link(Name.simple("."), createDirectory(2)); fail(); } catch (IllegalArgumentException expected) { } try { - dir.link(Name.simple(".."), Directory.create(2)); + dir.link(Name.simple(".."), createDirectory(2)); fail(); } catch (IllegalArgumentException expected) { } @@ -114,7 +120,7 @@ public void testLink_parentAndSelfNameFails() { @Test public void testGet_normalizingCaseInsensitive() { - File bar = Directory.create(2); + File bar = createDirectory(2); Name barName = caseInsensitive("bar"); dir.link(barName, bar); @@ -161,7 +167,7 @@ public void testUnlink_parentAndSelfNameFails() { @Test public void testUnlink_normalizingCaseInsensitive() { - dir.link(caseInsensitive("bar"), Directory.create(2)); + dir.link(caseInsensitive("bar"), createDirectory(2)); assertThat(dir.get(caseInsensitive("bar"))).isNotNull(); @@ -172,7 +178,7 @@ public void testUnlink_normalizingCaseInsensitive() { @Test public void testLinkDirectory() { - Directory newDir = Directory.create(10); + Directory newDir = createDirectory(10); assertThat(newDir.entryInParent()).isNull(); assertThat(newDir.get(Name.SELF).file()).isEqualTo(newDir); @@ -191,7 +197,7 @@ public void testLinkDirectory() { @Test public void testUnlinkDirectory() { - Directory newDir = Directory.create(10); + Directory newDir = createDirectory(10); dir.link(Name.simple("foo"), newDir); @@ -215,15 +221,8 @@ public void testSnapshot() { root.link(Name.simple("bar"), regularFile(10)); root.link(Name.simple("abc"), regularFile(10)); - /* - * If we inline this into the assertThat call below, javac resolves it to assertThat(SortedSet), - * which isn't available publicly. Our internal build system considers that to be an error, even - * though the code will compile fine externally by resolving to assertThat(Iterable) instead. So - * we avoid that by assigning to a non-SortedSet type here. - */ - ImmutableSet snapshot = root.snapshot(); // does not include . or .. and is sorted by the name - assertThat(snapshot) + assertThat(root.snapshot()) .containsExactly(Name.simple("abc"), Name.simple("bar"), Name.simple("foo")) .inOrder(); } @@ -244,7 +243,7 @@ public void testSnapshot_sortsUsingStringAndNotCanonicalValueOfNames() { // Tests for internal hash table implementation - private static final Directory A = Directory.create(0); + private final Directory a = createDirectory(0); @Test public void testInitialState() { @@ -361,15 +360,15 @@ public void testManyPutsAndRemoves() { } } - private static DirectoryEntry entry(String name) { - return new DirectoryEntry(A, Name.simple(name), A); + private DirectoryEntry entry(String name) { + return new DirectoryEntry(a, Name.simple(name), a); } - private static DirectoryEntry entry(Directory dir, String name, @NullableDecl File file) { + private 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) { + private void assertParentAndSelf(Directory dir, File parent, File self) { assertThat(dir).isEqualTo(self); assertThat(dir.parent()).isEqualTo(parent); diff --git a/jimfs/src/test/java/com/google/common/jimfs/FakeFileTimeSource.java b/jimfs/src/test/java/com/google/common/jimfs/FakeFileTimeSource.java new file mode 100644 index 00000000..8fcb64b2 --- /dev/null +++ b/jimfs/src/test/java/com/google/common/jimfs/FakeFileTimeSource.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 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.errorprone.annotations.CanIgnoreReturnValue; +import java.nio.file.attribute.FileTime; +import java.time.Duration; +import java.time.Instant; +import java.util.Random; + +/** Fake implementation of {@link FileTimeSource}. */ +final class FakeFileTimeSource implements FileTimeSource { + + private final Random random = new Random(System.currentTimeMillis()); + private Instant now; + + FakeFileTimeSource() { + randomize(); + } + + @CanIgnoreReturnValue + FakeFileTimeSource randomize() { + now = + Instant.ofEpochSecond( + random + .longs(Instant.MIN.getEpochSecond(), Instant.MAX.getEpochSecond()) + .findAny() + .getAsLong(), + random.nextInt(1_000_000_000)); + return this; + } + + @CanIgnoreReturnValue + FakeFileTimeSource advance(Duration duration) { + this.now = now.plus(duration); + return this; + } + + @Override + public FileTime now() { + return FileTime.from(now); + } +} diff --git a/jimfs/src/test/java/com/google/common/jimfs/FileFactoryTest.java b/jimfs/src/test/java/com/google/common/jimfs/FileFactoryTest.java index 9e3cb403..ad2e0dc4 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/FileFactoryTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/FileFactoryTest.java @@ -31,11 +31,13 @@ @RunWith(JUnit4.class) public class FileFactoryTest { + private final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); + private FileFactory factory; @Before public void setUp() { - factory = new FileFactory(new HeapDisk(2, 2, 0)); + factory = new FileFactory(new HeapDisk(2, 2, 0), fileTimeSource); } @Test @@ -43,14 +45,19 @@ public void testCreateFiles_basic() { File file = factory.createDirectory(); assertThat(file.id()).isEqualTo(0L); assertThat(file.isDirectory()).isTrue(); + assertThat(file.getCreationTime()).isEqualTo(fileTimeSource.now()); + fileTimeSource.randomize(); file = factory.createRegularFile(); assertThat(file.id()).isEqualTo(1L); assertThat(file.isRegularFile()).isTrue(); + assertThat(file.getCreationTime()).isEqualTo(fileTimeSource.now()); + fileTimeSource.randomize(); file = factory.createSymbolicLink(fakePath()); assertThat(file.id()).isEqualTo(2L); assertThat(file.isSymbolicLink()).isTrue(); + assertThat(file.getCreationTime()).isEqualTo(fileTimeSource.now()); } @Test @@ -58,14 +65,19 @@ public void testCreateFiles_withSupplier() { File file = factory.directoryCreator().get(); assertThat(file.id()).isEqualTo(0L); assertThat(file.isDirectory()).isTrue(); + assertThat(file.getCreationTime()).isEqualTo(fileTimeSource.now()); + fileTimeSource.randomize(); file = factory.regularFileCreator().get(); assertThat(file.id()).isEqualTo(1L); assertThat(file.isRegularFile()).isTrue(); + assertThat(file.getCreationTime()).isEqualTo(fileTimeSource.now()); + fileTimeSource.randomize(); file = factory.symbolicLinkCreator(fakePath()).get(); assertThat(file.id()).isEqualTo(2L); assertThat(file.isSymbolicLink()).isTrue(); + assertThat(file.getCreationTime()).isEqualTo(fileTimeSource.now()); } static JimfsPath fakePath() { diff --git a/jimfs/src/test/java/com/google/common/jimfs/FileSystemStateTest.java b/jimfs/src/test/java/com/google/common/jimfs/FileSystemStateTest.java index f8143b6e..8ad5c51c 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/FileSystemStateTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/FileSystemStateTest.java @@ -16,6 +16,7 @@ package com.google.common.jimfs; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -26,6 +27,7 @@ import java.io.Closeable; import java.io.IOException; import java.nio.file.ClosedFileSystemException; +import java.time.Duration; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,7 +42,8 @@ public class FileSystemStateTest { private final TestRunnable onClose = new TestRunnable(); - private final FileSystemState state = new FileSystemState(onClose); + private final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); + private final FileSystemState state = new FileSystemState(fileTimeSource, onClose); @Test public void testIsOpen() throws IOException { @@ -60,6 +63,13 @@ public void testCheckOpen() throws IOException { } } + @Test + public void testNow() { + assertThat(state.now()).isEqualTo(fileTimeSource.now()); + fileTimeSource.advance(Duration.ofSeconds(1)); + assertThat(state.now()).isEqualTo(fileTimeSource.now()); + } + @Test public void testClose_callsOnCloseRunnable() throws IOException { assertEquals(0, onClose.runCount); diff --git a/jimfs/src/test/java/com/google/common/jimfs/FileTest.java b/jimfs/src/test/java/com/google/common/jimfs/FileTest.java index 83cda00e..d128c6a2 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/FileTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/FileTest.java @@ -32,12 +32,14 @@ @RunWith(JUnit4.class) public class FileTest { + private final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); + @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)); + File file = RegularFile.create(0, fileTimeSource.now(), new HeapDisk(10, 10, 10)); assertThat(file.getAttributeKeys()).isEmpty(); assertThat(file.getAttribute("foo", "foo")).isNull(); @@ -65,7 +67,7 @@ public void testFileBasics() { @Test public void testDirectory() { - File file = Directory.create(0); + File file = Directory.create(0, fileTimeSource.now()); assertThat(file.isDirectory()).isTrue(); assertThat(file.isRegularFile()).isFalse(); assertThat(file.isSymbolicLink()).isFalse(); @@ -81,7 +83,7 @@ public void testRegularFile() { @Test public void testSymbolicLink() { - File file = SymbolicLink.create(0, fakePath()); + File file = SymbolicLink.create(0, fileTimeSource.now(), fakePath()); assertThat(file.isDirectory()).isFalse(); assertThat(file.isRegularFile()).isFalse(); assertThat(file.isSymbolicLink()).isTrue(); @@ -89,10 +91,10 @@ public void testSymbolicLink() { @Test public void testRootDirectory() { - Directory file = Directory.createRoot(0, Name.simple("/")); + Directory file = Directory.createRoot(0, fileTimeSource.now(), Name.simple("/")); assertThat(file.isRootDirectory()).isTrue(); - Directory otherFile = Directory.createRoot(1, Name.simple("$")); + Directory otherFile = Directory.createRoot(1, fileTimeSource.now(), Name.simple("$")); assertThat(otherFile.isRootDirectory()).isTrue(); } diff --git a/jimfs/src/test/java/com/google/common/jimfs/FileTreeTest.java b/jimfs/src/test/java/com/google/common/jimfs/FileTreeTest.java index 54f590d2..55032d93 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/FileTreeTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/FileTreeTest.java @@ -25,13 +25,14 @@ import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.base.Strings; +import com.google.errorprone.annotations.CanIgnoreReturnValue; 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 org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -87,7 +88,7 @@ public ParseResult parsePath(String path) { } @Override - public String toString(@NullableDecl String root, Iterable names) { + public String toString(@Nullable String root, Iterable names) { root = Strings.nullToEmpty(root); return root + Joiner.on('/').join(names); } @@ -101,22 +102,26 @@ public String toUriPath(String root, Iterable names, boolean directory) @Override public ParseResult parseUriPath(String uriPath) { checkArgument( - uriPath.matches("^/[/$!].*"), "uriPath (%s) must start with // or /$ or /!"); + uriPath.matches("^/[/$!].*"), + "uriPath (%s) must start with // or /$ or /!", + uriPath); return parsePath(uriPath.substring(1)); // skip leading / } }, false); + private final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); + private FileTree fileTree; private File workingDirectory; private final Map files = new HashMap<>(); @Before public void setUp() { - Directory root = Directory.createRoot(0, Name.simple("/")); + Directory root = Directory.createRoot(0, fileTimeSource.now(), Name.simple("/")); files.put("/", root); - Directory otherRoot = Directory.createRoot(2, Name.simple("$")); + Directory otherRoot = Directory.createRoot(2, fileTimeSource.now(), Name.simple("$")); files.put("$", otherRoot); Map roots = new HashMap<>(); @@ -437,14 +442,16 @@ private void assertParentExists(DirectoryEntry entry, String parent) { } } + @CanIgnoreReturnValue private File createDirectory(String parent, String name) { Directory dir = (Directory) files.get(parent); - Directory newFile = Directory.create(new Random().nextInt()); + Directory newFile = Directory.create(new Random().nextInt(), fileTimeSource.now()); dir.link(Name.simple(name), newFile); files.put(name, newFile); return newFile; } + @CanIgnoreReturnValue private File createFile(String parent, String name) { Directory dir = (Directory) files.get(parent); File newFile = regularFile(0); @@ -453,9 +460,12 @@ private File createFile(String parent, String name) { return newFile; } + @CanIgnoreReturnValue 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)); + File newFile = + SymbolicLink.create( + new Random().nextInt(), fileTimeSource.now(), pathService.parsePath(target)); dir.link(Name.simple(name), newFile); files.put(name, newFile); return newFile; diff --git a/jimfs/src/test/java/com/google/common/jimfs/HeapDiskTest.java b/jimfs/src/test/java/com/google/common/jimfs/HeapDiskTest.java index af09b85e..48235893 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/HeapDiskTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/HeapDiskTest.java @@ -35,12 +35,14 @@ @RunWith(JUnit4.class) public class HeapDiskTest { + private final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); + 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)); + blocks = RegularFile.create(-1, fileTimeSource.now(), new HeapDisk(2, 2, 2)); } @Test @@ -216,7 +218,7 @@ public void testFullDisk_doesNotAllocatePartiallyWhenTooManyBlocksRequested() th HeapDisk disk = new HeapDisk(4, 10, 4); disk.allocate(blocks, 6); - RegularFile blocks2 = RegularFile.create(-2, disk); + RegularFile blocks2 = RegularFile.create(-2, fileTimeSource.now(), disk); try { disk.allocate(blocks2, 5); diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java index 7d47588a..a4a4ba91 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java @@ -63,7 +63,7 @@ private static JimfsAsynchronousFileChannel channel( new JimfsFileChannel( file, Options.getOptionsForChannel(ImmutableSet.copyOf(options)), - new FileSystemState(Runnables.doNothing())); + new FileSystemState(new FakeFileTimeSource(), Runnables.doNothing())); return new JimfsAsynchronousFileChannel(channel, executor); } diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java index c525ef1f..295dd7c1 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java @@ -48,6 +48,8 @@ import java.nio.channels.NonReadableChannelException; import java.nio.channels.NonWritableChannelException; import java.nio.file.OpenOption; +import java.nio.file.attribute.FileTime; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; @@ -74,7 +76,7 @@ private static FileChannel channel(RegularFile file, OpenOption... options) thro return new JimfsFileChannel( file, Options.getOptionsForChannel(ImmutableSet.copyOf(options)), - new FileSystemState(Runnables.doNothing())); + new FileSystemState(new FakeFileTimeSource(), Runnables.doNothing())); } @Test @@ -227,76 +229,77 @@ public void testTruncate() throws IOException { @Test public void testFileTimeUpdates() throws IOException { RegularFile file = regularFile(10); + FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); FileChannel channel = new JimfsFileChannel( file, ImmutableSet.of(READ, WRITE), - new FileSystemState(Runnables.doNothing())); + new FileSystemState(fileTimeSource, Runnables.doNothing())); - // accessed - long accessTime = file.getLastAccessTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + // accessedTime + FileTime accessTime = file.getLastAccessTime(); + fileTimeSource.advance(Duration.ofMillis(2)); channel.read(ByteBuffer.allocate(10)); assertNotEquals(accessTime, file.getLastAccessTime()); accessTime = file.getLastAccessTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + fileTimeSource.advance(Duration.ofMillis(2)); channel.read(ByteBuffer.allocate(10), 0); assertNotEquals(accessTime, file.getLastAccessTime()); accessTime = file.getLastAccessTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + fileTimeSource.advance(Duration.ofMillis(2)); channel.read(new ByteBuffer[] {ByteBuffer.allocate(10)}); assertNotEquals(accessTime, file.getLastAccessTime()); accessTime = file.getLastAccessTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + fileTimeSource.advance(Duration.ofMillis(2)); channel.read(new ByteBuffer[] {ByteBuffer.allocate(10)}, 0, 1); assertNotEquals(accessTime, file.getLastAccessTime()); accessTime = file.getLastAccessTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + fileTimeSource.advance(Duration.ofMillis(2)); channel.transferTo(0, 10, new ByteBufferChannel(10)); assertNotEquals(accessTime, file.getLastAccessTime()); // modified - long modifiedTime = file.getLastModifiedTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + FileTime modifiedTime = file.getLastModifiedTime(); + fileTimeSource.advance(Duration.ofMillis(2)); channel.write(ByteBuffer.allocate(10)); assertNotEquals(modifiedTime, file.getLastModifiedTime()); modifiedTime = file.getLastModifiedTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + fileTimeSource.advance(Duration.ofMillis(2)); channel.write(ByteBuffer.allocate(10), 0); assertNotEquals(modifiedTime, file.getLastModifiedTime()); modifiedTime = file.getLastModifiedTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + fileTimeSource.advance(Duration.ofMillis(2)); channel.write(new ByteBuffer[] {ByteBuffer.allocate(10)}); assertNotEquals(modifiedTime, file.getLastModifiedTime()); modifiedTime = file.getLastModifiedTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + fileTimeSource.advance(Duration.ofMillis(2)); channel.write(new ByteBuffer[] {ByteBuffer.allocate(10)}, 0, 1); assertNotEquals(modifiedTime, file.getLastModifiedTime()); modifiedTime = file.getLastModifiedTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + fileTimeSource.advance(Duration.ofMillis(2)); channel.truncate(0); assertNotEquals(modifiedTime, file.getLastModifiedTime()); modifiedTime = file.getLastModifiedTime(); - Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS); + fileTimeSource.advance(Duration.ofMillis(2)); channel.transferFrom(new ByteBufferChannel(10), 0, 10); assertNotEquals(modifiedTime, file.getLastModifiedTime()); diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsInputStreamTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsInputStreamTest.java index 94cc5f47..8a2947e1 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/JimfsInputStreamTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsInputStreamTest.java @@ -228,7 +228,8 @@ private static JimfsInputStream newInputStream(int... bytes) throws IOException RegularFile file = regularFile(0); file.write(0, b, 0, b.length); - return new JimfsInputStream(file, new FileSystemState(Runnables.doNothing())); + return new JimfsInputStream( + file, new FileSystemState(new FakeFileTimeSource(), Runnables.doNothing())); } private static void assertEmpty(JimfsInputStream in) throws IOException { diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsOutputStreamTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsOutputStreamTest.java index 3c230a74..4a8b1690 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/JimfsOutputStreamTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsOutputStreamTest.java @@ -184,7 +184,8 @@ public void testClosedOutputStream_doesNotThrowOnFlush() throws IOException { private static JimfsOutputStream newOutputStream(boolean append) { RegularFile file = regularFile(0); - return new JimfsOutputStream(file, append, new FileSystemState(Runnables.doNothing())); + return new JimfsOutputStream( + file, append, new FileSystemState(new FakeFileTimeSource(), Runnables.doNothing())); } @SuppressWarnings("GuardedByChecker") @@ -199,7 +200,7 @@ private static void addBytesToStore(JimfsOutputStream out, int... bytes) throws @SuppressWarnings("GuardedByChecker") private static void assertStoreContains(JimfsOutputStream out, int... bytes) { byte[] actualBytes = new byte[bytes.length]; - out.file.read(0, actualBytes, 0, actualBytes.length); + int unused = out.file.read(0, actualBytes, 0, actualBytes.length); assertArrayEquals(bytes(bytes), actualBytes); } } diff --git a/jimfs/src/test/java/com/google/common/jimfs/PathSubject.java b/jimfs/src/test/java/com/google/common/jimfs/PathSubject.java index f6927a5e..991b1286 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/PathSubject.java +++ b/jimfs/src/test/java/com/google/common/jimfs/PathSubject.java @@ -26,6 +26,7 @@ import com.google.common.io.BaseEncoding; import com.google.common.truth.FailureMetadata; import com.google.common.truth.Subject; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.DirectoryStream; @@ -36,7 +37,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Subject for doing assertions on file system paths. @@ -67,11 +68,13 @@ private Path toPath(String path) { } /** Returns this, for readability of chained assertions. */ + @CanIgnoreReturnValue public PathSubject and() { return this; } /** Do not follow links when looking up the path. */ + @CanIgnoreReturnValue public PathSubject noFollowLinks() { this.linkOptions = NOFOLLOW_LINKS; return this; @@ -81,12 +84,14 @@ public PathSubject noFollowLinks() { * Set the given charset to be used when reading the file at this path as text. Default charset if * not set is UTF-8. */ + @CanIgnoreReturnValue public PathSubject withCharset(Charset charset) { this.charset = checkNotNull(charset); return this; } /** Asserts that the path is absolute (it has a root component). */ + @CanIgnoreReturnValue public PathSubject isAbsolute() { if (!actual.isAbsolute()) { failWithActual(simpleFact("expected to be absolute")); @@ -95,6 +100,7 @@ public PathSubject isAbsolute() { } /** Asserts that the path is relative (it has no root component). */ + @CanIgnoreReturnValue public PathSubject isRelative() { if (actual.isAbsolute()) { failWithActual(simpleFact("expected to be relative")); @@ -103,7 +109,8 @@ public PathSubject isRelative() { } /** Asserts that the path has the given root component. */ - public PathSubject hasRootComponent(@NullableDecl String root) { + @CanIgnoreReturnValue + public PathSubject hasRootComponent(@Nullable String root) { Path rootComponent = actual.getRoot(); if (root == null && rootComponent != null) { failWithActual("expected to have root component", root); @@ -114,12 +121,14 @@ public PathSubject hasRootComponent(@NullableDecl String root) { } /** Asserts that the path has no name components. */ + @CanIgnoreReturnValue public PathSubject hasNoNameComponents() { check("getNameCount()").that(actual.getNameCount()).isEqualTo(0); return this; } /** Asserts that the path has the given name components. */ + @CanIgnoreReturnValue public PathSubject hasNameComponents(String... names) { ImmutableList.Builder builder = ImmutableList.builder(); for (Path name : actual) { @@ -133,6 +142,7 @@ public PathSubject hasNameComponents(String... names) { } /** Asserts that the path matches the given syntax and pattern. */ + @CanIgnoreReturnValue public PathSubject matches(String syntaxAndPattern) { PathMatcher matcher = actual.getFileSystem().getPathMatcher(syntaxAndPattern); if (!matcher.matches(actual)) { @@ -142,6 +152,7 @@ public PathSubject matches(String syntaxAndPattern) { } /** Asserts that the path does not match the given syntax and pattern. */ + @CanIgnoreReturnValue public PathSubject doesNotMatch(String syntaxAndPattern) { PathMatcher matcher = actual.getFileSystem().getPathMatcher(syntaxAndPattern); if (matcher.matches(actual)) { @@ -151,6 +162,7 @@ public PathSubject doesNotMatch(String syntaxAndPattern) { } /** Asserts that the path exists. */ + @CanIgnoreReturnValue public PathSubject exists() { if (!Files.exists(actual, linkOptions)) { failWithActual(simpleFact("expected to exist")); @@ -162,6 +174,7 @@ public PathSubject exists() { } /** Asserts that the path does not exist. */ + @CanIgnoreReturnValue public PathSubject doesNotExist() { if (!Files.notExists(actual, linkOptions)) { failWithActual(simpleFact("expected not to exist")); @@ -173,6 +186,7 @@ public PathSubject doesNotExist() { } /** Asserts that the path is a directory. */ + @CanIgnoreReturnValue public PathSubject isDirectory() { exists(); // check for directoryness should imply check for existence @@ -183,6 +197,7 @@ public PathSubject isDirectory() { } /** Asserts that the path is a regular file. */ + @CanIgnoreReturnValue public PathSubject isRegularFile() { exists(); // check for regular fileness should imply check for existence @@ -193,6 +208,7 @@ public PathSubject isRegularFile() { } /** Asserts that the path is a symbolic link. */ + @CanIgnoreReturnValue public PathSubject isSymbolicLink() { exists(); // check for symbolic linkness should imply check for existence @@ -203,6 +219,7 @@ public PathSubject isSymbolicLink() { } /** Asserts that the path, which is a symbolic link, has the given path as a target. */ + @CanIgnoreReturnValue public PathSubject withTarget(String targetPath) throws IOException { Path actualTarget = Files.readSymbolicLink(actual); if (!actualTarget.equals(toPath(targetPath))) { @@ -218,6 +235,7 @@ public PathSubject withTarget(String targetPath) throws IOException { * 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. */ + @CanIgnoreReturnValue public PathSubject hasLinkCount(int count) throws IOException { exists(); @@ -229,11 +247,13 @@ public PathSubject hasLinkCount(int count) throws IOException { } /** Asserts that the path resolves to the same file as the given path. */ + @CanIgnoreReturnValue public PathSubject isSameFileAs(String path) throws IOException { return isSameFileAs(toPath(path)); } /** Asserts that the path resolves to the same file as the given path. */ + @CanIgnoreReturnValue public PathSubject isSameFileAs(Path path) throws IOException { if (!Files.isSameFile(actual, path)) { failWithActual("expected to be same file as", path); @@ -242,6 +262,7 @@ public PathSubject isSameFileAs(Path path) throws IOException { } /** Asserts that the path does not resolve to the same file as the given path. */ + @CanIgnoreReturnValue public PathSubject isNotSameFileAs(String path) throws IOException { if (Files.isSameFile(actual, toPath(path))) { failWithActual("expected not to be same file as", path); @@ -250,6 +271,7 @@ public PathSubject isNotSameFileAs(String path) throws IOException { } /** Asserts that the directory has no children. */ + @CanIgnoreReturnValue public PathSubject hasNoChildren() throws IOException { isDirectory(); @@ -262,6 +284,7 @@ public PathSubject hasNoChildren() throws IOException { } /** Asserts that the directory has children with the given names, in the given order. */ + @CanIgnoreReturnValue public PathSubject hasChildren(String... children) throws IOException { isDirectory(); @@ -287,6 +310,7 @@ public PathSubject hasChildren(String... children) throws IOException { } /** Asserts that the file has the given size. */ + @CanIgnoreReturnValue public PathSubject hasSize(long size) throws IOException { if (Files.size(actual) != size) { failWithActual("expected to have size", size); @@ -295,6 +319,7 @@ public PathSubject hasSize(long size) throws IOException { } /** Asserts that the file is a regular file containing no bytes. */ + @CanIgnoreReturnValue public PathSubject containsNoBytes() throws IOException { return containsBytes(new byte[0]); } @@ -302,6 +327,7 @@ public PathSubject containsNoBytes() throws IOException { /** * Asserts that the file is a regular file containing exactly the byte values of the given ints. */ + @CanIgnoreReturnValue public PathSubject containsBytes(int... bytes) throws IOException { byte[] realBytes = new byte[bytes.length]; for (int i = 0; i < bytes.length; i++) { @@ -311,6 +337,7 @@ public PathSubject containsBytes(int... bytes) throws IOException { } /** Asserts that the file is a regular file containing exactly the given bytes. */ + @CanIgnoreReturnValue public PathSubject containsBytes(byte[] bytes) throws IOException { isRegularFile(); hasSize(bytes.length); @@ -328,6 +355,7 @@ public PathSubject containsBytes(byte[] bytes) throws IOException { * Asserts that the file is a regular file containing the same bytes as the regular file at the * given path. */ + @CanIgnoreReturnValue public PathSubject containsSameBytesAs(String path) throws IOException { isRegularFile(); @@ -342,6 +370,7 @@ public PathSubject containsSameBytesAs(String path) throws IOException { * 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)}. */ + @CanIgnoreReturnValue public PathSubject containsLines(String... lines) throws IOException { return containsLines(Arrays.asList(lines)); } @@ -350,6 +379,7 @@ public PathSubject containsLines(String... lines) throws IOException { * 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)}. */ + @CanIgnoreReturnValue public PathSubject containsLines(Iterable lines) throws IOException { isRegularFile(); @@ -395,9 +425,11 @@ public PathSubject createSubject(FailureMetadata failureMetadata, Path that) { public interface Attribute { /** Asserts that the value of this attribute is equal to the given value. */ + @CanIgnoreReturnValue Attribute is(Object value) throws IOException; /** Asserts that the value of this attribute is not equal to the given value. */ + @CanIgnoreReturnValue Attribute isNot(Object value) throws IOException; /** Returns the path subject for further chaining. */ diff --git a/jimfs/src/test/java/com/google/common/jimfs/PathTester.java b/jimfs/src/test/java/com/google/common/jimfs/PathTester.java index b96d77e1..54a56b5d 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/PathTester.java +++ b/jimfs/src/test/java/com/google/common/jimfs/PathTester.java @@ -27,6 +27,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -44,16 +45,19 @@ public PathTester(PathService pathService, String string) { this.string = string; } + @CanIgnoreReturnValue public PathTester root(String root) { this.root = root; return this; } + @CanIgnoreReturnValue public PathTester names(Iterable names) { this.names = ImmutableList.copyOf(names); return this; } + @CanIgnoreReturnValue public PathTester names(String... names) { return names(Arrays.asList(names)); } @@ -108,7 +112,7 @@ private void testNames(Path path) { private void testParents(Path path) { Path parent = path.getParent(); - if (root != null && names.size() >= 1 || names.size() > 1) { + if ((root != null && names.size() >= 1) || names.size() > 1) { assertNotNull(parent); } diff --git a/jimfs/src/test/java/com/google/common/jimfs/PathTypeTest.java b/jimfs/src/test/java/com/google/common/jimfs/PathTypeTest.java index 59fc1142..53e08fe7 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/PathTypeTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/PathTypeTest.java @@ -17,12 +17,12 @@ 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 com.google.common.jimfs.PathType.ParseResult; import java.net.URI; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -94,7 +94,7 @@ public void testUriRoundTrips() { assertUriRoundTripsCorrectly(type, "$foo/bar baz"); } - static void assertParseResult(ParseResult result, @NullableDecl String root, String... names) { + static void assertParseResult(ParseResult result, @Nullable String root, String... names) { assertThat(result.root()).isEqualTo(root); assertThat(result.names()).containsExactly((Object[]) names).inOrder(); } @@ -126,7 +126,7 @@ public ParseResult parsePath(String path) { } @Override - public String toString(@NullableDecl String root, Iterable names) { + public String toString(@Nullable String root, Iterable names) { StringBuilder builder = new StringBuilder(); if (root != null) { builder.append(root); diff --git a/jimfs/src/test/java/com/google/common/jimfs/PollingWatchServiceTest.java b/jimfs/src/test/java/com/google/common/jimfs/PollingWatchServiceTest.java index 86f98323..407f70dd 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/PollingWatchServiceTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/PollingWatchServiceTest.java @@ -62,7 +62,7 @@ public void setUp() { new PollingWatchService( fs.getDefaultView(), fs.getPathService(), - new FileSystemState(Runnables.doNothing()), + new FileSystemState(new FakeFileTimeSource(), Runnables.doNothing()), 4, MILLISECONDS); } diff --git a/jimfs/src/test/java/com/google/common/jimfs/RegularFileBlocksTest.java b/jimfs/src/test/java/com/google/common/jimfs/RegularFileBlocksTest.java index 1dc9df22..22697ffc 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/RegularFileBlocksTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/RegularFileBlocksTest.java @@ -19,6 +19,9 @@ import static com.google.common.truth.Truth.assertThat; import com.google.common.primitives.Bytes; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.channels.Channels; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,6 +35,10 @@ @RunWith(JUnit4.class) public class RegularFileBlocksTest { + private static final int BLOCK_SIZE = 2; + + private final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); + private RegularFile file; @Before @@ -39,8 +46,8 @@ public void setUp() { file = createFile(); } - private static RegularFile createFile() { - return RegularFile.create(-1, new HeapDisk(2, 2, 2)); + private RegularFile createFile() { + return RegularFile.create(-1, fileTimeSource.now(), new HeapDisk(BLOCK_SIZE, 2, 2)); } @Test @@ -142,4 +149,33 @@ public void testTransferTo() { assertThat(Bytes.asList(file.getBlock(0))).isEqualTo(Bytes.asList(new byte[] {1, 2, 3})); assertThat(file.getBlock(1)).isNull(); } + + @Test + public void testTransferFrom() throws IOException { + // Test that when a transferFrom ends on a block boundary because the input has no further bytes + // and not because count bytes have been transferred, we don't leave an extra empty block + // allocated on the end of the file. + // https://github.com/google/jimfs/issues/163 + byte[] bytes = new byte[BLOCK_SIZE]; + RegularFile file = createFile(); + + long transferred = + file.transferFrom(Channels.newChannel(new ByteArrayInputStream(bytes)), 0, Long.MAX_VALUE); + assertThat(transferred).isEqualTo(bytes.length); + assertThat(file.blockCount()).isEqualTo(1); + } + + @Test + public void testTransferFrom_noBytesNoAllocation() throws IOException { + // Similar to the previous test but ensures that if no bytes are transferred at all, no new + // blocks remain allocated. + // https://github.com/google/jimfs/issues/163 + byte[] bytes = new byte[0]; + RegularFile file = createFile(); + + long transferred = + file.transferFrom(Channels.newChannel(new ByteArrayInputStream(bytes)), 0, Long.MAX_VALUE); + assertThat(transferred).isEqualTo(0); + assertThat(file.blockCount()).isEqualTo(0); + } } diff --git a/jimfs/src/test/java/com/google/common/jimfs/RegularFileTest.java b/jimfs/src/test/java/com/google/common/jimfs/RegularFileTest.java index f581690b..77d756d3 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/RegularFileTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/RegularFileTest.java @@ -121,6 +121,8 @@ public static final class TestConfiguration { private final int cacheSize; private final ReuseStrategy reuseStrategy; + private final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); + private HeapDisk disk; public TestConfiguration(int blockSize, int cacheSize, ReuseStrategy reuseStrategy) { @@ -142,7 +144,7 @@ public RegularFile createRegularFile() { if (reuseStrategy == ReuseStrategy.NEW_DISK) { disk = createDisk(); } - return RegularFile.create(0, disk); + return RegularFile.create(0, fileTimeSource.now(), disk); } public void tearDown(RegularFile file) { @@ -172,6 +174,7 @@ public String toString() { public static class RegularFileTestRunner extends TestCase { private final TestConfiguration configuration; + private final FakeFileTimeSource fileTimeSource = new FakeFileTimeSource(); protected RegularFile file; @@ -334,23 +337,31 @@ public void testEmpty_transferFrom_fromStart_countGreaterThanSrcSize() throws IO assertContentEquals("111111", file); } - public void testEmpty_transferFrom_fromBeyondStart_countEqualsSrcSize() throws IOException { + public void testEmpty_transferFrom_positionGreaterThanSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 4, 6); - assertEquals(6, transferred); - assertContentEquals("0000111111", file); + assertEquals(0, transferred); + assertContentEquals(bytes(), file); } - public void testEmpty_transferFrom_fromBeyondStart_countLessThanSrcSize() throws IOException { + public void testEmpty_transferFrom_positionGreaterThanSize_countEqualsSrcSize() + throws IOException { + long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 4, 6); + assertEquals(0, transferred); + assertContentEquals(bytes(), file); + } + + public void testEmpty_transferFrom_positionGreaterThanSize_countLessThanSrcSize() + throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 4, 3); - assertEquals(3, transferred); - assertContentEquals("0000111", file); + assertEquals(0, transferred); + assertContentEquals(bytes(), file); } - public void testEmpty_transferFrom_fromBeyondStart_countGreaterThanSrcSize() + public void testEmpty_transferFrom_positionGreaterThanSize_countGreaterThanSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 4, 12); - assertEquals(6, transferred); - assertContentEquals("0000111111", file); + assertEquals(0, transferred); + assertContentEquals(bytes(), file); } public void testEmpty_transferFrom_fromStart_noBytes_countEqualsSrcSize() throws IOException { @@ -366,18 +377,18 @@ public void testEmpty_transferFrom_fromStart_noBytes_countGreaterThanSrcSize() assertContentEquals(bytes(), file); } - public void testEmpty_transferFrom_fromBeyondStart_noBytes_countEqualsSrcSize() + public void testEmpty_transferFrom_postionGreaterThanSrcSize_noBytes_countEqualsSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("")), 5, 0); assertEquals(0, transferred); - assertContentEquals(bytes("00000"), file); + assertContentEquals(bytes(), file); } - public void testEmpty_transferFrom_fromBeyondStart_noBytes_countGreaterThanSrcSize() + public void testEmpty_transferFrom_postionGreaterThanSrcSize_noBytes_countGreaterThanSrcSize() throws IOException { long transferred = file.transferFrom(new ByteBufferChannel(buffer("")), 5, 10); assertEquals(0, transferred); - assertContentEquals(bytes("00000"), file); + assertContentEquals(bytes(), file); } public void testEmpty_transferTo() throws IOException { @@ -386,7 +397,7 @@ public void testEmpty_transferTo() throws IOException { } public void testEmpty_copy() throws IOException { - RegularFile copy = file.copyWithoutContent(1); + RegularFile copy = file.copyWithoutContent(1, fileTimeSource.now()); assertContentEquals("", copy); } @@ -565,7 +576,7 @@ public void testNonEmpty_read_fromPastEnd_byteArray() throws IOException { public void testNonEmpty_read_fromPastEnd_singleBuffer() throws IOException { fillContent("123"); ByteBuffer buffer = ByteBuffer.allocate(3); - file.read(3, buffer); + assertEquals(-1, file.read(3, buffer)); assertBufferEquals("000", 3, buffer); } @@ -793,11 +804,11 @@ public void testNonEmpty_transferFrom_toEnd() throws IOException { assertContentEquals("222222111111", file); } - public void testNonEmpty_transferFrom_toPastEnd() throws IOException { + public void testNonEmpty_transferFrom_positionGreaterThanSize() throws IOException { fillContent("222222"); ByteBufferChannel channel = new ByteBufferChannel(buffer("111111")); - assertEquals(6, file.transferFrom(channel, 10, 6)); - assertContentEquals("2222220000111111", file); + assertEquals(0, file.transferFrom(channel, 10, 6)); + assertContentEquals("222222", file); } public void testNonEmpty_transferFrom_hugeOverestimateCount() throws IOException { @@ -809,16 +820,16 @@ public void testNonEmpty_transferFrom_hugeOverestimateCount() throws IOException public void testNonEmpty_copy() throws IOException { fillContent("123456"); - RegularFile copy = file.copyWithoutContent(1); + RegularFile copy = file.copyWithoutContent(1, fileTimeSource.now()); file.copyContentTo(copy); assertContentEquals("123456", copy); } public void testNonEmpty_copy_multipleTimes() throws IOException { fillContent("123456"); - RegularFile copy = file.copyWithoutContent(1); + RegularFile copy = file.copyWithoutContent(1, fileTimeSource.now()); file.copyContentTo(copy); - RegularFile copy2 = copy.copyWithoutContent(2); + RegularFile copy2 = copy.copyWithoutContent(2, fileTimeSource.now()); copy.copyContentTo(copy2); assertContentEquals("123456", copy); } @@ -885,7 +896,7 @@ private static void assertContentEquals(String expected, RegularFile 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)); + int unused = actual.read(0, ByteBuffer.wrap(actualBytes)); assertArrayEquals(expected, actualBytes); } } diff --git a/jimfs/src/test/java/com/google/common/jimfs/TestAttributeProvider.java b/jimfs/src/test/java/com/google/common/jimfs/TestAttributeProvider.java index 4518132a..b5360477 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/TestAttributeProvider.java +++ b/jimfs/src/test/java/com/google/common/jimfs/TestAttributeProvider.java @@ -26,7 +26,7 @@ import java.nio.file.attribute.FileTime; import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.checkerframework.checker.nullness.qual.Nullable; /** @author Colin Decker */ public final class TestAttributeProvider extends AttributeProvider { @@ -133,9 +133,9 @@ public Attributes readAttributes() throws IOException { @Override public void setTimes( - @NullableDecl FileTime lastModifiedTime, - @NullableDecl FileTime lastAccessTime, - @NullableDecl FileTime createTime) + @Nullable FileTime lastModifiedTime, + @Nullable FileTime lastAccessTime, + @Nullable FileTime createTime) throws IOException { basicView.setTimes(lastModifiedTime, lastAccessTime, createTime); } diff --git a/jimfs/src/test/java/com/google/common/jimfs/TestUtils.java b/jimfs/src/test/java/com/google/common/jimfs/TestUtils.java index 30e0930b..61e138e9 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/TestUtils.java +++ b/jimfs/src/test/java/com/google/common/jimfs/TestUtils.java @@ -133,7 +133,8 @@ public static void assertNotEquals(Object unexpected, Object actual) { } static RegularFile regularFile(int size) { - RegularFile file = RegularFile.create(0, new HeapDisk(8096, 1000, 1000)); + RegularFile file = + RegularFile.create(0, new FakeFileTimeSource().now(), new HeapDisk(8096, 1000, 1000)); try { file.write(0, new byte[size], 0, size); return file; diff --git a/jimfs/src/test/java/com/google/common/jimfs/UnixAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/UnixAttributeProviderTest.java index dc32d202..e0c4608e 100644 --- a/jimfs/src/test/java/com/google/common/jimfs/UnixAttributeProviderTest.java +++ b/jimfs/src/test/java/com/google/common/jimfs/UnixAttributeProviderTest.java @@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableSet; -import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFilePermissions; import java.util.Set; import org.junit.Test; @@ -67,7 +66,7 @@ public void testInitialAttributes() { // 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())); + assertThat(provider.get(file, "ctime")).isEqualTo(file.getCreationTime()); // this is based on a property this file system does actually have assertThat(provider.get(file, "nlink")).isEqualTo(1); diff --git a/pom.xml b/pom.xml index eed6efd3..c7b82dcb 100644 --- a/pom.xml +++ b/pom.xml @@ -94,17 +94,11 @@ UTF-8 - 1.0-rc7 - 1.7 - 30.1-android - - 2.4.0 - 9+181-r4173-1 + 1.1.1 + 1.2.2 + 1.8 + 33.2.1-jre + 2.28.0 @@ -120,7 +114,7 @@ com.ibm.icu icu4j - 68.2 + 75.1 @@ -136,15 +130,15 @@ org.checkerframework - checker-compat-qual - 2.5.5 + checker-qual + 3.45.0 junit junit - 4.13.1 + 4.13.2 test @@ -156,7 +150,7 @@ com.google.truth truth - 1.1 + 1.4.4 test @@ -167,23 +161,23 @@ maven-compiler-plugin - 3.8.1 + 3.13.0 maven-source-plugin - 3.2.1 + 3.3.1 maven-javadoc-plugin - 3.2.0 + 3.7.0 true UTF-8 UTF-8 UTF-8 + ${java.version} false - https://checkerframework.org/api/ https://guava.dev/releases/${guava.version}/api/docs/ https://unicode-org.github.io/icu-docs/apidoc/released/icu4j @@ -193,16 +187,16 @@ maven-gpg-plugin - 1.6 + 3.2.4 maven-surefire-plugin - 3.0.0-M5 + 3.3.0 org.apache.felix maven-bundle-plugin - 5.1.1 + 5.1.9 @@ -213,10 +207,6 @@ ${java.version} ${java.version} - - -XDcompilePolicy=simple - -Xplugin:ErrorProne - com.google.errorprone @@ -233,23 +223,25 @@ auto-service ${auto-service.version} + + com.google.auto + auto-common + ${auto-common.version} + - - - default-testCompile - test-compile - - testCompile - - - - -XDcompilePolicy=simple - -Xplugin:ErrorProne -Xep:BetaApi:OFF - - - - + + + + org.apache.maven.plugins + maven-jar-plugin + + + + com.google.common.jimfs + + + @@ -287,57 +279,62 @@ - - - jdk8exactly + run-error-prone - 1.8 + + [11,12),[16,) - - - - maven-javadoc-plugin - - - https://checkerframework.org/api/ - https://guava.dev/releases/${guava.version}/api/docs/ - https://unicode-org.github.io/icu-docs/apidoc/released/icu4j - https://docs.oracle.com/javase/9/docs/api/ - - - - - - - org.apache.maven.plugins maven-compiler-plugin - true - - -J-Xbootclasspath/p:${settings.localRepository}/com/google/errorprone/javac/${javac.version}/javac-${javac.version}.jar + + + -XDcompilePolicy=simple + -Xplugin:ErrorProne -Xep:Java8ApiChecker:ERROR + + + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED - - - - maven-javadoc-plugin - - - https://checkerframework.org/api/ - https://guava.dev/releases/${guava.version}/api/docs/ - https://unicode-org.github.io/icu-docs/apidoc/released/icu4j - https://docs.oracle.com/javase/9/docs/api/ - - + + + default-testCompile + test-compile + + testCompile + + + + -XDcompilePolicy=simple + -Xplugin:ErrorProne -Xep:BetaApi:OFF + + + + + sonatype-oss-release diff --git a/util/deploy_snapshot.sh b/util/deploy_snapshot.sh deleted file mode 100755 index 6a26f100..00000000 --- a/util/deploy_snapshot.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# see https://coderwall.com/p/9b_lfq - -set -e -u - -if [ "$TRAVIS_REPO_SLUG" == "google/jimfs" ] && \ - [ "$TRAVIS_JDK_VERSION" == "openjdk8" ] && \ - [ "$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 diff --git a/util/settings.xml b/util/settings.xml deleted file mode 100644 index 306d14a7..00000000 --- a/util/settings.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - sonatype-nexus-snapshots - ${env.CI_DEPLOY_USERNAME} - ${env.CI_DEPLOY_PASSWORD} - - - diff --git a/util/update_snapshot_docs.sh b/util/update_snapshot_docs.sh index aae5fa02..947f13a5 100755 --- a/util/update_snapshot_docs.sh +++ b/util/update_snapshot_docs.sh @@ -1,25 +1,20 @@ #!/bin/bash -# see http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ for details +set -eu -set -e -u +echo -e "Publishing docs...\n" -if [ "$TRAVIS_REPO_SLUG" == "google/jimfs" ] && \ - [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \ - [ "$TRAVIS_PULL_REQUEST" == "false" ] && \ - [ "$TRAVIS_BRANCH" == "master" ]; then - echo "Publishing Javadoc and JDiff..." +GH_PAGES_DIR="$HOME/gh-pages" - cd $HOME - git clone -q -b gh-pages https://${GH_TOKEN}@github.com/google/jimfs gh-pages > /dev/null - cd gh-pages +git clone --quiet --branch=gh-pages https://x-access-token:${GITHUB_TOKEN}@github.com/google/jimfs.git $GH_PAGES_DIR > /dev/null - git config --global user.email "travis@travis-ci.org" - git config --global user.name "travis-ci" +cd $GH_PAGES_DIR - ./updaterelease.sh snapshot +git config --global user.name "$GITHUB_ACTOR" +git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" - git push -fq origin gh-pages > /dev/null +./updaterelease.sh snapshot - echo "Javadoc published to gh-pages." -fi +git push -fq origin gh-pages > /dev/null + +echo -e "Published docs to gh-pages.\n"