forked from clojure/java.data
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdata.clj
More file actions
211 lines (171 loc) · 7.86 KB
/
data.clj
File metadata and controls
211 lines (171 loc) · 7.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
;; Copyright (c) Cosmin Stejerean. All rights reserved. The use and
;; distribution terms for this software are covered by the Eclipse Public
;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
;; be found in the file epl-v10.html at the root of this distribution. By
;; using this software in any fashion, you are agreeing to be bound by the
;; terms of this license. You must not remove this notice, or any other,
;; from this software.
(ns
^{:author "Cosmin Stejerean",
:doc "Support for recursively converting Java beans to Clojure and vice versa."}
clojure.java.data
(:use [clojure.tools.logging :only (info)]))
(def
^{:dynamic true,
:doc "Specify the behavior of missing setters in to-java in the
default object case, using one of :ignore, :log, :error"}
*to-java-object-missing-setter* :ignore)
(defmulti to-java (fn [destination-type value] [destination-type (class value)]))
(defmulti from-java class)
(defn- get-property-descriptors [clazz]
(.getPropertyDescriptors (java.beans.Introspector/getBeanInfo clazz)))
;; getters
(defn- is-getter [^java.lang.reflect.Method method]
(and method
(= 0 (alength ^"[Ljava.lang.Class;" (.getParameterTypes method)))))
(defn- make-getter-fn [^java.lang.reflect.Method method]
(fn [instance]
(from-java (.invoke method instance nil))))
(defn- add-getter-fn [the-map ^java.beans.PropertyDescriptor prop-descriptor]
(let [name (.getName prop-descriptor)
method (.getReadMethod prop-descriptor)]
(if (and (is-getter method) (not (= "class" name)))
(assoc the-map (keyword name) (make-getter-fn method))
the-map)))
;; setters
(defn- is-setter [^java.lang.reflect.Method method]
(and method (= 1 (alength (. method (getParameterTypes))))))
(defn- get-setter-type [^java.lang.reflect.Method method]
(get (.getParameterTypes method) 0))
(defn- make-setter-fn [^java.lang.reflect.Method method]
(fn [instance value]
(.invoke method instance (into-array [(to-java (get-setter-type method) value)]))))
(defn- add-setter-fn [the-map ^java.beans.PropertyDescriptor prop-descriptor]
(let [name (.getName prop-descriptor)
method (.getWriteMethod prop-descriptor)]
(if (is-setter method)
(assoc the-map (keyword name) (make-setter-fn method))
the-map)))
(defn- add-array-methods [^Class acls]
(let [cls (.getComponentType acls)
to (fn [_ sequence] (into-array cls (map (partial to-java cls)
sequence)))
from (fn [obj] (map from-java obj))]
(.addMethod to-java [acls Iterable] to)
(.addMethod from-java acls from)
{:to to :from from}))
;; common to-java definitions
(defmethod to-java :default [^Class cls value]
(if (.isArray cls)
; no method for this array type yet
((:to (add-array-methods cls))
cls value)
value))
(defmethod to-java [Enum String] [^Class enum value]
(.invoke (.getDeclaredMethod enum "valueOf" (into-array [String]))
nil (into-array [value])))
(defn- throw-log-or-ignore-missing-setter [key ^Class clazz]
(let [message (str "Missing setter for " key " in " (.getCanonicalName clazz))]
(cond (= *to-java-object-missing-setter* :error)
(throw (new NoSuchFieldException message))
(= *to-java-object-missing-setter* :log)
(info message))))
(defmethod to-java [Object clojure.lang.APersistentMap] [clazz props]
"Convert a Clojure map to the specified class using reflection to set the properties"
(let [instance (.newInstance clazz)
setter-map (reduce add-setter-fn {} (get-property-descriptors clazz))]
(doseq [[key value] props]
(let [setter (get setter-map (keyword key))]
(if (nil? setter)
(throw-log-or-ignore-missing-setter key clazz)
(apply setter [instance value]))))
instance))
;; feature testing macro, based on suggestion from Chas Emerick:
(defmacro ^{:private true} when-available
[sym & body]
(try
(when (resolve sym)
(list* 'do body))
(catch ClassNotFoundException _#)))
(defmacro ^{:private true} when-not-available
[sym & body]
(try
(when-not (resolve sym)
(list* 'do body))
(catch ClassNotFoundException _#)))
(when-available
biginteger
(defmethod to-java [BigInteger Object] [_ value] (biginteger value)))
(when-not-available
biginteger
(defmethod to-java [BigInteger Object] [_ value] (bigint value)))
;; common from-java definitions
(defmethod from-java :default [^Object instance]
"Convert a Java object to a Clojure map"
(let [clazz (.getClass instance)]
(if (.isArray clazz)
((:from (add-array-methods clazz))
instance)
(let [getter-map (reduce add-getter-fn {} (get-property-descriptors clazz))]
(into {} (for [[key getter-fn] (seq getter-map)] [key (getter-fn instance)]))))))
(doseq [clazz [String Character Byte Short Integer Long Float Double Boolean BigInteger BigDecimal]]
(derive clazz ::do-not-convert))
(defmacro ^{:private true} defnumber [box prim prim-getter]
`(let [conv# (fn [_# number#]
(~(symbol (str box) "valueOf")
(. number# ~prim-getter)))]
(.addMethod to-java [~prim Number] conv#)
(.addMethod to-java [~box Number] conv#)))
(defmacro ^{:private true} defnumbers [& boxes]
(cons `do
(for [box boxes
:let [^Class box-cls (resolve box)
^Class prim-cls (.get (.getField box-cls "TYPE")
box-cls)
;; Clojure 1.3: (assert (class? box-cls) (str box ": no class found"))
_ (assert (class? box-cls))
;; Clojure 1.3: (assert (class? prim-cls) (str box " has no TYPE field"))
_ (assert (class? prim-cls))
prim-getter (symbol (str (.getName prim-cls) "Value"))]]
`(defnumber ~box ~(symbol (str box) "TYPE") ~prim-getter))))
(defnumbers Byte Short Integer Long Float Double)
(defmethod from-java ::do-not-convert [value] value)
(prefer-method from-java ::do-not-convert Object)
(defmethod from-java Iterable [instance] (for [each (seq instance)] (from-java each)))
(prefer-method from-java Iterable Object)
(defmethod from-java java.util.Map [instance] (into {} instance))
(prefer-method from-java java.util.Map Iterable)
(defmethod from-java nil [_] nil)
(defmethod from-java Enum [enum] (str enum))
;; definitions for interfacting with XMLGregorianCalendar
(defmethod to-java [javax.xml.datatype.XMLGregorianCalendar clojure.lang.APersistentMap] [clazz props]
"Create an XMLGregorianCalendar object given the following keys :year :month :day :hour :minute :second :timezone"
(let [^javax.xml.datatype.XMLGregorianCalendar instance (.newInstance clazz)
undefined javax.xml.datatype.DatatypeConstants/FIELD_UNDEFINED
getu #(get %1 %2 undefined)]
(doto instance
(.setYear (getu props :year))
(.setMonth (getu props :month))
(.setDay (getu props :day))
(.setHour (getu props :hour))
(.setMinute (getu props :minute))
(.setSecond (getu props :second))
(.setTimezone (getu props :timezone)))))
(defmethod from-java javax.xml.datatype.XMLGregorianCalendar
[^javax.xml.datatype.XMLGregorianCalendar obj]
"Turn an XMLGregorianCalendar object into a clojure map of year, month, day, hour, minute, second and timezone "
(let [date {:year (.getYear obj)
:month (.getMonth obj)
:day (.getDay obj)}
time {:hour (.getHour obj)
:minute (.getMinute obj)
:second (.getSecond obj)}
tz {:timezone (.getTimezone obj)}
is-undefined? #(= javax.xml.datatype.DatatypeConstants/FIELD_UNDEFINED %1)]
(conj {}
(if-not (is-undefined? (:year date))
date)
(if-not (is-undefined? (:hour time))
time)
(if-not (is-undefined? (:timezone tz))
tz))))