Clojure cannot import JavaFX classes with static initializers

I play with Clojure (1.6) and JavaFX 8, and right at the beginning I ran into a problem. For example, this very simple code does not work:

(ns xxyyzz.core) (gen-class :name "xxyyzz.core.App" :extends javafx.application.Application :prefix "app-") (defn app-start [app stage] (let [button (javafx.scene.control.Button.)])) (defn launch [] (javafx.application.Application/launch xxyyzz.core.App (into-array String []))) (defn -main [] (launch)) 

This is the last part of the stack trace that seems relevant:

 Caused by: java.lang.ExceptionInInitializerError at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:340) at clojure.lang.RT.classForName(RT.java:2070) at clojure.lang.Compiler$HostExpr.maybeClass(Compiler.java:969) at clojure.lang.Compiler$HostExpr.access$400(Compiler.java:747) at clojure.lang.Compiler$NewExpr$Parser.parse(Compiler.java:2494) at clojure.lang.Compiler.analyzeSeq(Compiler.java:6560) ... 48 more Caused by: java.lang.IllegalStateException: Toolkit not initialized at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:276) at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:271) at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:562) at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:524) at javafx.scene.control.Control.<clinit>(Control.java:81) ... 55 more 

I don’t speak Java at all, but by researching this, it seems that the problem is with Clojure and the way to import Java classes. If I understood correctly, during import it starts the static class initializer and for some JavaFX classes ( Button in my case), which fails.

Guess I have two questions: do I understand this error correctly? And secondly, is there any way around this problem? I tried pulling out the import inside functions instead of declaration (ns), but it still doesn't work.

If there is no Clojure fix, is this fix possible with some additional Java code?

Any tips and pointers are welcome!

+6
source share
1 answer

I could not find a way to change Clojure import behavior, but I found a couple of hackers to do what I needed.

First, JavaFX provides constructor classes, so the cleanest way in this particular case would be to use ButtonBuilder to create new buttons.

The second way is to write a simple Java class that wraps the Button element and then imports this packaging class from Clojure. This is a good solution when working with fewer problem classes.

The third way is to import at runtime, something like this (thanks to the guys from # clojure for helping with this):

 (defn import-at-runtime [name] (.importClass (the-ns *ns*) (clojure.lang.RT/classForName name))) (import-at-runtime "javafx.scene.control.Button") (let [button (eval `(new ~(symbol "javafx.scene.control.Button") ~"Button Text")) 

In the end, it looks like an ugly wart in the Clojure Java interface, it would be great if it could be fixed in the future.


UPDATE: there is also clojure.lang.RT / classForNameNonLoading , but unfortunately it is not public from Clojure 1.6, it is easy to repeat it in Clojure, though:

 (fn [^String class-name] (Class/forName class-name false (clojure.lang.RT/baseLoader))) 

Later, a class can be created using clojure.lang.Reflector/invokeConstructor .

+3
source

All Articles