I have a java interface that just emits events, and I'm trying to implement it in Clojure. The Java interface is like this (plenty of other methods in reality):
public interface EWrapper {
void accountSummary(int reqId, String account, String tag, String value, String currency);
void accountSummaryEnd(int reqId);
}
And my Clojure code looks like:
(defn create
"Creates a wrapper calling a single function (cb) with maps that all have a :type to indicate
what type of messages was received, and event parameters
"
[cb]
(reify
EWrapper
(accountSummary [this reqId account tag value currency]
(dispatch-message cb {:type :account-summary :request-id reqId :account account :tag tag :value value :currency currency}))
(accountSummaryEnd [this reqId]
(dispatch-message cb {:type :account-summary-end :request-id reqId}))
))
I have about 75 functions to "implement" and all my implementation does is dispatching a map looking like {:type calling-function-name-kebab-case :parameter-one-kebab-case parameter-one-value :parameter-two-kebab-case parameter-two-value}
etc. It seems ripe for another macro - which would also be safer as if the underlying interface gets updated with more functions, so will my implementation.
Is that possible? How do I even get started? My ideal scenario would be to read the .java code directly, but alternatively I can manually paste the Java code into a map structure? Thank you,
You can parse out simple method data yourself (I haven't tried the reflection API myself). Here is a sample, including a unit test to demonstrate.
First, put in the Java source into Clojure data structures:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[camel-snake-kebab.core :as csk]
[schema.core :as s]
[tupelo.string :as ts]))
(def java-spec
(quote {:interface EWrapper
:methods [; assume have structure of
; <ret-type> <method-name> <arglist>, where <arglist> => (<type1> <name1>, <type2> <name2> ...)
void accountSummary (int reqId, String accountName, String tag, String value, String currencyName)
void accountSummaryEnd (int reqId)
]
}))
Then, a function to pull apart the method specs, and deconstruct the args into types & names. We use a library to convert from CamelCase to kabob-case:
(defn reify-gen
[spec-map]
(let [methods-data (partition 3 (grab :methods spec-map))
; >> (spyx-pretty methods-data)
method-entries (forv [mdata methods-data]
(let [[ret-type mname arglist] mdata ; ret-type unused
mname-kebab (csk/->kebab-case mname)
arg-pairs (partition 2 arglist)
arg-types (mapv first arg-pairs) ; unused
arg-names (mapv second arg-pairs)
arg-names-kebab (mapv csk/->kebab-case arg-names)
arg-names-kebab-kw (mapv ->kw arg-names-kebab)
mresult (list mname (prepend
(quote this)
arg-names)
(list
mname-kebab
(glue {:type (->kw mname-kebab)}
(zipmap arg-names-kebab-kw arg-names))))]
; (spyx-pretty mresult)
mresult ))]
(->list
(prepend
(quote reify)
(grab :interface spec-map)
method-entries))))
And a unit test to demonstrate:
(dotest
(newline)
(is= (spyx-pretty (reify-gen java-spec))
(quote
(reify
EWrapper
(accountSummary
[this reqId accountName tag value currencyName]
(account-summary
{:type :account-summary
:req-id reqId,
:account-name accountName,
:tag tag,
:value value,
:currency-name currencyName}))
(accountSummaryEnd
[this reqId]
(account-summary-end {:type :account-summary-end, :req-id reqId})))
))
)