In Java, java.io.File
is one of the more frequently used low-level API objects. It also happens to be lacking in some basic functionality that we’ve all needed at some point, doesn’t provide different representations/API for files and directories and doesn’t throw fine-grained exceptions to differentiate between different types of error conditions (i.e., file already exists, directory not empty, invalid path, etc.)
At Carfey, we’ve been using our own File and Directory classes for years and have been enhancing their utility over time by adding new functionality as the need arises – File.moveToFile(File newFile), File.writeFromStream(InputStream is), Directory.listFilesRecursively(), Directory.emptyDirectory
, etc.
Probably the only real pain you’d encounter using an alternate representation of java.io.File
would be in interacting with other libraries that would need access to java.io.File
objects. That is easily accommodated by exposing a getJavaFile()
method on each class.
Free for Use
Attached you can find our classes and test code available under the MIT licence. It does depend on our open-source Date library which we spoke about here. If you’d rather skip the extra library, you can drop the getLastModifiedDate
method from the File class.
Here are some of the highlights.
First, we use different classes to represent File and Directory. Rather than having to invoke isFile()
and isDirectory()
, we have the type to guide us. Our Directory class has some public constants, such as PATH_SEPARATOR
which is equivalent to java.io.File.pathSeparator
but uses standard constant naming convention. Also TEMP_DIR
, which provides easy access to the temporary directory loaded using the System property java.io.tmpdir
. We get typed Exceptions such as DirectoryNotCreatedException, InvalidDirectoryException, DirectoryNotDeletedException
with descriptive messages when attempting to construct a Directory reference or physically create/delete a Directory but encountering problems. We can listFiles()
or even listFilesRecursively()
on a Directory. Want to completely empty a directory of files and subdirectories? Use emptyDirectory()
.
Our File class similarly gives us typed Exceptions (InvalidFileException, FileIOException, FileNotDeletedException, FileNotDeletedException
) with descriptive messages on construction, create or delete of physical Files but encountering problems. We have very convenient move and write methods: moveToFile(File moveFile), writeFromStream(InputStream is), write(String contents)
. More convenience methods for getting a FileInputStream
or FileOutputStream
for the given File. Even a getDirectory()
that will return one of our Directory objects representing the containing Directory.
Also in the bundle is our own IOUtil class that provides some necessary functions for our File and Directory classes. You happen to get some of its own helpful functionality including
public static final byte[] getBytes(InputStream is) public static long copyStream(InputStream src, OutputStream dest, int bufferSize, boolean flushEachRead, StreamListener... listeners) public static InputStream streamFromReader(Reader reader) public static String streamAsString(InputStream is, String encoding) public static <T extends Serializable> T cloneThroughSerialize(T t) public static Object deserialize(InputStream is) public static void serializeToOutputStream(Serializable ser, OutputStream os)
If you’re an Eclipse user, this brings up an interesting issue with resolving imports. If you add Carfey File and Directory to your project, you might want to avoid the annoyance of having to select the type of File you want to use. Eclipse allows you to customize how imports are done. If you’ve never done this before, check it out. You can use with other common names such as List, Util, StringUtil, etc.
Now when you “Organize Imports” with Ctrl+Shift+O, you won’t be prompted to choose which File you want.
Happy coding and if you find any bugs or have questions, leave a comment here.
I think having separate classes for files and directories is not a good decision because classes are static, i.e. they don’t change at runtime. However, the file system is not static and a file system node can change its state at any time, e.g. from not existing to file, then not existing again, then directory, then not existing again etc. Therefore, a VFS API typically has one class to represent all these states. A more modern VFS API (e.g. NIO.2 and implementations like TrueZIP Path) would even separate file system addressing (Path) from actual file system I/O (Files).
I can’t disagree with your analysis that these become static representations of something that is dynamic in nature (then again, aren’t most objects?), these aren’t meant to be anything more than a easy-to-use toolset for basic operations against files and directories. If you’re going to be changing /home/craig/somepath/ (dir) to /home/craig/somepath (file), I’d guess you have more intersting things to worry about.
I’d recommend using the Files class from Google’s Guava library instead of any homegrown code.
In Java 7, this is anyway all obsolete, as the File API got a major overhaul.
Nice thing about the libs we provide for free is they are extremely small and lightweight. But sure, if there’s a library you prefer, by all means keep using it.
The target for this library is JDK 5 and 6. We wrote these classes years ago and there is a little similarity to the JDK 7 classes. We currently use java.io.File and in future, we’ll probably use java.nio.file.Path if we choose to maintain the library. It does give us some more functionality and can still maintain our preferred distinction between Files and Directories.
Many of the issues and short comings with java.io.File were addressed in JDK7, see the java.nio.file package.