Developing Virtual File Systems

This chapter first explains the workings of jEdit's I/O system in detail. Then, it overviews the APIs allowing plugins to extend it.

Input/Output Basics

jEdit performs all buffer loading and saving in a separate thread, known as the work thread. It is an instance of the org.gjt.sp.util.WorkThread class, and at its simplest, all it does is execute queued requests. A requests can specify if it is to be run in the work thread, or the AWT event dispatch thread. In the former case, the work thread simply executes the request. In the latter, passes it to the SwingUtilities.invokeLater() method, which queues it up for execution in the event dispatch thread. The ability to execute requests in the AWT thread may seem redudant, but in fact it is very useful; for example, it is used to update jEdit's GUI after a file load is complete.[1] The work thread is not limited to performing I/O; in fact, plugins can create their own work thread instances and use them for other purposes. However, in this section, we will only cover I/O-related uses of the work thread.

Buffer Loading and Saving

The Buffer.load() method is called when a buffer load is requested by the user; either when a file is opened, or the File>Reload command is invoked. The main purpose of this method is to queue two requests with the I/O work thread; the first loads the buffer, the second performs various post-load "cleanups", such as updating the GUI, setting up a new undo queue, and so on. Because the latter is of little relevance to the plugin VFS developer, it will not be discussed further.

The buffer loading request is an instance of the org.gjt.sp.jedit.io.IORequest class. This class first obtains the virtual filesystem used by that buffer (more on how this is determined later). It then calls the _createInputStream() method of the filesystem. This method does whatever is necessary to obtain a stream to read from; for example, the "file" VFS simply creates a FileInputStream. The "ftp" VFS opens a network connection, logs in, and begins downloading the specified file. IORequest then procceeds to read the buffer from this input stream. Although in reality the buffer load process is a bit more complicated than this, but the above should still give you a good idea of how it works.

The Buffer.save() method behaves in a similar manner. It also queues up an IORequest, which calls VFS._createOutputStream() instead of VFS._createInputStream().

Now wait a minute, you say. Why bother with all this IORequest stuff? Why doesn't Buffer.load() just call the VFS directly? To understand, consider the above more carefully. Buffer.load() queues two requests, and returns to its caller. The requests are then run asynchronously, in a different thread. This allows them to be aborted if the user wishes to do so. Views also continue to be repainted.

The VFSManager Class

public class VFSManager
{
        public static void registerVFS(String protocol, VFS vfs);
        public static void waitForRequests();
        public static void runInAWTThread(Runnable run);
        public static void error(Component comp, String message, Object[] args);
}

TODO

VFSManager.registerVFS() method

import org.gjt.sp.jedit.io.VFS;

public static void registerVFS(String protocol, VFS vfs);

This method registers the specified virtual filesystem instance as being able to handle buffers whose URL protocol is protocol. In most cases, you would call this method from your plugin's start() method. The following example registers a new instance of the (hypothetical) ZipVFS class as being able to handle URLs with protocol "zip:":

VFSManager.registerVFS("zip",new ZipVFS());

VFSManager.waitForRequests() method

public static void waitForRequests(void);

This method blocks the current thread until the work thread finishes executing all pending requests. Note that if you need to be notified when a buffer finishes loading, it is a much better idea to listen for the BufferUpdate.LOADED EditBus message than to use this method.

VFSManager.runInAWTThread() method

public static void runInAWTThread(Runnable run);

This method adds the specified Runnable to the work thread queue. When the request moves to the front of the queue, it will be run in the AWT event dispatch thread. TODO: give examples of this method's usage.

VFSManager.error() method

public static void runInAWTThread(Component comp, String message, Object[] args);

This method is exactly the same as GUIUtilities.error(), except that it is safe to call it from the work thread.

The VFS Class

TODO

Advanced Topics

Reusing network connections

TODO: FtpVFS reuses the same FtpClient throughout a load or save... describe how this is done with buffer-local properties, and the VFS.load/saveComplete() methods.

Storing login information

TODO: Again, buffer-local properties are you friend...

Pre-load sanity checks

TODO: describe why opening an unreadable file doesn't create an empty buffer; the return value of VFS.load() method

Notes

[1]

Most Swing are not thread safe and can only be called from the AWT thread; attempting to call them from the work thread can lead to race conditions.