Configuring TrueVFS Access

TrueVFS follows the convention-over-configuration paradigm, so there are reasonable defaults for everything in order to relieve you from typical configuration tasks.

For example, in order to configure the detectable archive file extensions, you just need to make sure that the JAR artifacts of the respective file system driver modules are present on the class path. At run time, the module TrueVFS Kernel will then enumerate the available file system drivers in order to register their canonical archive file extensions and make this mapping available for the initial setup of the client API module TrueVFS Access. To see the list of canonical archive file extensions, please consult the documentation of the respective file system driver module, e.g. TrueVFS Driver ZIP.

So if you use Maven, you just need to add the driver modules as dependencies to your POM like this:

<project>
  ...
  <dependencies>
    ...
    <dependency>
      <groupId>net.java.truevfs</groupId>
      <artifactId>truevfs-driver-zip</artifactId>
      <version>0.12.0</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

However, if you want to change any of these defaults, then you may need some more insight which is given in this article.

The TConfig class

The class TConfig implements an inheritable thread local configuration stack. That's a lot to digest, so let's digest it bite for bite:

  • TConfig instances are configuration elements with properties which can get queried and updated by the application, e.g. via TConfig.getArchiveDetector() or TConfig.setArchiveDetector(TArchiveDetector).
  • It's a stack because an application can push and pop configuration elements by calling TConfig.open() and TConfig.close() respectively.
  • It's thread local because each thread maintains its own stack.
  • It's inheritable because new threads inherit the top element from the thread local configuration stack of their parent thread. However, child threads cannot pop this element off their thread local stack.

This class also maintains a global configuration which is used whenever the inheritable thread local configuration stack is empty. As the name implies, this configuration element gets shared by all threads, so accessing it is not thread-safe!

Last, but not least, this class also maintains a current configuration for each thread. This is simply the top element of the thread local stack or the global configuration if and only if the thread local stack is empty. A thread can obtain its current configuration by calling TConfig.current():

Disclaimer: Although this classes internally uses a java.lang.InheritableThreadLocal, it does not leak memory in multi class loader environments when used appropriately.

Given these properties, there are two canonical use cases:

Customizing the configuration for the entire application

In order to customize the configuration for the entire application you would need to change the global configuration. Because the global configuration is identical to the current configuration at application startup, you could simply do this as follows:

TConfig config = TConfig.current();
config.setAccessPreference(FsAccessOption.GROW, true);

This example changes the current configuration. The option GROW will cause any subsequent update to existing archive entries to get appended to their archive file rather than triggering a full update. If this code gets called at application startup, it would change the global configuration for subsequent use by all threads.

Customizing the configuration for isolated operations

Sometimes you may want to customize the current configuration for some isolated file system operations. This is best done with thread local temporary scope as follows:

try (TConfig config = TConfig.open()) {
    config.setAccessPreference(FsAccessOption.ENCRYPT, true);
    TFile src = new TFile("directory");
    TFile dst = new TFile("secret.zip");
    src.cp_rp(dst); // creates a WinZip AES encrypted ZIP file
}

This example pushes a copy of the current configuration onto the inheritable thread local configuration stack and changes it. The option ENCRYPT will cause the subsequent copy operation to create a WinZip AES encrypted ZIP file. Note that the updated configuration gets automatically popped off the inheritable thread local configuration stack at the end of the try-block, thereby restoring the previous configuration.

The TArchiveDetector class

The task of the class TArchiveDetector is to scan path names for archive file extensions and map it to the respective FsDriver class. For example, it could detect the file extension jar in the path name directory/lib.jar and map it to the class JarDriver.

TArchiveDetector provides many options to customize the archive detection:

  • The predefined object TArchiveDetector.NULL detects no archive file extensions at all.
  • The predefined object TArchiveDetector.ALL detects all archive file extensions which can get mapped by enumerating the file system drivers on the class path at run time. Please consult the documentation of the individual driver modules for the list of registered archive file extensions.
  • The constructor TArchiveDetector(String extensions) lets you filter the list of archive file extensions known by TArchiveDetector.ALL. This enables you to limit the set of all archive file extensions registered by all file system driver modules on the run time class path.
  • The constructor TArchiveDetector(String extensions, FsDriver driver) lets you define a custom list of archive file extensions for any archive driver. This enables you to define a custom application file format.

For example, the following code would map the file extensions foo and bar to the archive driver for JAR files in order to define a custom application file format and update the current configuration with it:

TArchiveDetector detector = new TArchiveDetector("foo|bar", new JarDriver());
TConfig.current().setArchiveDetector(detector);

Mind that this example changes the current configuration, which is identical to the global configuration at application startup.

Please consult the Javadoc for even more constructor options.

The TFile and TPath classes

Whenever an object of the class TFile or TPath gets instantiated, it gets initialized with a TArchiveDetector to scan its path name for prospective archive files. Note that these classes are immutable, so once an instance has been created you cannot change its archive detector and hence its behavior.

The TFile class provides multiple overloaded constructors and factory methods, e.g. TFile.listFiles(). Most of these come in pairs: One variant with and one variant without an additional TArchiveDetector parameter for initialization.

If you don't provide a TArchiveDetector parameter to a TFile constructor or factory method, then the instance gets initialized with the current archive detector. The current archive detector can get obtained or changed by respectively calling TConfig.current().getArchiveDetector() or TConfig.current().setArchiveDetector(detector).

The TPath class has no such provisions because the java.nio.file.Files class couldn't use it anyway. So the TPath class is always initialized with the current archive detector.

Initial setup

The global configuration gets initialized with TArchiveDetector.ALL as its archive detector and net.java.truevfs.kernel.spec.FsAccessOption.CREATE_PARENTS as its sole access preference. This enables the following simple code for creating an archive entry to work as expected if the JAR of the module TrueVFS Driver ZIP is present on the class path at run time:

File entry = new TFile("archive.zip/dir/HälloWörld.txt");
try (Writer writer = new TFileWriter(entry)) {
    writer.write("Hello world!\n");
}

Without the option CREATE_PARENTS, you would have to call TFile.mkdirs() first in order to create "archive.zip/dir" and without the archive detector ALL, "archive.zip" would get created as a regular directory.

The TApplication class

The class TApplication is a simple template class which aids you in implementing the typical lifecycle of a TrueVFS application. Consider the following example:

class MyApplication extends TApplication<IOException> {

    public static void main(String[] args) throws IOException {
        System.exit(new MyApplication().run(args));
    }

    @Override
    protected void setup() {
        TConfig config = TConfig.get();
        config.setArchiveDetector(new TArchiveDetector("aff", new JarDriver()));
        config.setAccessPreference(FsOutputOption.GROW, true);
    }

    ...
}

Here, the current configuration is changed to contain a custom archive detector for an application file format and the option GROW in addition to the default option CREATE_PARENTS. By overriding TApplication.setup(), it can be safely assumed that there is no enclosing TConfig.open|close() and hence config indeed refers to the global configuration.