@Immutable
public class FileCache
extends java.lang.Object
This class is used to avoid creating the same file/directory multiple times. The main API
method createFile(File, Inputs, ExceptionRunnable) creates an output file/directory by
either copying it from the cache, or creating it first and caching it if the cached
file/directory does not yet exist. The other API method, createFileInCacheIfAbsent(Inputs, ExceptionConsumer), returns the cached output file/directory
directly, and creates it first if the cached file/directory does not yet exist.
Access to the cache is synchronized. Synchronization can take effect for threads within the
same process or across different processes. The client can configure this locking scope when
constructing a FileCache. If the cache is never accessed by more than one process at a
time, the client should configure the cache with SINGLE_PROCESS locking scope since there
will be less synchronization overhead. However, if the cache may be accessed by more than one
process at a time, the client must configure the cache with MULTI_PROCESS locking scope.
In any case, synchronization takes effect only for the same cache (i.e., threads/processes
accessing different caches are not synchronized). Also, the client must access the cache via
FileCache's API; otherwise, the previous concurrency guarantees will not hold.
Two caches are considered the same if they refer to the same physical cache directory. There
could be multiple instances of FileCache for the same physical cache directory, and as
long as they refer to the same physical cache directory, access to them will be synchronized.
(The client does not need to normalize the cache directory's path when constructing a FileCache.)
Multiple threads/processes can read the same cache entry at the same time. However, once a
thread/process starts writing to the cache entry, the other threads/processes will block. This
behavior is similar to a ReadWriteLock.
Note that we often use the term "process", although the term "JVM" would be more correct since there could exist multiple JVMs in a process.
If a cache entry exists but is found to be corrupted, the cache entry will be deleted and recreated.
This class is thread-safe.
| Modifier and Type | Class and Description |
|---|---|
static class |
FileCache.Command
Command to be provided by the client when using
FileCache. |
static class |
FileCache.Inputs
List of input parameters to be provided by the client when using
FileCache. |
static class |
FileCache.QueryEvent
The event that happens when the client queries a cache entry: the cache entry may be hit,
missed, or corrupted.
|
static class |
FileCache.QueryResult
The result of a cache query, which includes a
FileCache.QueryEvent indicating whether the cache
is hit, missed, or corrupted, a cause if the cache is corrupted, and an (optional) path to
the cached output file/directory. |
| Modifier and Type | Method and Description |
|---|---|
boolean |
cacheEntryExists(FileCache.Inputs inputs)
Returns
true if the cache entry for the given list of inputs exists and is not
corrupted, and false otherwise. |
FileCache.QueryResult |
createFile(java.io.File outputFile,
FileCache.Inputs inputs,
ExceptionRunnable fileCreator)
Creates an output file/directory by either copying it from the cache, or creating it first
via the given file creator callback function and caching it if the cached file/directory does
not yet exist.
|
FileCache.QueryResult |
createFileInCacheIfAbsent(FileCache.Inputs inputs,
ExceptionConsumer<java.io.File> fileCreator)
Creates the cached output file/directory via the given file creator callback function if it
does not yet exist.
|
void |
delete()
Deletes the cache directory and its contents.
|
java.io.File |
getCacheDirectory() |
java.io.File |
getFileInCache(FileCache.Inputs inputs)
Returns the path of the cached output file/directory that is unique to the given list of
inputs (different lists of inputs correspond to different cached files/directories).
|
static FileCache |
getInstanceWithMultiProcessLocking(java.io.File cacheDirectory)
Returns a
FileCache instance where synchronization takes effect for threads both
within the same process and across different processes. |
static FileCache |
getInstanceWithSingleProcessLocking(java.io.File cacheDirectory)
Returns a
FileCache instance where synchronization takes effect for threads within
the same process but not for threads across different processes. |
java.lang.String |
toString() |
@NonNull public static FileCache getInstanceWithMultiProcessLocking(@NonNull java.io.File cacheDirectory)
FileCache instance where synchronization takes effect for threads both
within the same process and across different processes.
Inter-process synchronization is provided via SynchronizedFile, which requires
lock files to be created. One lock file will be placed next to the cache directory and the
other lock files will be placed next to the cache entry directories (inside the cache
directory). Note that currently it is not possible for the underlying locking mechanism to
delete these lock files. The lock files should only be deleted together with the entire cache
directory.
The given cache directory may or may not already exist. If it does not yet exist, this method will not create the cache directory here (the cache directory will be created when the cache is actually used).
Note: If the cache is never accessed by more than one process at a time, the client should
use the getInstanceWithSingleProcessLocking(File) method instead since there will be
less synchronization overhead.
cacheDirectory - the cache directory, which may not yet existgetInstanceWithSingleProcessLocking(File)@NonNull public static FileCache getInstanceWithSingleProcessLocking(@NonNull java.io.File cacheDirectory)
FileCache instance where synchronization takes effect for threads within
the same process but not for threads across different processes.
The given cache directory may or may not already exist. If it does not yet exist, this method will not create the cache directory here (the cache directory will be created when the cache is actually used).
Note: If the cache may be accessed by more than one process at a time, the client must use
the getInstanceWithMultiProcessLocking(File) method instead.
cacheDirectory - the cache directory, which may not yet existgetInstanceWithMultiProcessLocking(File)@NonNull public java.io.File getCacheDirectory()
@NonNull public FileCache.QueryResult createFile(@NonNull java.io.File outputFile, @NonNull FileCache.Inputs inputs, @NonNull ExceptionRunnable fileCreator) throws java.util.concurrent.ExecutionException, java.io.IOException
The output file/directory must not reside in, contain, or be identical to the cache directory.
To determine whether to reuse a cached file/directory or create a new file/directory, the
client needs to provide all the inputs that affect the creation of the output file/directory
to the FileCache.Inputs object.
If this method is called multiple times on the same list of inputs, on the first call, the cache will delete the output file/directory (if it exists), create its parent directories, invoke the file creator to create the output file/directory, and copy the output file/directory to the cache. On subsequent calls, the cache will create/replace the output file/directory with the cached result without invoking the file creator.
Note that the file creator is not required to always create an output. If the file creator does not create an output on the first call, the cache will remember this result and produce no output on subsequent calls (it will still delete the output file/directory if it exists and creates its parent directory on both the first and subsequent calls).
Depending on whether there are other threads/processes concurrently accessing this cache and the type of locking scope configured for this cache, this method may block until it is allowed to continue.
NOTE ON THREAD SAFETY: The cache is responsible for synchronizing access within the cache directory only. When the clients of the cache use this method to create an output file/directory (which must be outside the cache directory), they need to make sure that multiple threads/processes do not create the same output file/directory, or they need to have a way to manage that situation.
outputFile - the output file/directoryinputs - all the inputs that affect the creation of the output file/directoryfileCreator - the callback function to create the output file/directoryjava.util.concurrent.ExecutionException - if an exception occurred during the execution of the file creatorjava.io.IOException - if an I/O exception occurred, but not during the execution of the file
creator (or the file creator was not executed)java.lang.RuntimeException - if a runtime exception occurred, but not during the execution of the
file creator (or the file creator was not executed)@NonNull public FileCache.QueryResult createFileInCacheIfAbsent(@NonNull FileCache.Inputs inputs, @NonNull ExceptionConsumer<java.io.File> fileCreator) throws java.util.concurrent.ExecutionException, java.io.IOException
To determine whether a cached file/directory exists, the client needs to provide all the
inputs that affect the creation of the output file/directory to the FileCache.Inputs object.
If this method is called multiple times on the same list of inputs, the first call will invoke the file creator to create the cached output file/directory (which is given as the argument to the file creator callback function), and subsequent calls will return the cached file/directory without invoking the file creator.
Note that the file creator is not required to always create an output. If the file creator does not create an output on the first call, the cache will remember this result and return a cached file that does not exist on subsequent calls.
Depending on whether there are other threads/processes concurrently accessing this cache and the type of locking scope configured for this cache, this method may block until it is allowed to continue.
Note that this method returns a cached file/directory that is located inside the cache directory. To avoid corrupting the cache, the client must never write to the returned cached file/directory (or any other files/directories inside the cache directory) without using the cache's API. If the client wants to read the returned cached file/directory, it must ensure that another thread (of the same or a different process) is not overwriting or deleting the same cached file/directory at the same time.
WARNING: DO NOT use this method if the returned cached file/directory will be annotated
with Gradle's @OutputFile or @OutputDirectory annotations as it is undefined
behavior of Gradle incremental builds when multiple Gradle tasks have the same
output-annotated file/directory.
inputs - all the inputs that affect the creation of the output file/directoryfileCreator - the callback function to create the output file/directoryjava.util.concurrent.ExecutionException - if an exception occurred during the execution of the file creatorjava.io.IOException - if an I/O exception occurred, but not during the execution of the file
creator (or the file creator was not executed)java.lang.RuntimeException - if a runtime exception occurred, but not during the execution of the
file creator (or the file creator was not executed)public boolean cacheEntryExists(@NonNull
FileCache.Inputs inputs)
throws java.io.IOException
true if the cache entry for the given list of inputs exists and is not
corrupted, and false otherwise. This method will block if the cache/cache entry is
being created/deleted by another thread/process.inputs - all the inputs that affect the creation of the output file/directoryjava.io.IOException@NonNull
public java.io.File getFileInCache(@NonNull
FileCache.Inputs inputs)
Note that this method returns the path only, the cached file/directory may or may not have been created.
This method is typically used together with the
createFileInCacheIfAbsent(Inputs, ExceptionConsumer) method to get the path of the
cached file/directory in advance, before attempting to create it at a later time. The
returned path of this method is guaranteed to be the same as the returned path by that
method. The client calling this method should take precautions in handling the returned
cached file/directory; refer to the javadoc of
createFileInCacheIfAbsent(Inputs, ExceptionConsumer) for more details.
inputs - all the inputs that affect the creation of the output file/directorypublic void delete()
throws java.io.IOException
If the cache is being used by another thread of any process in the case of MULTI_PROCESS locking scope, or by another thread of the current process in the case of
SINGLE_PROCESS locking scope, then this method will block until that operation
completes.
java.io.IOExceptionpublic java.lang.String toString()
toString in class java.lang.Object