2012-03-17 17 views
7

C'è un modo per generare facilmente java bean dato un vettore in clojure? Ad esempio dato un vettore come questo:genera java bean con clojure

[ 
    String :key1 
    Integer :key2 
] 

mi piacerebbe che per generare il codice come questo:

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. 
} 

per il contesto, mi piacerebbe scrivere un app che è scritto in Java, ma chiama in una biblioteca scritta in clojure. Ciò significa che i valori di ritorno dovrebbero essere java bean (so che non devono essere, ma mi piacerebbe che fossero). Un modo sarebbe quello di definire il modello in java e quindi usare il normale jop di clojure per popolare il modello nel codice del clojure, ma mi piace l'idea di un vettore (o mappa) conciso di clojure che si espanda verso un java bean (dettagliato).

risposta

3

Non penso che il codice Java funzioni bene con classi compatibili con Java bean generate automaticamente. È necessario avere almeno un'interfaccia sul lato Java per dare un senso a ciò che Clojure sta per tornare. Senza questo, si dovrà tornare a:

Object result = callClojureLib(params); 

Poi, non importa se il risultato effettivo implementa il contratto bean Java, il codice Java dovrà fare ogni sorta di riflessione magia di essere in grado di chiamare anche un setter, come ti manca la specifica della classe.

Un altro approccio al problema sarebbe utilizzare l'interfaccia java.util.Map come contratto tra i mondi Java e Clojure.In questo modo, si potrebbe utilizzare mappe semplici Clojure come oggetti di trasferimento, in quanto sono assegnabili a java.util.Map:

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

+1 per l'utilizzo di java.util.Map come classe di interfaccia. Suggerisco inoltre di usare String come chiavi: funzionano altrettanto bene come parole chiave sul lato Clojure ma sono molto più facili da usare per gli utenti dell'API Java .... – mikera

+0

questa è probabilmente la risposta giusta, ma manca il punto che io in particolare non voglio una mappa sul lato java. Ho un'enorme quantità di codice java che è service, dao e un sacco di oggetti modello. Speravo di poter riscrivere in clojure in modo tale che gli oggetti del modello potessero essere generati dinamicamente (riducendo così la dimensione del codice in modo massiccio). E dal punto di vista del cliente, il codice non è diverso dal codice java originale. – Kevin

+0

Ti suggerisco di iniziare da un campione del codice Java client come vorresti che fosse, e quindi provare a trovare un'interfaccia comune che possa descrivere tutti i casi che ti servono e che puoi 'reificare 'da Clojure. Il problema è in realtà con il tentativo di generare Java bean al volo senza un contratto comune con il lato Java. – skuro

0

Suppongo che dovrebbe essere possibile, ma non sono sicuro di aver compreso completamente le chiavi in ​​Clojure (potrebbe essere solo che sto leggendo male il tuo codice di esempio).

Tasti come :name sono di tipo clojure.lang.Keyword, non String o Integer ecc (anche non normalmente dichiara tipi in Clojure). Vengono spesso utilizzati nelle mappe (che utilizzano la sintassi {}) per recuperare i valori, ad esempio il seguente codice recupera il valore associato a :key2 dalla mappa {:key1 "hello", :key2 4}.

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

io non sono sicuro se il vostro esempio sta cercando di dire che hai :key1 associato con un valore String e :key2 associata con un valoreIntegero se si pensa :key1 è di tipo String. Se il primo, probabilmente vuole usare una mappa invece di un vettore.

Temo di non pensare di conoscere abbastanza i bean Java o il vostro caso d'uso, in particolare, per aiutare ulteriormente.

2

lungi dall'essere perfetto e probabilmente ha un sacco di problemi imprevisti quando si tenta di utilizzarlo, ma penso si può iniziare con qualcosa di simile:

(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)) 
     [email protected](def-access-methods (keys fields)) 
     )) 

    (defbean com.test.Foo {Bar Integer Baz String What int}) 

Utilizzando dal lato Java:

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

produce:

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

Nota che è necessario compilare lo spazio dei nomi geanbean. L'implementazione utilizza un atomo per memorizzare tutte le proprietà, quindi assicurati di comprendere i compromessi.

Inoltre, quando si lavora in Clojure, probabilmente non si vuole lavorare con i javabeans ma è possibile creare un paio di metodi per ottenere e impostare l'atomo che tiene lo stato.