No matching method while translating java code

I was recently attempting to use jimfs from clojure. I was working on a system that dealt with files, and wanted to be able to test it quickly and in parallel. There is an example of basic use in the readme.


// For a simple file system with Unix-style paths and behavior:
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
Path foo = fs.getPath("/foo");

Translating this into clojure provides

user> (import [ Configuration Jimfs]
user> (-> (Configuration/unix)
          (.getPath "foo")

But when I run this, there is an exception:

CompilerException java.lang.IllegalArgumentException: No matching method: createDirectory, compiling:(/tmp/form-init1528222455965905079.clj:1:1

The exception says that the method Files/createDirectory does not exist. At least not where it takes only one argument, which has whatever type .getPath returns.

This is a direct translation of the example java code. Is the jimfs readme broken? Isn't clojure suppose to have great interop with java?

Reading javadoc

In order to solve this we should look and see if Files/createDirectory exists. The easiest way to do this is to check for javadoc. This shows the method exists in java 7, but it has an unexpected signature. It takes two arguments?

public static Path createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException

The first argument is of type Path and named dir. This seems reasonable to expect as a type from .getPath. The second argument has a type description FileAttribute<?>... and is named attrs. The type description FileAttribute<?>... should be parsed as two things. The first is FileAttribute<?>. The <?> is a java generic wildcard. We don't have to bother with that it in clojure. We should interpret this simply as FileAttribute.

The ... makes this type varargs. This is a mechanism to implement variable arguments in java. The method receives an array for the second argument. In java you don't have to pass anything for that argument. The java compiler notices the method takes a varargs and just passes an empty array for you. It is very similar to using [& args] in clojure.

Handling java varargs

But the clojure compiler doesn't handle auto-generating an array for us. We have to create a java array and pass it in. In these cases I use (make-array type 0) to pass an empty array, and (into-array type [arg1 arg2]) to send data.

After solving this for Path.getPath as well, we finally have code that works.

user> (import [ Configuration Jimfs]
user> (-> (Configuration/unix)
          (.getPath "foo" (make-array String 0))
          (Files/createDirectory (make-array FileAttribute 0)))
#object[ 0x5fabbf2a "foo"]

This unfortunately makes the code quite a bit uglier. It would be nice if the clojure compiler did this for us. Ticket CLJ-440 exists to track work done for omitting varargs, but there has not been any activity on it in a couple years.

For now, this remains a potential hazard for java interop and translating java examples.