Useful abstractions for I/O, part three

In the second part of this series, I’ve covered caveats when using the Source and Sink interfaces. This time, I’ll look into another useful abstraction, the Store interface.

Now, what is this interface? Here it comes:

public interface Store extends Source, Sink {
    void delete() throws IOException;
    boolean exists();
    int BUFSIZE = 8 * 1024;
}

Simply put, a Store is a combination of a Source and a Sink which allows you to delete its data from the underlying entity, e.g. a file, and test if it exists. BUFSIZE is a useful constant for allocating buffers.

Now what makes this interface so powerful is that its simple and storage-media-neutral: Implementations could store the data in a file, a preferences node, an array on the heap, a database record or whatever you can imagine. Here’s a straightforward implementation which uses a File:

public final class FileStore implements Store {

    private final File file;

    public FileStore(final File file) { this.file = Objects.requireNonNull(file); }

    @Override public InputStream input() throws IOException {
        return new FileInputStream(file);
    }

    @Override public OutputStream output() throws IOException {
        return new FileOutputStream(file);
    }

    @Override public void delete() throws IOException {
        if (!file.delete())
            throw new FileNotFoundException(file +  (cannot delete));
    }

    @Override public boolean exists() { return file.exists(); }
}

I’ve declared this class final because there is nothing reasonable to customize in a subclass and I like to follow the “design for inheritance or inhibit it” strategy.

An alternative implementation, let’s call it MemoryStore, could store the data in an array on the heap. This is particularly useful for unit testing where you would inject a MemoryStore into the class under test. I’ll leave the implementation to you as an exercise.

Here’s how to use a Store with the general-purpose copy-algorithm introduced in part one:

Store source = ... // some store
Store sink = ... // some store
Copy.copy(source, sink);

Note that the copy-method is just relying on the Source and Sink super-interfaces of the Store interface.

Enjoy!