[英]Clojure reify - automate implementation of Java interface with another macro?
I have a java interface that just emits events, and I'm trying to implement it in Clojure.我有一个只发出事件的 java 接口,我正在尝试在 Clojure 中实现它。 The Java interface is like this (plenty of other methods in reality): Java接口是这样的(现实中还有很多其他方法):
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:我的 Clojure 代码如下所示:
(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.我有大约 75 个函数要“实现”,我所有的实现都是发送一个 map,看起来像{:type calling-function-name-kebab-case:parameter-one-kebab-case parameter-one-value:parameter-two-kebab-case parameter-two-value}
等。对于另一个宏来说似乎已经成熟了——这也会更安全,因为如果底层接口更新了更多功能,我的实现也会如此。
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?我的理想方案是直接读取 .java 代码,但或者我可以手动将 Java 代码粘贴到 map 结构中? Thank you,谢谢,
You can parse out simple method data yourself (I haven't tried the reflection API myself).您可以自己解析出简单的方法数据(我自己没有尝试过反射API)。 Here is a sample, including a unit test to demonstrate.这是一个示例,包括一个用于演示的单元测试。
First, put in the Java source into Clojure data structures:首先,将 Java 源代码放入 Clojure 数据结构中:
(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.然后,一个 function 将方法规范分开,并将 args 解构为类型和名称。 We use a library to convert from CamelCase to kabob-case:我们使用一个库将 CamelCase 转换为 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})))
))
)
The clojure.reflect
namespace contains methods to get information about a class. clojure.reflect
命名空间包含获取有关 class 信息的方法。 I don't think it will give you the parameter names, though.不过,我认为它不会为您提供参数名称。 But you can use it to implement something close to what you are asking for:但是您可以使用它来实现接近您要求的东西:
(ns playground.reify
(:require [clojure.reflect :as r])
(:import EWrapper))
(defn kebab-case [s]
;; TODO
s)
(defn arg-name [index]
(symbol (str "arg" index)))
(defn generate-method [member this cb]
(let [arg-names (mapv arg-name (range (count (:parameter-types member))))
method-name (:name member)]
`(~method-name [~this ~@arg-names]
(~cb {:type ~(keyword (kebab-case method-name))
:args ~arg-names}))))
(defmacro reify-ewrapper [this cb]
`(reify EWrapper
~@(map #(generate-method % this cb) (:members (r/reflect EWrapper)))))
(defn create [cb]
(reify-ewrapper this cb))
The reify-ewrapper macro call will expand to reify-ewrapper 宏调用将扩展为
(reify*
[EWrapper]
(accountSummary
[this arg0 arg1 arg2 arg3 arg4]
(cb {:args [arg0 arg1 arg2 arg3 arg4], :type :accountSummary}))
(accountSummaryEnd
[this arg0]
(cb {:args [arg0], :type :accountSummaryEnd})))
To get the parameter names right, you would probably have to parse the original Java source code, I don't think they are preserved in the byte code.要获得正确的参数名称,您可能必须解析原始 Java 源代码,我认为它们不会保留在字节码中。
Extended solution with parameter names带有参数名称的扩展解决方案
If you really do want the parameter names, here is a small parser that will extract them.如果您确实需要参数名称,这里有一个小型解析器可以提取它们。 You need to first require clojure.string:as cljstr
:您需要首先要求clojure.string:as cljstr
:
(defn parse-method [[name-str arg-str]]
(let [arg-sliced (subs arg-str 0 (cljstr/index-of arg-str ")"))
param-pairs (for [p (cljstr/split arg-sliced #",")]
(into []
(comp (map cljstr/trim)
(remove empty?)
(map symbol))
(cljstr/split p #" ")))]
{:name (symbol (subs name-str (inc (cljstr/last-index-of name-str " "))))
:parameter-types (mapv first param-pairs)
:parameter-names (mapv second param-pairs)}))
(defn parse-interface [s]
(map parse-method (partition 2 1 (cljstr/split s #"\("))))
Relevant bits of the code to output the paramater names now look like this: output 代码的相关位参数名称现在如下所示:
(defn generate-method [member this cb]
(let [arg-names (:parameter-names member)
method-name (:name member)]
`(~method-name [~this ~@arg-names]
(~cb ~(merge {:type (keyword (kebab-case method-name))}
(zipmap (map (comp keyword kebab-case str)
arg-names)
arg-names))))))
(defmacro reify-ewrapper [this cb]
`(reify EWrapper
~@(map #(generate-method % this cb) (parse-interface (slurp "javasrc/EWrapper.java")))))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.