Search code examples
clojureclojure-java-interop

Better way to make init a java builder class from clojure map?


everyone

I'm try to write a function to wrap CsvReadOptions.Builder.html in clojure .

The function will take a map like this : {:header true :locale "US"}, the function will configure the builder according to the map.

(defn reader-options [ opts ]
  (let [ b (CsvReadOptions$Builder.)]
    (cond-> b
      (contains? opts :locale ) (.locale (:locale opts))
      (contains? opts :header ) (.header (:header opts))
        true (.build ))
  )
)

Sorry if it is too much to ask, is there a better way in clojure to accomplish this ? because the key works duplicates in single line.

      (contains? opts :locale ) (.locale (:locale opts))    

Thank you again for any suggestions.


Solution

  • In theory, you can write a macro that expands into the code you need:

    (defmacro defbuilder [fn-name builder-class fields]
      (let [opts (gensym)]
        `(defn ~fn-name [~opts]
           (cond-> (new ~builder-class)
             ~@(mapcat
                (fn [field-sym]
                  (let [field-kw (keyword (name field-sym))]
                    `((contains? ~opts ~field-kw)
                      (. ~field-sym (get ~opts ~field-kw)))))
                fields)
             true (.build)))))
    

    Now,

    (defbuilder options-from-map CsvReadOptions$Builder
      [header locale...])
    

    will generate:

    (clojure.core/defn options-from-map [G__12809]
      (clojure.core/cond-> (new CsvReadOptions$Builder)
        (clojure.core/contains? G__12809 :header)
        (. header (clojure.core/get G__12809 :header))
        (clojure.core/contains? G__12809 :locale)
        (. locale (clojure.core/get G__12809 :locale))
        ...
        true (.build)))
    

    In practice, however, this code is:

    • less readable and less maintainable (there are libraries that heavily use macros and they are a pain to read), and
    • you may want to add additional specific processing for some methods (for example, converting a locale string to a Locale object).

    Thus, you are much better off writing a wrapper by hand - or, if you need to use a Builder just once in your code, omit the wrapper altogether and use interop.