Table of Contents
A file system contains Entity:s such as files and directories. Entity objects are entities in the object-oriented sense of the word[3]. This means that, for instance for EFile (the same holds for Directory), as far as the client application is concerned, is the file itself. At any given time, in one FileSystem, there can only be one EFile object referencing that file. If the file is moved or renamed, it is still represented by the same EFile object. Compare this with how Java's File works.
An entity object always lives within the context of one and the same FileSystem. It cannot be moved to another file system.
The methods of file entities are defined by the EFile interface and the interfaces that it inherits. Directory methods are defined by the Directory interface and its inherited interfaces. The super interfaces of EFile and Directory each capture some aspect of files or directories. Just like when working with the Java Collections API, clients are recommended to use the simplest possible interface that meets their needs. For instance, a client just wanting to read from a file can use an implementation of the ReadableFile interface instead of the full EFile. This makes the client code easier to test since it is easier to replace a ReadableFile with a mock implementation. See Chapter 3, What is a file, anyway? and Figure 5.1, “Simplified inheritance chart for entity objects”.
Some entity types (Directory:s) are ViewCapable. For a Directory, this means that a client can create a view of the directory that uses one or several entity Filter:s to hide some of its child entities. EntityView:s such as the DirectoryView are not entities like the Entity objects; there can be several different active DirectoryView:s for a Directory entity, for instance.
CapabilityProvider:s may give entities or file systems extra capabilities, such as metadata support or support for entity attributes. See Chapter 10, Capabilities.
Many of the examples in this and the following chapters do entity locking as described in Chapter 7, Locking and multi-threaded access.
An entity's AbsoluteLocation, its absolute path, is its unique location in its file system. An entity also has a different RelativeLocation relative to every directory in its file system.
The Entity interface is what identifies an object as an entity, with all the properties discussed above. It defines all common properties and methods for entity objects. It is augmented by static utility methods implemented in the Entities and EntityViews classes.
For the complete interface inheritance chart, see the Inheritance chart for all entity objcets.
Example 5.1. Moving an entity in a locking file system
// d1 and d2 are directories, e1 is an entity of any type that currently is in // the directory d1. Collection<EntityLock> locks = new ArrayList<EntityLock>(); try { locks.add(d1.lockForWriting()); // The write lock on d1 is also a read lock. Get the entity to move. EntityView e1 = d1.getEntityOrNull("e1"); locks.add(e1.lockForWriting()); locks.add(d2.lockForWriting()); // Move the entity e1.rename(d2, "e1"); } finally { // Use the utility class EntityLocks to unlock all locks EntityLocks.unlockAll(locks); } // The entity is still represented by the variable e1, even though it has // moved.
Example 5.2. Moving an entity using utility methods
// d1 and d2 are directories, e1 is an entity of any type that currently is in // the directory d1. // // In a locking file system, the utility classes make sure to lock all entities // that need to be locked to perform the requested operation. See the chapter // on locking. EntityView e1 = Directories.getEntity(d1, "e1"); Entities.move(e1, d2);
In the second example, no locks are held on
d1
between e1
is retrieved and moved. Another thread may move or even delete
e1
between the calls to
getEntity
and move
.
If this is a real risk (this depends on the application), lock
d1
for writing before calling
getEntity
and release the lock after calling
move
. Locks are reentrant, so the locking
performed by the utility classes will not be disturbed by that. See
Chapter 7, Locking and multi-threaded access.
A Directory is a container for other entities, child entities. Basic directory operations are defined in the EntityHolder and EntityListable interfaces.
For the Directory inheritance chart, see the Inheritance chart for Directory.
The utility class Directories implements utility methods for working with directories.
Example 5.3. Creating a file in the root directory
// fs is a locking file system. Get the root directory Directory root = fs.getRootDirectory(); EFile myFile; // Lock the root directory for writing EntityLock wl = root.lockForWriting(); try { // Create an entity of the type ETFile.TYPE called myFile myFile = (EFile) root.newEntity(ETFile.TYPE, "myFile", null); } finally { // Unlock the directory wl.unlock(); }
Example 5.4. Creating a file in the root directory using Directories
// fs is a file system. Get the root directory Directory root = fs.getRootDirectory(); // Create a file called "myFile". EFile myFile = Directories.newFile(root, "myFile");
Example 5.5. Getting an entity in a subdirectory
// d is a directory // Create a directory under d with an empty file in it. This invocation // returns a reference to the file, but we ignore it for the sake // of the example Directories.newFile(org.entityfs.util.Directories.newDirectory(d, "d"), "f"); // Directories.getDirectory returns a DirectoryView. Since the argument d // is a Directory, the result can safely be cast to a Directory. Directory dd = (Directory) Directories.getDirectory(d, "d"); // (We could have used the entity reference returned by newFile above.) EFile f = Directories.getFile(d, new RelativeLocation("d/f"));
Example 5.6. Listing entities in a directory
Set<EntityView> s; // d is a directory in a locking file system. Lock it for reading. EntityLock rl = d.lockForReading(); try { s = d.listEntities(); } finally { // Unlock the directory. rl.unlock(); }
Example 5.7. Listing entities in a directory using
Directories
// d is a directory Set<EntityView> s = Directories.listEntities(d);
A DirectoryView is a directory viewed through one or several entity Filter:s. The filter hides all child entities that do not match the filter criteria. Read more about entity filters in the section called “Entity filters”.
Example 5.8. A directory view
// d is a directory EFile f1 = Directories.newFile(d, "f1"); DirectoryView d1 = Directories.newDirectory(d, "d1"); // Create a view that only shows files DirectoryView dv = d.newView(EFileFilter.FILTER); Set<EntityView> s = Directories.listEntities(dv);
The returned set will only contain f1
.
The Directory interface inherits DirectoryView, so all directories can be considered to be views of themselves with no filters.
All EntityListable objects, including directories and directory views, are
Iterable<EntityView>
. They have an
iterator()
method that returns an
Iterator<EntityView>
for iterating
over all visible child entities. This means that directories
can be used directly in a for loop like in the examples below.
Example 5.9. Using a Directory directly in a for loop
// d is a directory EntityLock rl = d.lockForReading(); try { for (EntityView ev : d) { // process child entities... } } finally { rl.unlock(); }
Example 5.10. Using a DirectoryView directly in a for loop
// d is a directory (or a view) // Create a view that only shows XML files. DirectoryView dv = d.newView(new EFileNameExtensionFilter("xml")); // Even though this is called on the view, it locks the viewed directory Lock rl = dv.lockForReading(); try { for (EntityView ev : dv) { EFile xmlFile = (EFile) ev; // process the XML files... } } finally { rl.unlock(); }
There are utility classes that implement other types of entity iterators, for instance recursive iterators over entity hierarchies. Read more about them in the section called “Recursive iterators” and the section called “Iterator utilities”.
Example 5.11. Using a depth-first iterator
// d is a directory Directory dd = (Directory) Directories.newDirectory(d, "d"); Directory ddd = (Directory) Directories.newDirectory(dd, "d"); EFile f = Directories.newFile(ddd, "f"); Iterator<EntityView> itr = Directories.getDepthFirstIterator(d);
The iterator will return the entities f
,
ddd
, dd
and
d
, in that order.
View settings are inherited. If a view capable child entity is retrieved from a directory view, the returned object is a view of that entity with the same filters (filter object instances) as the directory view.
Example 5.12. Views settings are inherited
// d is a directory EFile f1txt = Directories.newFile(d, "f1.txt"); EFile f1xml = Directories.newFile(d, "f1.xml"); Directory d1 = (Directory) Directories.newDirectory(d, "d1"); EFile d1f1txt = Directories.newFile(d1, "d1f1.txt"); EFile d1f1xml = Directories.newFile(d1, "d1f1.xml"); // Create a view that only shows directories and XML files DirectoryView dv = d.newView( DirectoryFilter.FILTER. or(new EFileNameExtensionFilter("xml"))); Iterator<EntityView> itr = Directories.getDepthLastIterator(dv);
itr
will return the entities
/
(view of), f1.xml
,
d1
(view of)
and d1f1.xml
(d1
may be returned before f1.xml
).
In the the inherited view example above, a FilteringIterator may be used if you don't want the iterator to return any directories. Create the iterator like this instead:
Iterator itr = new FilteringIterator( Directories.getDepthLastIterator(dv), DirectoryFilter.FILTER.not());
File entities are containers for byte data. Basic file operations are defined in the ReadableFile, WritableFile and RandomlyAccessibleFile interfaces. They are inherited by the file entity interface, EFile.
For the EFile inheritance chart, see the Inheritance chart for EFile.
Example 5.13. Creating and opening a file
// d is a directory EFile f = Directories.newFile(d, "f"); // Lock the file for writing EntityLock wl = f.lockForWriting(); try { OutputStream os = f.openForWrite(); try { try { ... write to os } finally { os.close(); } } catch (IOException e) { ... error handling } } finally { wl.unlock(); }
The class Files contains utility methods for working with file entities.
Example 5.14. Creating and writing to a text file
// d is a directory EFile f = Directories.newFile(d, "f"); Files.writeText(f, "Ceterum censeo Carthaginem esse delendam");
Sometimes an open input or output stream on a file has to be passed as an argument to another method. If using a locking file system, to make sure that the lock on the file is released when the stream is closed, use the lock-aware LockAwareInputStream or a lock-aware stream from Files.openForRead like in the example below.
Example 5.15. Using a lock-aware stream
// f is a file myStreamUsingMethod(Files.openForRead(f)); // The openForRead method locks the file for reading. The lock is released when // the stream is closed. (And that is a responsibility of the stream-using // class.)
Methods that only are interested in files as sources or sinks of data, not of their entity qualities, are encouraged to use the ReadableFile and WritableFile interfaces, just like most of the methods in Files do. They are easier to mock for testing than the EFile interface.