Generate java beans with clojure

Is there a way to easily generate java beans based on a vector in clojure? For example, specify a vector as follows:

[ String :key1 Integer :key2 ] 

I would like it to generate code as follows:

 public class NotSureWhatTheTypeWouldBeHere { private String key1; private Integer key2; public NotSureWhatTheTypeWouldBeHere() {} public NotSureWhatTheTypeWouldBeHere(String key1, Integer key2) { this.key1 = key1; this.key2 = key2; } public void setKey1(String key1) { this.key1 = key1; } public String getKey1() { return this.key1; } public void setKey2(Integer key2) { this.key2 = key2; } public String getKey2() { return this.key2; } // and equals,hashCode, toString, etc. } 

In context, I would like to write an application written in java, but calls to a library written in clojure. So the return values ​​should be java beans (I know they don't have to be, but I would like them to be). One way would be to define a model in java and then use clojure to use regular java interop to populate the model in clojure code, but I like the idea of ​​a short clojure vector (or map) expanding to (verbose) java bean.

+7
source share
3 answers

I don’t think your Java code will play well with automatically generated Java bean compatible classes. You must have at least an interface on the Java side to understand what Clojure will return. Without this, you will have to return to:

 Object result = callClojureLib(params); 

Then, regardless of whether the Java result implements the real result, your Java code will have to do all kinds of reflection manuals to be able to even call the setter, since you lack the class specification.

Another approach to the problem would be to use the java.util.Map interface as a contract between Java and the Clojure worlds. Thus, you can simply use simple Clojure maps as transfer objects, as they can be assigned to java.util.Map :

 user=> (isa? (class {}) java.util.Map) true 
+3
source

Far from perfect and you probably have a lot of unforeseen problems when you try to use it, but I think you can start with something like:

  (ns genbean) (defn -init [] [[] (atom {})]) (defn -toString [this] (str @(.state this))) (defn -equals [this other] (= @(.state this) @(.state other))) (defn -hashCode [this] (hash @(.state this))) (defn set-field [this key value] (swap! (.state this) into {key value})) (defn get-field [this key] (@(.state this) key)) (defn gen-method-defs [fields] (mapcat (fn [[name type]] [[(str "set" name) [type] 'void] [(str "get" name) [] type]]) fields)) (defn def-access-methods [fields] (mapcat (fn [field] [`(defgetter ~field) `(defsetter ~field)]) fields)) (defmacro defsetter [field] `(defn ~(symbol (str "-set" field)) [this# value#] (set-field this# ~(keyword field) value#))) (defmacro defgetter [field] `(defn ~(symbol (str "-get" field)) [this#] (get-field this# ~(keyword field)))) (defmacro defbean [bean fields] `(do (gen-class :main false :state ~'state :init ~'init :name ~bean :methods ~(gen-method-defs fields)) ~@ (def-access-methods (keys fields)) )) (defbean com.test.Foo {Bar Integer Baz String What int}) 

Using it from the java side:

  Foo f = new Foo(); f.setBaz("hithere"); f.setBar(12); System.out.println("f = " + f); Foo f2 = new Foo(); System.out.println("f2.equals(f) = " + f2.equals(f)); f2.setBaz("hithere"); f2.setBar(12); System.out.println("f2.equals(f) = " + f2.equals(f)); System.out.println("(f2.hashCode() == f.hashCode()) = " + (f2.hashCode() == f.hashCode())); 

It produces:

 f = {:Baz "hithere", :Bar 12} f2.equals(f) = false f2.equals(f) = true (f2.hashCode() == f.hashCode()) = true 

Note that you will need to compile the geanbean namespace. An implementation uses an atom to store all properties, so make sure you understand the tradeoffs.

Also, when working in Clojure, you probably do not want to work with javabeans, but you can create several methods to get and set the state-holding atom.

+2
source

I assume this should be possible, but I'm not sure that you fully understand the keys in Clojure (maybe I'm just reading your sample code incorrectly).

Keys of type :name are of type clojure.lang.Keyword , not String or Integer , etc. (Nor do you usually declare types in Clojure). They are often used on maps (which use the {} syntax to get values, for example, the following code retrieves the value associated with :key2 from the map {:key1 "hello", :key2 4} .

 (get {:key1 "hello", :key2 4} :key2) 4 

I'm not sure your example is trying to say that you have :key1 associated with a String value and :key2 associated with an Integer value, or if you think that :key1 is of type String . If the first one probably wants to use a map instead of a vector.

I am afraid, I do not think that I know enough about Java beans or in your use case, in particular, to help much further.

0
source

All Articles