Skip to content

Commit

Permalink
Allow Jimfs file systems to get file times from something other than …
Browse files Browse the repository at this point in the history
…`System.currentTimeMillis()`, such as a fake time source, by configuring them with a custom `FileTimeSource`.

RELNOTES=Added `FileTimeSource` and made it possible to set a `FileTimeSource` in the `Configuration` of a file system.
PiperOrigin-RevId: 473284348
  • Loading branch information
cgdecker authored and Jimfs Team committed Sep 9, 2022
1 parent cde6051 commit 139c10e
Show file tree
Hide file tree
Showing 39 changed files with 452 additions and 205 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,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;
}
Expand All @@ -88,15 +88,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":
Expand Down Expand Up @@ -156,15 +156,15 @@ public void setTimes(
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);
}
}
}
Expand All @@ -182,9 +182,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();
Expand Down
30 changes: 30 additions & 0 deletions jimfs/src/main/java/com/google/common/jimfs/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -233,6 +234,7 @@ public static Builder builder(PathType pathType) {
final ImmutableSet<String> attributeViews;
final ImmutableSet<AttributeProvider> attributeProviders;
final ImmutableMap<String, Object> defaultAttributeValues;
final FileTimeSource fileTimeSource;

// Watch service
final WatchServiceConfiguration watchServiceConfig;
Expand Down Expand Up @@ -261,6 +263,7 @@ private Configuration(Builder builder) {
builder.defaultAttributeValues == null
? ImmutableMap.<String, Object>of()
: ImmutableMap.copyOf(builder.defaultAttributeValues);
this.fileTimeSource = builder.fileTimeSource;
this.watchServiceConfig = builder.watchServiceConfig;
this.roots = builder.roots;
this.workingDirectory = builder.workingDirectory;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -341,6 +345,7 @@ public static final class Builder {
private ImmutableSet<String> attributeViews = ImmutableSet.of();
private Set<AttributeProvider> attributeProviders = null;
private Map<String, Object> defaultAttributeValues;
private FileTimeSource fileTimeSource = SystemFileTimeSource.INSTANCE;

// Watch service
private WatchServiceConfiguration watchServiceConfig = WatchServiceConfiguration.DEFAULT;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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));
Expand Down Expand Up @@ -447,6 +455,7 @@ private static void checkNormalizationNotSet(
*
* <p>The default is false.
*/
@CanIgnoreReturnValue
public Builder setPathEqualityUsesCanonicalForm(boolean useCanonicalForm) {
this.pathEqualityUsesCanonicalForm = useCanonicalForm;
return this;
Expand All @@ -458,6 +467,7 @@ public Builder setPathEqualityUsesCanonicalForm(boolean useCanonicalForm) {
*
* <p>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;
Expand All @@ -478,6 +488,7 @@ public Builder setBlockSize(int blockSize) {
*
* <p>The default is 4 GB.
*/
@CanIgnoreReturnValue
public Builder setMaxSize(long maxSize) {
checkArgument(maxSize > 0, "maxSize (%s) must be positive", maxSize);
this.maxSize = maxSize;
Expand All @@ -497,6 +508,7 @@ public Builder setMaxSize(long maxSize) {
* <p>Like the maximum size, the actual value will be the closest multiple of the block size
* that is less than or equal to the given size.
*/
@CanIgnoreReturnValue
public Builder setMaxCacheSize(long maxCacheSize) {
checkArgument(maxCacheSize >= 0, "maxCacheSize (%s) may not be negative", maxCacheSize);
this.maxCacheSize = maxCacheSize;
Expand Down Expand Up @@ -553,12 +565,14 @@ public Builder setMaxCacheSize(long maxCacheSize) {
* <p>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) {
Expand Down Expand Up @@ -614,6 +628,7 @@ public Builder addAttributeProvider(AttributeProvider provider) {
* </tr>
* </table>
*/
@CanIgnoreReturnValue
public Builder setDefaultAttributeValue(String attribute, Object value) {
checkArgument(
ATTRIBUTE_PATTERN.matcher(attribute).matches(),
Expand All @@ -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.
*
Expand All @@ -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<String> roots = Lists.asList(first, more);
for (String root : roots) {
Expand All @@ -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(
Expand All @@ -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;
Expand All @@ -682,6 +711,7 @@ public Builder setSupportedFeatures(Feature... features) {
*
* @since 1.1
*/
@CanIgnoreReturnValue
public Builder setWatchServiceConfiguration(WatchServiceConfiguration config) {
this.watchServiceConfig = checkNotNull(config);
return this;
Expand Down
25 changes: 13 additions & 12 deletions jimfs/src/main/java/com/google/common/jimfs/Directory.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableSortedSet;
import java.nio.file.attribute.FileTime;
import java.util.Iterator;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;

Expand All @@ -32,23 +33,23 @@ final class Directory extends File implements Iterable<DirectoryEntry> {
/** 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));
}

Expand All @@ -57,8 +58,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);
}

/**
Expand Down
Loading

0 comments on commit 139c10e

Please sign in to comment.