Appendix B. EntityFS cookbook

Table of Contents

EntityFS overview
Interoperability with java.io classes
File systems
Entities
Directory views
Utility classes
EntityFilter implementations
Best practices

Schmant uses EntityFS extensively for working with files and directories. This appendix gives some basic information about EntityFS programming that might be useful for build script writers. More exhaustive information about EntityFS programming can be found in the EntityFS Programmer's Guide.

EntityFS is an object-oriented file system API for Java. The difference in philosophy between EntityFS and Java's built in file and directory support is that Java's File object represents a path in the local file system, while EntityFS entities represent the files and directories themselves. From the application's perspective, an EntityFS EFile entity, for instance, can be said to be the file, while a Java File object is a pointer to the file. If the EntityFS EFile is moved, the object is updated with the new location. If it is deleted, the entity object is invalidated. If a new file is put in the same location in the file system, the original EFile object is still invalid.

The properties of entity objects are further discussed in this article on the EntityFS site.

EntityFS file systems can be built on the operating system's file system, on Zip or Jar files or in memory. See for instance the section called “In-memory file systems”.

An EntityFS FileSystem contains file and directory entities. Entities are mostly used through any of the utility classes.

The EntityFS Programmer's Guide contains an appendix on how to move from EntityFS classes to java.io classes and back again. See also Example 5.3, “From Java files to EntityFS and back again”.

The SchmantFileSystems utility class contains the makeFileBacked and makeRandomlyAccessible methods for making EFile entities File-backed and FCRandomAccess:ible, respectively.

Entities always live within the context of a FileSystem. It has some global properties, such as strategies for entity locking and for handling of file system events. It can also have any number of FileSystemCapability:s and EntityCapability:s that extend its functionality. For instance, the ECFileResolvable entity capability makes files and directories resolvable to Java's File objects.

A file system always contains a root Directory that can be retrieved by calling its getRootDirectory() method.

File systems are either created manually using a FileSystemBuilder or through any of the utility methods of SchmantFileSystems. SchmantFileSystems adapts the file system-creating methods of EntityFS' FileSystems and also adds a few file system-creation methods of its own.

FileSystem:s may be created to support entity locking. This should be enabled for all read/write file systems accessed by parallel build threads, since it prevents tasks from accidentally writing to the same files or directories simultaneously. Many of the file systems returned by SchmantFileSystems' methods have locking enabled.


Example B.1. Creating source and target file systems

Groovy

import java.io.File import org.entityfs.util.Directories import org.entityfs.util.filter.entity.EntityNameFilter import org.schmant.support.entityfs.SchmantFileSystems import org.schmant.support.io.TempFileUtil // Create a read only file system with its root directory in // /home/me/myproject/src srcd = SchmantFileSystems.getEntityForDirectory( new File("/home/me/myproject/src"), true) // Get a view of the root directory that hides all .svn directories. When this // view is used for getting child directories, all .svn directories will be // hidden in them too, making all .svn directories in the file system invisible // for all EntityFS-aware tasks. // The ~ is used to negate the filter. srcRoot = srcd.newView(~(new EntityNameFilter(".svn"))) // Create a read/write file system in a temporary directory to use for putting // built files in. Keep this directory when the script is done (set keep flag to // true). targetRoot = TempFileUtil.createTempDirectory( "myproject", null, true) // Set a temporary directory on the target file system. Tasks that need to // create temporary files use this directory for them. targetRoot.fileSystem.setTemporaryFilesDirectory( Directories.newDirectory(targetRoot, "tmp"))

JavaScript

// Create a read only file system with its root directory in // /home/me/myproject/src srcd = SchmantFileSystems.getEntityForDirectory( new File("/home/me/myproject/src"), true); // Get a view of the root directory that hides all .svn directories. When this // view is used for getting child directories, all .svn directories will be // hidden in them too, making all .svn directories in the file system invisible // for all EntityFS-aware tasks. srcRoot = srcd.newView(new EntityNameFilter(".svn").not()); // Create a read/write file system in a temporary directory to use for putting // built files in. Keep this directory when the script is done (set keep flag to // true). targetRoot = TempFileUtil.createTempDirectory( "myproject", null, true); // Set a temporary directory on the target file system. Tasks that need to // create temporary files use this directory for them. targetRoot.getFileSystem().setTemporaryFilesDirectory( Directories.newDirectory(targetRoot, "tmp"));

JRuby

// Create a read only file system with its root directory in // /home/me/myproject/src srcd = SchmantFileSystems.getEntityForDirectory( new File("/home/me/myproject/src"), true); // Get a view of the root directory that hides all .svn directories. When this // view is used for getting child directories, all .svn directories will be // hidden in them too, making all .svn directories in the file system invisible // for all EntityFS-aware tasks. srcRoot = srcd.newView(new EntityNameFilter(".svn").not()); // Create a read/write file system in a temporary directory to use for putting // built files in. Keep this directory when the script is done (set keep flag to // true). targetRoot = TempFileUtil.createTempDirectory( "myproject", null, true); // Set a temporary directory on the target file system. Tasks that need to // create temporary files use this directory for them. targetRoot.getFileSystem().setTemporaryFilesDirectory( Directories.newDirectory(targetRoot, "tmp"));

Jython

# Create a read only file system with its root directory in # /home/me/myproject/src srcd = SchmantFileSystems.getEntityForDirectory( \ File("/home/me/myproject/src"), True) # Get a view of the root directory that hides all .svn directories. When this # view is used for getting child directories, all .svn directories will be # hidden in them too, making all .svn directories in the file system invisible # for all EntityFS-aware tasks. srcRoot = srcd.newView(EntityNameFilter(".svn").not()) # Create a read/write file system in a temporary directory to use for putting # built files in. Keep this directory when the script is done (set keep flag to # true). targetRoot = TempFileUtil.createTempDirectory( \ "myproject", None, True) # Set a temporary directory on the target file system. Tasks that need to # create temporary files use this directory for them. targetRoot.getFileSystem().setTemporaryFilesDirectory( \ Directories.newDirectory(targetRoot, "tmp"))

Schmant implements a LogAdapter, the SchmantReportLogAdapter, for making EntityFS log into a Schmant Report. This log adapter can manually be set on a file system fs by calling fs.getLogAdapterHolder().setLogAdapter(SchmantReportLogAdapter.INSTANCE). If any of the methods of SchmantFileSystems or TempFileUtil are used for creating entities (and thus also file systems), the returned file systems already have the Schmant log adapter set.

Example B.2. Using the SchmantReportLogAdapter

Groovy

import org.entityfs.fs.FSRWFileSystemBuilder import org.schmant.report.SchmantReportLogAdapter // Create a new file system with its root directory in the directory d // (a File). def fs = new FSRWFileSystemBuilder(). setRoot(d). create() // Set the Schmant log adapter fs.logAdapterHolder.setLogAdapter( SchmantReportLogAdapter.INSTANCE) // This is written to the current Report fs.logAdapter.logWarning("This is a warning message!")

JavaScript

// Create a new file system with its root directory in the directory d // (a File). fs = new FSRWFileSystemBuilder(). setRoot(d). create(); // Set the Schmant log adapter fs.getLogAdapterHolder().setLogAdapter( SchmantReportLogAdapter.INSTANCE); // This is written to the current Report fs.getLogAdapter().logWarning("This is a warning message!");

JRuby

# Create a new file system with its root directory in the directory d # (a File). fs = Schmant::FSRWFileSystemBuilder.new. setRoot($d). create # Set the Schmant log adapter fs.logAdapterHolder.setLogAdapter( Schmant::SchmantReportLogAdapter::INSTANCE) # This is written to the current Report fs.logAdapter.logWarning("This is a warning message!")

Jython

# Create a new file system with its root directory in the directory d # (a File). fs = FSRWFileSystemBuilder(). \ setRoot(d). \ create() # Set the Schmant log adapter fs.getLogAdapterHolder().setLogAdapter( \ SchmantReportLogAdapter.INSTANCE) # This is written to the current Report fs.getLogAdapter().logWarning("This is a warning message!")

Entity:s live within the context of a FileSystem; an entity can never leave it. Every entity has a unique location, an AbsoluteLocation in its file system.

Entity objects are never instantiated directly by clients. Existing entities are retrieved from their parent Directory object or, more commonly, by using the utility methods of Directories. New entities are also created by using the methods of their parent Directory or by using Directories' methods.

All entities use read/write locking to protect them from concurrent modification. Before using an entity method on an entity in a locking file system, the entity has to be either locked for reading or writing, depending on what the method does. All utility class methods acquire the necessary locks before calling entity methods, which make them much easier to use compared with calling entity methods directly.

A DirectoryView is a Directory viewed through zero or more EntityFilter:s. If a child directory is fetched through a directory view, it inherits the parent directory's view settings.

A common usage is to use directory views to hide all .svn directories in a file system, see this example.

Often, most of the work against a FileSystem and its entities is done through EntityFS' utility classes. They have high-level methods for common operations, such as listing directory contents with filters or reading contents of files.


The following table contains the entity Filter implementations that EntityFS comes with.

Table B.3. EntityFilter implementations

ImplementationDescription
DirectoryEmptyFilterMatches empty directories.
DirectoryFilterMatches directories.
EFileFilterMatches files.
EFileNameExtensionFilterMatches files whose names have one of a set of extensions.
EntityIdentityFilterMatches a specific entity instance.
EntityLatestModificationTimeFilterMatches entities that were modified before a specific time.
EntityLocationGlobFilterMatches entities whose location relative to a base directory matches a glob pattern.
EntityLocationRegexpFilterMatches entities whose location relative to a base directory matches a regular expression pattern.
EntityNameFilterMatches entities with a specific name.
EntityNameGlobFilterMatches entities whose names match a glob pattern.
EntityNamePrefixFilterMatches entities whose names start with a specific prefix.
EntityNameRegexpFilterMatches entities whose names match a regular expression pattern.
EntityRecentModificationFilterMatches entities that have been modified within a specified time limit.
EntityTypeFilterMatches entities of a specific type (files or directories). It is better to use EFileFilter or DirectoryFilter instead.
FalseFilterDoes not match any entity.
LockedEntityFilterMatches entities that are locked for reading or writing by the current thread.
ParentFilterMatches entities that have a specific parent directory.
ReadLockedEntityFilterMatches entities that are locked for reading by the current thread.
SuperParentAndFilterMatches an entity if all of its parent directories match another filter.
SuperParentAndFilterMatches an entity if any of its parent directories match another filter.
TrueFilterMatches all entities.
WriteLockedEntityFilterMatches entities that are locked for writing by the current thread.

All entity filters implement ConvenientFilter so they have and, or, xor and not methods to combine them with other filters.

See for instance this example for how filters can be used.

The following is a collection of best practices for using EntityFS in Schmant build scripts:

  1. If possible, use SchmantFileSystems methods instead of FileSystemBuilder:s for creating file systems. The file systems created by SchmantFileSystems are configured with settings that work well with build scripts.

  2. Create a read only file system for the build's source files and a read/write file system for the build's target directories. See Example B.1, “Creating source and target file systems”.