我正在处理一个事件流,编码为Google Protobuffers,存储为字节。Protobuffers的诀窍在于,你必须在加载一个东西之前知道它的类。另一个技巧是,我加载的事件是嵌套的。
。我有最内层事件的ByteArray
。幸运的是,在最内层的事件中有一个字段指定了最内层事件的类型,因此我可以确定应该由哪个类加载它。
好消息:解析函数在每个候选类上具有相同的名称(parseFrom
)。坏消息是:我需要调用的函数是静态的,并且根据数量和类型进行分派。
我要做的是:
(ns do-the-thing
(import com.thing.place Type$Subtype Type$SecondSubType)
(def decl-obj-map
{:type-subtype Type$SubType
:type-second-subtype Type$Second$SubType})
(defn call-fn
[class n-args method]
(let [o (gensym)
args (repeatedly n-args gensym)
assure-symbol (fn [thing] (if (symbol? thing) thing (symbol thing)))
method (assure-symbol method)]
(eval
`(fn [~o ~@args]
(. ~(with-meta o {:tag class})
(~method ~@args))))))
(def event-type (.getSubtypeField event-obj)
(def parse-func (call-fn (event-type decl-obj-map) 0 "parseFrom")
(parse-func (.getByteArrayFromInnerObj inner-obj))
。这根本行不通。代替call-fn
,我也从clojure.contrib
尝试了这个方法。它会抛出永远难以理解的IllegalArgumentException array element type mismatch java.lang.reflect.Array.set (Array.java:-2)
错误。
有人知道吗?
这段代码在应用于类的静态方法时存在一些问题,最重要的是类必须在编译时为dot特殊形式和同类(注:(Class/method ...)
扩展为(. Class method ...)
)所知。因此,使用此策略,您必须在每次调用时使用eval
——必须将eval
移到返回的函数中(在对其进行了一些修补之后)。这是不可取的。
你可以用反射来解决这个问题,例如
(defn call-fn [^Class class method]
(fn [& args]
(clojure.lang.Reflector/invokeStaticMethod
(.getName class)
(str method)
(to-array args))))
(def my-abs (call-fn Math "abs"))
(my-abs -1) ;=> 1
但是我认为你把问题设计得太过分了。考虑从规范到函数的映射,而不是从类型关键字到类的映射,例如
(def spec->parser
{:type-subtype {1 #(Type$Subtype/parseFrom %) 2 #(Type$Subtype/parseFrom % %2)}
:type-second-subtype {1 #(Type$SecondSubtype/parseFrom %) ...}})
那么你的call-fn
就是
(defn get-parser [type-kw nargs] (get-in spec->parser [type-kw nargs]))
或简写为#(get-in spec->parser &%)