Clojure: Attaching annotations to AOT-compiled methods - clojure

I have a module for the BaseX Java interface which I'm writing in Clojure. The interface provides a number of annotations which can be used to determine how methods are called and optimized; however, I'm having trouble getting these to actually attach to the generated class:
(ns net.dyfis.svnkit_wrapper.SvnWrapper
(:import (org.basex.query QueryModule
QueryModule$Requires
QueryModule$Permission
QueryModule$Deterministic))
(:gen-class
:main false
:extends org.basex.query.QueryModule
:methods [
^{:static true}
[^{QueryModule$Requires QueryModule$Permission/NONE,
Deprecated {}}
cat [java.lang.String] java.lang.String]
^{:static true}
[^{QueryModule$Deterministic {},
QueryModule$Requires QueryModule$Permission/NONE}
catRev [java.lang.String int] java.lang.String]]))
However, only the Deprecated annotation gets attached -- the QueryModule$Requires and QueryModule$Deterministic annotations are silently discarded:
>>> cat
public static java.lang.String net.dyfis.svnkit_wrapper.SvnWrapper.cat(java.lang.String)
>>> cat.getAnnotations()
array(java.lang.annotation.Annotation,[#java.lang.Deprecated()])
This is happening with Clojure 1.4.0-beta6, whereas support for annotations in AOT-compiled methods is supposed to be present from Clojure 1.2. As such, this is presumably a usage error -- but what should I be doing differently?

Except for classes in java.lang (like Deprecated), all classnames must be fully qualified in gen-class declarations. So, your code should be:
^{org.basex.query.QueryModule$Deterministic {},
org.basex.query.QueryModule$Requires org.basex.query.QueryModule$Permission/NONE}
Note that the same restriction does not apply to annotation metadata on/in deftype, defprotocol, or defrecord forms.

Moving the gen-class definition out of the ns declaration allowed the imports to apply:
(ns net.dyfis.svnkit_wrapper.SvnWrapper
(:import (org.basex.query QueryModule
QueryModule$Requires
QueryModule$Permission
QueryModule$Deterministic)))
(gen-class
:name com.indeed.svnkit_wrapper.SvnWrapper
:main false
:extends org.basex.query.QueryModule
:methods [
^{:static true}
[^{QueryModule$Requires QueryModule$Permission/NONE,
Deprecated {}}
cat [java.lang.String] java.lang.String]
^{:static true}
[^{QueryModule$Deterministic {},
QueryModule$Requires QueryModule$Permission/NONE}
catRev [java.lang.String long] java.lang.String]
])

Related

Problems calling a clojure function that takes a map as parameter from java

I'm using maven with several modules, one in java, another in clojure. I'm calling a clojure function from java and want to pass in a HashMap as a parameter and return a HashMap.
(I ran lein uberjar and lein pom on the clojure project to make it work with maven. I can get things to work for clojure function with simple types e.g. String, so the maven setup does work.)
I am getting the following error when I run some java unit tests calling the java code:
java.lang.ClassCastException: class clojure.lang.LazySeq cannot be cast to class java.util.Map (clojure.lang.LazySeq is in unnamed module o
f loader 'app'; java.util.Map is in module java.base of loader 'bootstrap')
How can I get this to work? Is this the proper way to call clojure methods from java?
What about if the HashMap had a POJO object as a value rather than a String?
My java code:
import interop.Core;
public class BillingCalc {
static Map<String, String> nonEmptyItems(Map<String, String> items) {
return Core.non_empty_seats(new HashMap<String, String>());
}
}
My clojure code:
(ns interop.core
(:gen-class
:name interop.Core
:methods [^{:static true} [apply_vat_to_netto [java.math.BigDecimal java.math.BigDecimal] java.math.BigDecimal]
^{:static true} [non_empty_seats [java.util.Map] java.util.Map]]) )
(defn -filter-empty-seats
"filter out those with empty seats"
[seats]
(filter (fn [[_ v]] (pos? (:booked-items v))) seats))
(defn -non_empty_seats
[java-seats]
(-filter-empty-seats (into {} java-seats)))
I guess that your error is caused by this definition in :gen-class:
[non_empty_seats [java.util.Map] java.util.Map]]
From docs for :gen-class:
:methods [ [name [param-types] return-type], ...]
The expected type of returned value is java.util.Map, but filter in -filter-empty-seats returns instance of clojure.lang.LazySeq. You should rewrite -non_empty_seats like this:
(defn -non_empty_seats
[java-seats]
(into {} (-filter-empty-seats java-seats)))

Resolving a keyword into a Malli schema from the default registry in Clojure

How do I resolve a keyword to a schema from the default Malli registry? I seem unable to look up a value in the registry in order to walk it.
(def registry
(atom {}))
(defn register! [type ?schema]
(swap! registry assoc type ?schema))
;; Combine the default registry with our own mutable registry.
(mreg/set-default-registry!
(mreg/composite-registry
(mreg/fast-registry (malli/default-schemas))
(mreg/mutable-registry registry)))
(register! :db/kasse
[:map
[:id [:int {:primary-key true :db-generated true}]]
[:odlingsplats [:string {:foreign-key "odlingsplatser"}]]
[:diameter_m :int]
[:djup_m :int]
[:volym_m2 [:int {:db-generated true}]]])
(malli/walk
:db/kasse
(malli/schema-walker identity))
;; => :db/kasse
I've tried wrapping :db/kasse in different functions from malli but none seem to do the lookup and malli/-lookup is private. Just running (:db/kasse malli/default-registry) does not work either. Using malli/schema seems like the obvious choice but it seemingly has no effect.
(malli/walk
(malli/schema :db/kasse)
(malli/schema-walker identity))
;; => :db/kasse
Calling malli/deref was the answer:
(malli/walk
(malli/deref :db/kasse)
(malli/schema-walker identity))
;; => [:map [:id [:int {:primary-key true, :db-generated true}]] [:odlingsplats [:postgres/string {:foreign-key "odli\
ngsplatser"}]] [:diameter_m :int] [:djup_m :int] [:volym_m2 [:int {:db-generated true}]] [:namn {:optional true} [:po\
stgres/string {:db-generated true}]]]
Thank you to ikitommi at the Clojurians slack for providing the answer. He also provided an explanation as to why the library works this way:
The :db/kasse returned is a Malli Schema instance, it’s print output is just the form, so looks like keyword. It’s type is :malli.core/schema, which is the internal eager reference, like a Var in Clojure. If you want to get the schema behind it, you can m/deref it. But, calling m/validate on :db/kasse works too. the :malli.core/schema forwards the calls to the actual instance, like Var.

Problem overwritting java method from clojure

I am desperetly trying to create a class that with a "toString" method from clojure
According to clojure docs the following should work:
(ns override-test.simpleClass
(:gen-class
:name simpleClass
:methods [[^{Override {}} toString [] String]]
:state state
:init init
:constructors {[String] []}))
(defn -init
[name_]
[[] (atom name_)])
(defn -toString [this]
(deref (.state this)))
However evaluating
(simpleClass. "test")
Throws
CompilerException java.lang.ClassFormatError: Duplicate method name "toString" with signature "()Ljava.lang.String;" in class file simpleClass, compiling:(override_test/simpleClass.clj:19:3)
Any incites of what i might be doing wrong ?
As Biped Phill mentioned the problem was that toString seems to be already implemented by virtue of the automatic subclassing mechanism of gen-class. Probably Ljava.lang. String is treated as a (?) superclass and toString is added automatically, so i just had to remove it from :methods which is for not inherited methods and it worked like a charm.

how to get the Class generated by gen-class in clojure

I am using some java lib which require the access the class generated by gen-class
(ns cljfx.test
(:import some.java.Lib))
(gen-class :name Main)
(defn -main [& arg]
(Lib/method-require-class-arg (classOf Main)))
the prototype of method-require-class-arg is
public static void method-require-class-arg(Class someClass) {
// ...
}
How to write the (classOf Main) part in the first snippet?
Your gen-class example is incorrect. If you use
(gen-class :name cljfx.test.Main)
then just cljfx.test.Main will return the Class object.
You can use Class/forName method.
Ex: (Class/forName "java.lang.String")
In your case it should be (Class/forName "Main") and you need to make sure you enable aot in your project.clj

Declare array as return type in gen-class method declaration in Clojure

How to declare an array in method declaration in gen-class?
(ns foo.bar
(:gen-class
:methods [[parseString [String Object] Object]]))
That works fine. But the return type is really an array. How I can declare that so Java can understand it?
Try
(ns foo.bar
(:gen-class
:methods [[parseString [String Object] "[Ljava.lang.Object;"]]))
I needed a
static Number[][] method(int, Number[][]);
signature, in a similar way:
(:gen-class
:methods [#^{:static true} [method [int "[[Ljava.lang.Number;"] "[[Ljava.lang.Number;"]])
seemed to work.