-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathjava-proxy.clj
More file actions
336 lines (269 loc) · 11.6 KB
/
java-proxy.clj
File metadata and controls
336 lines (269 loc) · 11.6 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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
;; java repl using lein repl
;;
; lein repl
; => (load-file "java.proxy.clj")
; require other clj namespace x_core.clj
(require '[clojure.string :as string])
; can not import entire package, has to import one class by one class
(import (java.util.regex Pattern Matcher))
(import '[java.util HashMap HashSet])
(require '[clojure.core.async :as async
:refer [>! <! >!! <!! go go-loop chan buffer close! thread
alts! alts!! timeout]])
; (-> (class.) (.memfn args))
(.. System (getProperties) (get "os.name"))
(-> (System/getProperties) (.get "os.name") (prn))
(doto (java.util.HashMap.) (.put "a" 1) (.put "b" 2))
; (.memfn obj args)
(let [p (Pattern/compile "hello (\\S+)")
m (.matcher p "hello world")]
(if (.find m)
(prn (string/join ["match group -> " (.group m 1)]))))
; ; simple string match
(prn (.matches "hello world" "hello \\S+"))
(java.net.URI. "http://clojure.org") ; ⇒ #<URI http://clojure.org>
(class (java.net.URI. "http://github.com")) ; ⇒ java.net.URI
(Class/forName "java.util.Date") ; ⇒ java.util.Date
; to use core.async, do `lein new app test-async` and dep on [[org.clojure/core.async "0.2.395"]].
; then rquire clojure.core.async with :refer []; lein repl (load-file "../x.clj"). N
(def echo-chan (chan))
(go (println (<! echo-chan)))
(>!! echo-chan "ketchup to echo-chan")
(def hi-chan (chan 1000))
; doseq with go block, order is not guaranteed
(doseq [n (range 10)]
(go (>! hi-chan (str "hi " n))))
(go-loop []
(let [msg (<! hi-chan)]
; (<! (timeout 1000))
(println msg)
(recur)))
; go-loop, order is guaranteed
(go-loop [n 10]
(when (> n 0)
(>! hi-chan (str "hi " n))
(recur (dec n))))
(prn (.buf (.buf hi-chan))) ;; Get elements in buffer
(go-loop []
(let [msg (<! hi-chan)]
(<! (timeout 100))
(println msg)
(recur)))
;; future rets callable object that can be de-refed.
(def task (future
(println "[Future] started computation")
(Thread/sleep 1000) ;; running for 3 seconds
(println "[Future] completed computation")
42))
(prn @task)
;; java proxy
;; (load-file "java-proxy.clj")
;; http://kotka.de/blog/2010/03/proxy_gen-class_little_brother.html
;; first, import the java package.
(ns java-proxy
(:import [java.text SimpleDateFormat]
[java.util Calendar TimeZone]
[java.io OutputStream FileOutputStream]))
; Where Proxy shines ? whenever you have to extend concrete class in the framework.
; proxy extends concrete class dynamically while reify abstracts type, protocol and interface.
; To extend a class, create a proxy stub with a spec map of the class, and wrap it inside proxy macro.
; The object instantiated by proxy is an instance of extended class and delegate fn based on spec map.
; This means proxy-methods are lambda function closures closed over their context.
; If a method is not found in the proxy's map the stub throws an UnsupportedOperationException or calls the super's method.
; (proxy [baseclass-name] [baseclass-constructor-parameters]
; (method-redefinition-name [parameters]
; method-body))
(def d (proxy [Date] []
(toString [] "Proxy-date-to-string-changed-to-hello")))
(.toString d) ; <= "Proxy-date-to-string-changed-to-hello"
; clojure proxy extends a class with a spec map. Proxy macro gen bytecode impl IF to extend base class on demand on the fly.
; Based on the method name, the corresponding function is retrieved from a map and invoked with the this reference and the argument(s)
; proxy just intercepts method calls and wraps it with extra info or dispatch it to delegates.
; you can use (update-prox proxy mappings) to dynamically assoc spec maps to proxy.
; so dynamically create closure to extend any class at runtime.
(update-proxy d {"toString" (fn [this] "update-proxy-date-to-string")})
(.toString d) ; <= "update-proxy-date-to-string"
(defn streaming-filter [os]
(proxy [FilterOutputStream] [os] ; args or base class ctor args
(write [b]
(proxy-super write (.getBytes (str "<strong>" (.toUpperCase (String .b)) "</strong>"))))))
(def f (streaming-filter (FileOutputStream. "./java-proxy.clj")))
(.write f "xxxx")
; proxy java object's toString
(defn toStringProxy
[msg]
(proxy [Object] [] ; proxy Object class, ctor takes no args
(toString [] (conj "tag-" msg (proxy-super toString)))))
(.toString (toStringProxy "hello world !"))
; Proxy a java interface
(defn IDerefProxy
[msg]
(let [state (atom msg)]
(proxy [clojure.lang.IDeref] ; proxy a IF, not ctor args
(toString [] @state)
(deref [] state))))
(def o (IDerefProxy "hello world"))
(.toString o)
(reset! @o "world hello")
(.toString o)
; Proxy with multiple Arities in base class
(proxy [Example] []
(someMethod [x]
(condp instance? x
String (.doSomethingWithString this x)
Integer (.doSomethingWithInteger this x)))
(diffArgs
([x] (proxy-super diffArgs x)
([x y] (.doMoreStuff this, x, y)))))
; the magic this for dispatch. Do not redef this inside proxy-method
; While the methods for gen-class take the object as first argument (and can thus it can be named whatever you like)
; proxy captures the symbol this in a similar way Java does. So in a proxy method this will always refer to the instance at hand.
(proxy [Object] []
(toString []
(let [this "huh?"] ;; do NOT re-def this inside proxy-method
(proxy-super toString))))
(def myr
(proxy [Runnable][]
(run []
(prn "running proxy"))))
(def myz
(reify Runnable
(run [this]
(prn "running reify"))))
(.start (Thread. myr))
(.start (Thread. myz))
(import 'java.util.concurrent.Executors)
(.submit (Executors/newSingleThreadExecutor) myr)
(import [java.util.concurrent Executors])
(.submit (Executors/newSingleThreadExecutor) myr)
; ns defines a namespace, with gen-class, also defines a class, that simply contains stubs to
; map java class methods calls to ns functions.
; gen-class, need AOT compiler in project.clj to gen java class before hand. AOT tie gened files to clj version.
; (compile 'joy.gui.DynaFrame')
; gen-class creates a class that's a delegate for the vars (prefixed fn), contains state.
; clj gen a set of classes for each fn in a namespace at location classpath.
; java class has many fields, here we only have single class field, called state. use (atom {}) as field map.
; use require to resolve class dependency.
(ns some.namespace
(:gen-class ; convert a clojure ns to a java class, with private state, init, getter/setter
:name com.colorcloud.MyClass ; this ns impls this class.
:extends javax.swing.JFrame ;
:implements [clojure.lang.IMeta]
:prefix df- ; method prefix with df- is class method, first arg is this pointer.
; (defn df-foo ...), called by (.foo class-instance-object)
:state state ; define a method that return the object's state. called as (swap ! (.state this) conj (.getValue ))
:init init ; called when obj initiation. ret a [], first arg is a vec of args for super class.
; second is object's state. init ret the state of object and called when obj instantiate.
:constructors {[String] [String]} ; map the args of Klz constructor to the args to super klz constructor.
; to determine which constructor to call.
:methods [ [display [java.awt.Container] void] ; public method
#^{:static true} [version [] String]]) ; class static method
(:import (javax.swing JFrame JPanel)
(java.awt BorderLayout Container)))
(in-ns 'joy.gui.DynaFrame') ; class name specified by gen-class :name prop
; init called when obj instantiate, :constructors decide when super klz constructor to call, and init object state.
; the first element is an arg vector to super klz to determine which super constructor to call.
; The second element of the vector is the state for the instance. use (atom {}) as concurmap storing mutable state.
(defn df-init [title]
[[title] (atom {:title title})])
; to access object state, use (.state this)
(swap! @(.state this) merge {:test "test"})
; another example
(ns #^{:doc "A simple class with instance vars"
:author "David G. Durand"}
com.tizra.example)
(gen-class
:name com.tizra.example.Demo
:state state
:init init
:prefix "-"
:main false
:methods [[setLocation [String] void]
[getLocation [] String]]
)
(defn -init []
"store our fields as a hash"
[[] (atom {:location "default"})])
(defmacro setfield
[this key value]
`(swap! (.state ~this) into {~key ~value}))
(defmacro getfield
[this key]
`(@(.state ~this) ~key))
(defn -setLocation [this ^java.lang.String loc]
(setfield this :location loc))
(defn ^String -getLocation
[this]
(getfield this :location))
=> (com.tizra.example.Demo.)
#<Demo com.tizra.example.Demo@673a95af>
=> (def ex (com.tizra.example.Demo.))
#'user/ex
=> (.getLocation ex)
"default"
=> (.setLocation ex "time")
nil
=> (.getLocation ex)
"time"
;;
;; the difference between gen-class and proxy
;; gen-class creates a named class while proxy extends an existing concrete java class.
;; gen-class taks an object as first arg, proxy capture this symbol the java way. Do not rebind this symbol.
;;
;;
;; an http server example with http proxy handler.
;;
(ns joy.web
(:import (com.sun.net.httpserver HttpHandler HttpExchange HttpServer)
(java.net InetSocketAddress HttpURLConnection)
(java.io IOException FilterOutputStream)
(java.util Arrays)))
(defn new-server [port path handler]
(doto (HttpServer/create (InetSocketAddress. port) 0)
(.createContext path handler)
(.setExecutor nil)
(.start)))
; proxy create an obj that impls a IF or extends a concrete class.
; involke the method on the object reted by (proxy ) macro.
(defn default-handler [txt] ; ret a proxy object
(proxy [HttpHandler] [] ; proxy gen bytecode for actual class on demand.
(handle [exchange] ; proxy methods can be changed by update-proxy at run-time
(.sendResponseHeaders exchange HttpURLConnection/HTTP_OK 0)
(doto (.getResponseBody exchange)
(.write (.getBytes txt))
(.close))))) ;; Close over txt
(def server
(new-server 3001 "/joy/hello" (default-handler "Hello Cleveland"))) ; default-handler ret a proxy obj
;; bind the ret of default-handle to a var, and pass to server
(.stop server 0)
(def p (default-handler ; default-handler ret a proxy object
"There's no problem that can't be solved with another level of indirection"))
(def server (new-server 8123 "/joy/hello" p)) ; now p is a proxy object
; ret a closure
(defn make-handler-fn [fltr txt]
(fn [this exchange] ;; this captures the caller, which is proxy of http handler.
(let [b (.getBytes txt)]
(-> exchange
.getResponseHeaders
(.set "Content-Type" "text/html"))
(.sendResponseHeaders exchange
HttpURLConnection/HTTP_OK
0)
(doto (fltr (.getResponseBody exchange))
(.write b)
(.close)))))
; bind the ret fn of make-handler-fn
; update-proxy updates the mapped functions within a proxy at runtime.
(defn change-message
"Convenience method to change a proxy's output message"
([p txt] (change-message p identity txt))
([p fltr txt]
; update-proxy proxy mappings : change the fn name string mapping to fn object
(update-proxy p {"handle" (make-handler-fn fltr txt)})))
; create a closure that
(change-message p "Hello Dynamic!")
;; java object member function is not high order fn. use macro memfn to convert.
(map #(.getBytes %) ["amit" "rob" "kyle"])
(map (memfn getBytes) ["amit" "rob" "kyle"])
(.subSequence "Clojure" 2 5)
((memfn subSequence start end) "Clojure" 2 5)