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.
import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; // For a simple file system with Unix-style paths and behavior: FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); Path foo = fs.getPath("/foo"); Files.createDirectory(foo);
Translating this into clojure provides
user> (import [com.google.common.jimfs Configuration Jimfs] java.nio.file.Files) java.nio.file.Files user> (-> (Configuration/unix) (Jimfs/newFileSystem) (.getPath "foo") Files/createDirectory)
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
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?
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
<?> is a java generic wildcard. We don't have to bother with that it in clojure. We should interpret this simply as
... 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 [com.google.common.jimfs Configuration Jimfs] java.nio.file.Files java.nio.file.attribute.FileAttribute) java.nio.file.attribute.FileAttribute user> (-> (Configuration/unix) (Jimfs/newFileSystem) (.getPath "foo" (make-array String 0)) (Files/createDirectory (make-array FileAttribute 0))) #object[com.google.common.jimfs.JimfsPath 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.