Tengo una interfaz java que solo emite eventos, y estoy tratando de implementarla en Clojure. La interfaz de Java es así (muchos otros métodos en realidad):

public interface EWrapper {

    void accountSummary(int reqId, String account, String tag, String value, String currency);
    void accountSummaryEnd(int reqId);
}

Y mi código Clojure se ve así:

(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}))

))

Tengo alrededor de 75 funciones para "implementar" y todo lo que hago es enviar un mapa con el aspecto de {:type calling-function-name-kebab-case :parameter-one-kebab-case parameter-one-value :parameter-two-kebab-case parameter-two-value}, etc. Parece estar listo para otra macro, que también sería más segura como si la interfaz subyacente se actualizara con más funciones, también lo hará mi implementación.

¿Es eso posible? ¿Cómo empiezo? ¿Mi escenario ideal sería leer el código .java directamente, pero alternativamente puedo pegar manualmente el código Java en una estructura de mapa? Gracias,

0
alex314159 18 abr. 2020 a las 00:58

2 respuestas

El espacio de nombres clojure.reflect contiene métodos para obtener información sobre una clase. Sin embargo, no creo que le dé los nombres de los parámetros. Pero puede usarlo para implementar algo cercano a lo que está pidiendo:

(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))

La llamada de macro reify-ewrapper se expandirá a

(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})))

Para obtener los nombres de parámetros correctos, probablemente tenga que analizar el código fuente original de Java, no creo que se conserven en el código de bytes.

0
Rulle 17 abr. 2020 a las 22:28

Puede analizar datos de métodos simples usted mismo (no he probado la API de reflexión yo mismo). Aquí hay una muestra, que incluye una prueba unitaria para demostrar.

Primero, ingrese la fuente Java en las estructuras de datos de 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)
                      ]
          }))

Luego, una función para separar las especificaciones del método y deconstruir los argumentos en tipos y nombres. Usamos una biblioteca para convertir de CamelCase a 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))))

Y una prueba unitaria para demostrar:

(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})))

      ))
  )
0
Alan Thompson 17 abr. 2020 a las 23:09