Clojure macro for calling Java setters based on a map? - java

I'm writing a Clojure wrapper for the Braintree Java library to provide a more concise and idiomatic interface. I'd like to provide functions to instantiate the Java objects quickly and concisely, like:
(transaction-request :amount 10.00 :order-id "user42")
I know I can do this explicitly, as shown in this question:
(defn transaction-request [& {:keys [amount order-id]}]
(doto (TransactionRequest.)
(.amount amount)
(.orderId order-id)))
But this is repetitive for many classes and becomes more complex when parameters are optional. Using reflection, it's possible to define these functions much more concisely:
(defn set-obj-from-map [obj m]
(doseq [[k v] m]
(clojure.lang.Reflector/invokeInstanceMethod
obj (name k) (into-array Object [v])))
obj)
(defn transaction-request [& {:as m}]
(set-obj-from-map (TransactionRequest.) m))
(defn transaction-options-request [tr & {:as m}]
(set-obj-from-map (TransactionOptionsRequest. tr) m))
Obviously, I'd like to avoid reflection if at all possible. I tried defining a macro version of set-obj-from-map but my macro-fu isn't strong enough. It probably requires eval as explained here.
Is there a way to call a Java method specified at runtime, without using reflection?
Thanks in advance!
Updated solution:
Following the advice from Joost, I was able to solve the problem using a similar technique. A macro uses reflection at compile-time to identify which setter methods the class has and then spits out forms to check for the param in a map and call the method with it's value.
Here's the macro and an example use:
; Find only setter methods that we care about
(defn find-methods [class-sym]
(let [cls (eval class-sym)
methods (.getMethods cls)
to-sym #(symbol (.getName %))
setter? #(and (= cls (.getReturnType %))
(= 1 (count (.getParameterTypes %))))]
(map to-sym (filter setter? methods))))
; Convert a Java camelCase method name into a Clojure :key-word
(defn meth-to-kw [method-sym]
(-> (str method-sym)
(str/replace #"([A-Z])"
#(str "-" (.toLowerCase (second %))))
(keyword)))
; Returns a function taking an instance of klass and a map of params
(defmacro builder [klass]
(let [obj (gensym "obj-")
m (gensym "map-")
methods (find-methods klass)]
`(fn [~obj ~m]
~#(map (fn [meth]
`(if-let [v# (get ~m ~(meth-to-kw meth))] (. ~obj ~meth v#)))
methods)
~obj)))
; Example usage
(defn transaction-request [& {:as params}]
(-> (TransactionRequest.)
((builder TransactionRequest) params)
; some further use of the object
))

You can use reflection at compile time ~ as long as you know the class you're dealing with by then ~ to figure out the field names, and generate "static" setters from that. I wrote some code that does pretty much this for getters a while ago that you might find interesting. See https://github.com/joodie/clj-java-fields (especially, the def-fields macro in https://github.com/joodie/clj-java-fields/blob/master/src/nl/zeekat/java/fields.clj).

The macro could be as simple as:
(defmacro set-obj-map [a & r] `(doto (~a) ~#(partition 2 r)))
But this would make your code look like:
(set-obj-map TransactionRequest. .amount 10.00 .orderId "user42")
Which I guess is not what you would prefer :)

Related

Clojure gen-class this keyword

Is it possible to refer to Java's 'this' keyword from within a gen-class method?
I am trying to implement daredesm's answer here, in Clojure. However, when I try to use 'this' in the run function, I get "java.lang.RuntimeException: Unable to resolve symbol: this in this context."
(gen-class
:name ClipboardListener
:extends java.lang.Thread
:implements [java.awt.datatransfer.ClipboardOwner]
:prefix ClipboardListener-
:methods [[takeOwnership [Transferable] void]])
(def systemClipboard (.getSystemClipboard (java.awt.Toolkit/getDefaultToolkit)))
(defn ClipboardListener-run []
(let [transferable (.getContents systemClipboard this)]
(.takeOwnership transferable)))
(defn ClipboardListener-lostOwnership [clipboard trasferable] (prn "hit lost"))
(defn ClipboardListener-takeOwnership [transferable] (prn "hit take"))
(defn processClipboard [transferable clipboard] (prn "hit process"))
Note: This is my first time generating Java classes in Clojure, so any general feedback/resources is greatly appreciated.
Instance methods can take an implicit 'self' arg- as the first argument. So to take your example:
(defn ClipboardListener-run [this]
(let [transferable (.getContents systemClipboard this)]
(.takeOwnership transferable)))
Note the this argument :)
Same goes for any instance method, e.g:
(defn ClipboardListener-toString [this]
"override Object#toString with something cool")
Have a look at this (no pun intended) for more info on gen-class.
Also consider reify for cases like Runnable, Callable, e.t.c where you just need to implement a small-ish interface.

Link vector element in Clojure

I'm trying to read a file in a macro in Clojure.
I'm launching my macro with that line :
(def result (rd [s (FileReader. (File. "myFile.txt"))] (.read s)))
where "rd" is the name of my macro.
The prototype of my macro is like that :
(defmacro rd
([] nil)
([arg] arg)
([[variable val] expr]
)
)
The thing is that I can "execute" the FileReader, but when I'm trying to "execute" expr (.read s), it's not working because s is not known.
So I'm trying to link my elements of a vector to made s known, so I want "variable" pointed by val.
I'm not sure I'm in what I want to do, so if you see other ways, I'm up to it.
Thanks in advance guys.
if you need to read the file at runtime, as you said, you need to introduce the var.. something like this:
(defmacro rd [[variable val] expr]
`(let [~variable ~val]
~expr))
and then your macro call would expand to this:
(let [s (FileReader. (File. "myFile.txt"))] (.read s))

Log all method calls to a Java object from Clojure

I'm working on a Clojure wrapper for some Java library.
In order to help me debug, I would like to log all calls to specific Java objects.
After searching how I might do this from a raw Java perspective, I discovered the java.lang.reflect.Proxy class and java.lang.reflect.InvocationHandler interfaces.
This led me to find a small snipped posted by R.H. a few years ago :
(defn debug-proxy [obj]
(java.lang.reflect.Proxy/newProxyInstance
(.. obj getClass getClassLoader)
(.. obj getClass getInterfaces)
(proxy [java.lang.reflect.InvocationHandler] []
(invoke [proxy m args]
(apply println m args)
(.invoke m obj args)))))
Small note: I had to change java.lang.reflect.Proxy.newProxyInstance into java.lang.reflect/newProxyInstance to make it work in my REPL, since newProxyInstance is a static method. Corrected version is given so that you may cut & paste it.
With this version, calling methods on the proxy appear work at first :
youpi.core=> (.charAt (debug-proxy "foo") 2)
#object[java.lang.reflect.Method 0x53ce1eb0 public abstract char java.lang.CharSequence.charAt(int)] 2
=> \o
But sadly, calling a method that accepts no argument will not work:
youpi.core=> (.toUpperCase (debug-proxy "foo"))
IllegalArgumentException No matching field found: toUpperCase for class com.sun.proxy.$Proxy1 clojure.lang.Reflector.getInstanceField (Reflector.java:271)
java.lang.IllegalArgumentException: No matching field found: toUpperCase for class com.sun.proxy.$Proxy1
at clojure.lang.Reflector.getInstanceField(Reflector.java:271)
at clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:315)
at youpi.core$eval5981.invokeStatic(form-init3685547971046661105.clj:1)
at youpi.core$eval5981.invoke(form-init3685547971046661105.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6927)
at clojure.lang.Compiler.eval(Compiler.java:6890)
at clojure.core$eval.invokeStatic(core.clj:3105)
at clojure.core$eval.invoke(core.clj:3101)
at clojure.main$repl$read_eval_print__7408$fn__7411.invoke(main.clj:240)
at clojure.main$repl$read_eval_print__7408.invoke(main.clj:240)
<...>
at java.lang.Thread.run(Thread.java:745)
I tried to update the invoke implementation following advice given in this clojuredocs comment, but it didn't fix.
Here is my (still broken) attempt at implementing the comment's suggestion:
(defn- debug-print-invoke [obj proxy m args]
(apply println m args)
(.invoke m obj args))
(defn debug-proxy [obj]
(java.lang.reflect.Proxy/newProxyInstance
(.. obj getClass getClassLoader)
(.. obj getClass getInterfaces)
(proxy [java.lang.reflect.InvocationHandler] []
(invoke
([proxy m] (debug-print-invoke obj proxy m []))
([proxy m args] (debug-print-invoke obj proxy m args))))))
A good answer would either fix the snippet I've been working with, or suggest an alternative way to achieve the same goal from Clojure.

Calling a very simple clojure function from Java does not work

I'm very new in learning Clojure. This intended to be my first and very simple Clojure tries in which I call a simple Clojure method from inside java code. Unfortunately it does not work. The Compilation is successful and from the Clojure REPL the written function does as it was ordered, but when calling from Java it says the following:
Exception in thread "main" java.lang.IllegalArgumentException: Wrong number of args (2) passed to: ClojNum$-myinc
at clojure.lang.AFn.throwArity(AFn.java:439)
at clojure.lang.AFn.invoke(AFn.java:43)
at com.experimental.clojure.test.ClojNum.myinc(Unknown Source)
at com.experimental.clojure.java.JavaCaller.main(JavaCaller.java:14)
Here is the very simple Clojure code:
(ns com.experimental.clojure.test.ClojNum
(:gen-class
:init init
:name com.experimental.clojure.test.ClojNum
:methods [
[myinc [int] int]
]))
(defn -init [] [[] (atom [])])
(defn myinc "comment" [x] (+ x 1))
(defn -myinc "comment" [x] (myinc x))
And the java part:
package com.experimental.clojure.java;
import com.experimental.clojure.test.ClojNum;
public class JavaCaller {
/**
* #param args
*/
public static void main(String[] args) {
int i = 0;
System.out.println(i);
ClojNum c = new ClojNum();
i = c.myinc(0);
System.out.println(i);
}
}
What did I do wrong?
(Note again: This is primitve test code just to make a first successful function call)
Thanks for the help, I'm clueless.
Jeremy's link in the comments show you one way to call a static method in a clojure class. If you want to call a clojure function on an object instance, you need to add a parameter to your wrapper method definition:
(defn -myinc "comment" [this x] (myinc x))
The 'this' parameter is required for any non-static wrapper function. Clojure threw an exception because it received two parameters for a function only defined with one. Note, you do not change anything in your :gen-class :methods section or the myinc function definition itself.
The documentation is a bit sparse, but examples of this can be found at:
http://clojure.org/compilation (the last example on the page shows instance methods).

Class introspection in Common Lisp

Java's java.lang.Class class has a getDeclaredFields method which will return all the fields in a given class. Is there something similar for Common Lisp? I came across some helpful functions such as describe, inspect and symbol-plist after reading trying out the instructions in Successful Lisp, Chapter 10 (http://www.psg.com/~dlamkins/sl/chapter10.html). But none of them do what getDeclaredFields does.
You should use class-slots and/or class-direct-slots (both are from CLOS Metaobject Protocol, MOP). class-slots returns all slots that are present in given class, and class-direct-slots returns all slots are declared in class definition.
Different lisp implementations implement MOP slightly differently; use closer-mop package to have uniform interface to MOP.
Example:
(defclass foo ()
(foo-x))
(finalize-inheritance (find-class 'foo)) ;this is needed to be able to query class slots and other properties. Or, class is automatically finalized when its first instance is created
(class-slots (find-class 'foo))
=> (#<STANDARD-EFFECTIVE-SLOT-DEFINITION FOO-X>)
(slot-definition-name (first (class-slots (find-class 'foo))))
=> FOO-X
Example :
(defun inspect (( object standard-object))
(inspect-rec (class-slots (class-of object)) object) )
(defun inspect-rec (slots o)
( if(atom slots) ()
(let ((sn (slot-definition-name (car slots)))) (cons (list sn '=> ( slot-value o sn) ) ( inspect-rec (cdr slots) o)))))
I think you're looking for the MetaObject Protocol for CL.

Categories

Resources