Mercurial > lasercutter
diff src/clojure/core_proxy.clj @ 10:ef7dbbd6452c
added clojure source goodness
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sat, 21 Aug 2010 06:25:44 -0400 |
parents | |
children |
line wrap: on
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/clojure/core_proxy.clj Sat Aug 21 06:25:44 2010 -0400 1.3 @@ -0,0 +1,407 @@ 1.4 +; Copyright (c) Rich Hickey. All rights reserved. 1.5 +; The use and distribution terms for this software are covered by the 1.6 +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 1.7 +; which can be found in the file epl-v10.html at the root of this distribution. 1.8 +; By using this software in any fashion, you are agreeing to be bound by 1.9 +; the terms of this license. 1.10 +; You must not remove this notice, or any other, from this software. 1.11 + 1.12 +(in-ns 'clojure.core) 1.13 + 1.14 +;;;;;;;;;;;;;;;;;;;;;;;;;;;; proxy ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.15 + 1.16 +(import 1.17 + '(clojure.asm ClassWriter ClassVisitor Opcodes Type) 1.18 + '(java.lang.reflect Modifier Constructor) 1.19 + '(clojure.asm.commons Method GeneratorAdapter) 1.20 + '(clojure.lang IProxy Reflector DynamicClassLoader IPersistentMap PersistentHashMap RT)) 1.21 + 1.22 +(defn method-sig [^java.lang.reflect.Method meth] 1.23 + [(. meth (getName)) (seq (. meth (getParameterTypes))) (. meth getReturnType)]) 1.24 + 1.25 +(defn- most-specific [rtypes] 1.26 + (or (some (fn [t] (when (every? #(isa? t %) rtypes) t)) rtypes) 1.27 + (throw (Exception. "Incompatible return types")))) 1.28 + 1.29 +(defn- group-by-sig [coll] 1.30 + "takes a collection of [msig meth] and returns a seq of maps from return-types to meths." 1.31 + (vals (reduce (fn [m [msig meth]] 1.32 + (let [rtype (peek msig) 1.33 + argsig (pop msig)] 1.34 + (assoc m argsig (assoc (m argsig {}) rtype meth)))) 1.35 + {} coll))) 1.36 + 1.37 +(defn proxy-name 1.38 + {:tag String} 1.39 + [^Class super interfaces] 1.40 + (let [inames (into (sorted-set) (map #(.getName ^Class %) interfaces))] 1.41 + (apply str (.replace (str *ns*) \- \_) ".proxy" 1.42 + (interleave (repeat "$") 1.43 + (concat 1.44 + [(.getName super)] 1.45 + (map #(subs % (inc (.lastIndexOf ^String % "."))) inames) 1.46 + [(Integer/toHexString (hash inames))]))))) 1.47 + 1.48 +(defn- generate-proxy [^Class super interfaces] 1.49 + (let [cv (new ClassWriter (. ClassWriter COMPUTE_MAXS)) 1.50 + cname (.replace (proxy-name super interfaces) \. \/) ;(str "clojure/lang/" (gensym "Proxy__")) 1.51 + ctype (. Type (getObjectType cname)) 1.52 + iname (fn [^Class c] (.. Type (getType c) (getInternalName))) 1.53 + fmap "__clojureFnMap" 1.54 + totype (fn [^Class c] (. Type (getType c))) 1.55 + to-types (fn [cs] (if (pos? (count cs)) 1.56 + (into-array (map totype cs)) 1.57 + (make-array Type 0))) 1.58 + super-type ^Type (totype super) 1.59 + imap-type ^Type (totype IPersistentMap) 1.60 + ifn-type (totype clojure.lang.IFn) 1.61 + obj-type (totype Object) 1.62 + sym-type (totype clojure.lang.Symbol) 1.63 + rt-type (totype clojure.lang.RT) 1.64 + ex-type (totype java.lang.UnsupportedOperationException) 1.65 + gen-bridge 1.66 + (fn [^java.lang.reflect.Method meth ^java.lang.reflect.Method dest] 1.67 + (let [pclasses (. meth (getParameterTypes)) 1.68 + ptypes (to-types pclasses) 1.69 + rtype ^Type (totype (. meth (getReturnType))) 1.70 + m (new Method (. meth (getName)) rtype ptypes) 1.71 + dtype (totype (.getDeclaringClass dest)) 1.72 + dm (new Method (. dest (getName)) (totype (. dest (getReturnType))) (to-types (. dest (getParameterTypes)))) 1.73 + gen (new GeneratorAdapter (bit-or (. Opcodes ACC_PUBLIC) (. Opcodes ACC_BRIDGE)) m nil nil cv)] 1.74 + (. gen (visitCode)) 1.75 + (. gen (loadThis)) 1.76 + (dotimes [i (count ptypes)] 1.77 + (. gen (loadArg i))) 1.78 + (if (-> dest .getDeclaringClass .isInterface) 1.79 + (. gen (invokeInterface dtype dm)) 1.80 + (. gen (invokeVirtual dtype dm))) 1.81 + (. gen (returnValue)) 1.82 + (. gen (endMethod)))) 1.83 + gen-method 1.84 + (fn [^java.lang.reflect.Method meth else-gen] 1.85 + (let [pclasses (. meth (getParameterTypes)) 1.86 + ptypes (to-types pclasses) 1.87 + rtype ^Type (totype (. meth (getReturnType))) 1.88 + m (new Method (. meth (getName)) rtype ptypes) 1.89 + gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv) 1.90 + else-label (. gen (newLabel)) 1.91 + end-label (. gen (newLabel)) 1.92 + decl-type (. Type (getType (. meth (getDeclaringClass))))] 1.93 + (. gen (visitCode)) 1.94 + (if (> (count pclasses) 18) 1.95 + (else-gen gen m) 1.96 + (do 1.97 + (. gen (loadThis)) 1.98 + (. gen (getField ctype fmap imap-type)) 1.99 + 1.100 + (. gen (push (. meth (getName)))) 1.101 + ;lookup fn in map 1.102 + (. gen (invokeStatic rt-type (. Method (getMethod "Object get(Object, Object)")))) 1.103 + (. gen (dup)) 1.104 + (. gen (ifNull else-label)) 1.105 + ;if found 1.106 + (.checkCast gen ifn-type) 1.107 + (. gen (loadThis)) 1.108 + ;box args 1.109 + (dotimes [i (count ptypes)] 1.110 + (. gen (loadArg i)) 1.111 + (. clojure.lang.Compiler$HostExpr (emitBoxReturn nil gen (nth pclasses i)))) 1.112 + ;call fn 1.113 + (. gen (invokeInterface ifn-type (new Method "invoke" obj-type 1.114 + (into-array (cons obj-type 1.115 + (replicate (count ptypes) obj-type)))))) 1.116 + ;unbox return 1.117 + (. gen (unbox rtype)) 1.118 + (when (= (. rtype (getSort)) (. Type VOID)) 1.119 + (. gen (pop))) 1.120 + (. gen (goTo end-label)) 1.121 + 1.122 + ;else call supplied alternative generator 1.123 + (. gen (mark else-label)) 1.124 + (. gen (pop)) 1.125 + 1.126 + (else-gen gen m) 1.127 + 1.128 + (. gen (mark end-label)))) 1.129 + (. gen (returnValue)) 1.130 + (. gen (endMethod))))] 1.131 + 1.132 + ;start class definition 1.133 + (. cv (visit (. Opcodes V1_5) (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_SUPER)) 1.134 + cname nil (iname super) 1.135 + (into-array (map iname (cons IProxy interfaces))))) 1.136 + ;add field for fn mappings 1.137 + (. cv (visitField (+ (. Opcodes ACC_PRIVATE) (. Opcodes ACC_VOLATILE)) 1.138 + fmap (. imap-type (getDescriptor)) nil nil)) 1.139 + ;add ctors matching/calling super's 1.140 + (doseq [^Constructor ctor (. super (getDeclaredConstructors))] 1.141 + (when-not (. Modifier (isPrivate (. ctor (getModifiers)))) 1.142 + (let [ptypes (to-types (. ctor (getParameterTypes))) 1.143 + m (new Method "<init>" (. Type VOID_TYPE) ptypes) 1.144 + gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)] 1.145 + (. gen (visitCode)) 1.146 + ;call super ctor 1.147 + (. gen (loadThis)) 1.148 + (. gen (dup)) 1.149 + (. gen (loadArgs)) 1.150 + (. gen (invokeConstructor super-type m)) 1.151 + 1.152 + (. gen (returnValue)) 1.153 + (. gen (endMethod))))) 1.154 + ;add IProxy methods 1.155 + (let [m (. Method (getMethod "void __initClojureFnMappings(clojure.lang.IPersistentMap)")) 1.156 + gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)] 1.157 + (. gen (visitCode)) 1.158 + (. gen (loadThis)) 1.159 + (. gen (loadArgs)) 1.160 + (. gen (putField ctype fmap imap-type)) 1.161 + 1.162 + (. gen (returnValue)) 1.163 + (. gen (endMethod))) 1.164 + (let [m (. Method (getMethod "void __updateClojureFnMappings(clojure.lang.IPersistentMap)")) 1.165 + gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)] 1.166 + (. gen (visitCode)) 1.167 + (. gen (loadThis)) 1.168 + (. gen (dup)) 1.169 + (. gen (getField ctype fmap imap-type)) 1.170 + (.checkCast gen (totype clojure.lang.IPersistentCollection)) 1.171 + (. gen (loadArgs)) 1.172 + (. gen (invokeInterface (totype clojure.lang.IPersistentCollection) 1.173 + (. Method (getMethod "clojure.lang.IPersistentCollection cons(Object)")))) 1.174 + (. gen (checkCast imap-type)) 1.175 + (. gen (putField ctype fmap imap-type)) 1.176 + 1.177 + (. gen (returnValue)) 1.178 + (. gen (endMethod))) 1.179 + (let [m (. Method (getMethod "clojure.lang.IPersistentMap __getClojureFnMappings()")) 1.180 + gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)] 1.181 + (. gen (visitCode)) 1.182 + (. gen (loadThis)) 1.183 + (. gen (getField ctype fmap imap-type)) 1.184 + (. gen (returnValue)) 1.185 + (. gen (endMethod))) 1.186 + 1.187 + ;calc set of supers' non-private instance methods 1.188 + (let [[mm considered] 1.189 + (loop [mm {} considered #{} c super] 1.190 + (if c 1.191 + (let [[mm considered] 1.192 + (loop [mm mm 1.193 + considered considered 1.194 + meths (concat 1.195 + (seq (. c (getDeclaredMethods))) 1.196 + (seq (. c (getMethods))))] 1.197 + (if (seq meths) 1.198 + (let [^java.lang.reflect.Method meth (first meths) 1.199 + mods (. meth (getModifiers)) 1.200 + mk (method-sig meth)] 1.201 + (if (or (considered mk) 1.202 + (not (or (Modifier/isPublic mods) (Modifier/isProtected mods))) 1.203 + ;(. Modifier (isPrivate mods)) 1.204 + (. Modifier (isStatic mods)) 1.205 + (. Modifier (isFinal mods)) 1.206 + (= "finalize" (.getName meth))) 1.207 + (recur mm (conj considered mk) (next meths)) 1.208 + (recur (assoc mm mk meth) (conj considered mk) (next meths)))) 1.209 + [mm considered]))] 1.210 + (recur mm considered (. c (getSuperclass)))) 1.211 + [mm considered])) 1.212 + ifaces-meths (into {} 1.213 + (for [^Class iface interfaces meth (. iface (getMethods)) 1.214 + :let [msig (method-sig meth)] :when (not (considered msig))] 1.215 + {msig meth})) 1.216 + mgroups (group-by-sig (concat mm ifaces-meths)) 1.217 + rtypes (map #(most-specific (keys %)) mgroups) 1.218 + mb (map #(vector (%1 %2) (vals (dissoc %1 %2))) mgroups rtypes) 1.219 + bridge? (reduce into #{} (map second mb)) 1.220 + ifaces-meths (remove bridge? (vals ifaces-meths)) 1.221 + mm (remove bridge? (vals mm))] 1.222 + ;add methods matching supers', if no mapping -> call super 1.223 + (doseq [[^java.lang.reflect.Method dest bridges] mb 1.224 + ^java.lang.reflect.Method meth bridges] 1.225 + (gen-bridge meth dest)) 1.226 + (doseq [^java.lang.reflect.Method meth mm] 1.227 + (gen-method meth 1.228 + (fn [^GeneratorAdapter gen ^Method m] 1.229 + (. gen (loadThis)) 1.230 + ;push args 1.231 + (. gen (loadArgs)) 1.232 + ;call super 1.233 + (. gen (visitMethodInsn (. Opcodes INVOKESPECIAL) 1.234 + (. super-type (getInternalName)) 1.235 + (. m (getName)) 1.236 + (. m (getDescriptor))))))) 1.237 + 1.238 + ;add methods matching interfaces', if no mapping -> throw 1.239 + (doseq [^java.lang.reflect.Method meth ifaces-meths] 1.240 + (gen-method meth 1.241 + (fn [^GeneratorAdapter gen ^Method m] 1.242 + (. gen (throwException ex-type (. m (getName)))))))) 1.243 + 1.244 + ;finish class def 1.245 + (. cv (visitEnd)) 1.246 + [cname (. cv toByteArray)])) 1.247 + 1.248 +(defn- get-super-and-interfaces [bases] 1.249 + (if (. ^Class (first bases) (isInterface)) 1.250 + [Object bases] 1.251 + [(first bases) (next bases)])) 1.252 + 1.253 +(defn get-proxy-class 1.254 + "Takes an optional single class followed by zero or more 1.255 + interfaces. If not supplied class defaults to Object. Creates an 1.256 + returns an instance of a proxy class derived from the supplied 1.257 + classes. The resulting value is cached and used for any subsequent 1.258 + requests for the same class set. Returns a Class object." 1.259 + {:added "1.0"} 1.260 + [& bases] 1.261 + (let [[super interfaces] (get-super-and-interfaces bases) 1.262 + pname (proxy-name super interfaces)] 1.263 + (or (RT/loadClassForName pname) 1.264 + (let [[cname bytecode] (generate-proxy super interfaces)] 1.265 + (. ^DynamicClassLoader (deref clojure.lang.Compiler/LOADER) (defineClass pname bytecode [super interfaces])))))) 1.266 + 1.267 +(defn construct-proxy 1.268 + "Takes a proxy class and any arguments for its superclass ctor and 1.269 + creates and returns an instance of the proxy." 1.270 + {:added "1.0"} 1.271 + [c & ctor-args] 1.272 + (. Reflector (invokeConstructor c (to-array ctor-args)))) 1.273 + 1.274 +(defn init-proxy 1.275 + "Takes a proxy instance and a map of strings (which must 1.276 + correspond to methods of the proxy superclass/superinterfaces) to 1.277 + fns (which must take arguments matching the corresponding method, 1.278 + plus an additional (explicit) first arg corresponding to this, and 1.279 + sets the proxy's fn map." 1.280 + {:added "1.0"} 1.281 + [^IProxy proxy mappings] 1.282 + (. proxy (__initClojureFnMappings mappings))) 1.283 + 1.284 +(defn update-proxy 1.285 + "Takes a proxy instance and a map of strings (which must 1.286 + correspond to methods of the proxy superclass/superinterfaces) to 1.287 + fns (which must take arguments matching the corresponding method, 1.288 + plus an additional (explicit) first arg corresponding to this, and 1.289 + updates (via assoc) the proxy's fn map. nil can be passed instead of 1.290 + a fn, in which case the corresponding method will revert to the 1.291 + default behavior. Note that this function can be used to update the 1.292 + behavior of an existing instance without changing its identity." 1.293 + {:added "1.0"} 1.294 + [^IProxy proxy mappings] 1.295 + (. proxy (__updateClojureFnMappings mappings))) 1.296 + 1.297 +(defn proxy-mappings 1.298 + "Takes a proxy instance and returns the proxy's fn map." 1.299 + {:added "1.0"} 1.300 + [^IProxy proxy] 1.301 + (. proxy (__getClojureFnMappings))) 1.302 + 1.303 +(defmacro proxy 1.304 + "class-and-interfaces - a vector of class names 1.305 + 1.306 + args - a (possibly empty) vector of arguments to the superclass 1.307 + constructor. 1.308 + 1.309 + f => (name [params*] body) or 1.310 + (name ([params*] body) ([params+] body) ...) 1.311 + 1.312 + Expands to code which creates a instance of a proxy class that 1.313 + implements the named class/interface(s) by calling the supplied 1.314 + fns. A single class, if provided, must be first. If not provided it 1.315 + defaults to Object. 1.316 + 1.317 + The interfaces names must be valid interface types. If a method fn 1.318 + is not provided for a class method, the superclass methd will be 1.319 + called. If a method fn is not provided for an interface method, an 1.320 + UnsupportedOperationException will be thrown should it be 1.321 + called. Method fns are closures and can capture the environment in 1.322 + which proxy is called. Each method fn takes an additional implicit 1.323 + first arg, which is bound to 'this. Note that while method fns can 1.324 + be provided to override protected methods, they have no other access 1.325 + to protected members, nor to super, as these capabilities cannot be 1.326 + proxied." 1.327 + {:added "1.0"} 1.328 + [class-and-interfaces args & fs] 1.329 + (let [bases (map #(or (resolve %) (throw (Exception. (str "Can't resolve: " %)))) 1.330 + class-and-interfaces) 1.331 + [super interfaces] (get-super-and-interfaces bases) 1.332 + compile-effect (when *compile-files* 1.333 + (let [[cname bytecode] (generate-proxy super interfaces)] 1.334 + (clojure.lang.Compiler/writeClassFile cname bytecode))) 1.335 + pc-effect (apply get-proxy-class bases) 1.336 + pname (proxy-name super interfaces)] 1.337 + ;remember the class to prevent it from disappearing before use 1.338 + (intern *ns* (symbol pname) pc-effect) 1.339 + `(let [;pc# (get-proxy-class ~@class-and-interfaces) 1.340 + p# (new ~(symbol pname) ~@args)] ;(construct-proxy pc# ~@args)] 1.341 + (init-proxy p# 1.342 + ~(loop [fmap {} fs fs] 1.343 + (if fs 1.344 + (let [[sym & meths] (first fs) 1.345 + meths (if (vector? (first meths)) 1.346 + (list meths) 1.347 + meths) 1.348 + meths (map (fn [[params & body]] 1.349 + (cons (apply vector 'this params) body)) 1.350 + meths)] 1.351 + (if-not (contains? fmap (name sym)) 1.352 + (recur (assoc fmap (name sym) (cons `fn meths)) (next fs)) 1.353 + (throw (IllegalArgumentException. 1.354 + (str "Method '" (name sym) "' redefined"))))) 1.355 + fmap))) 1.356 + p#))) 1.357 + 1.358 +(defn proxy-call-with-super [call this meth] 1.359 + (let [m (proxy-mappings this)] 1.360 + (update-proxy this (assoc m meth nil)) 1.361 + (let [ret (call)] 1.362 + (update-proxy this m) 1.363 + ret))) 1.364 + 1.365 +(defmacro proxy-super 1.366 + "Use to call a superclass method in the body of a proxy method. 1.367 + Note, expansion captures 'this" 1.368 + {:added "1.0"} 1.369 + [meth & args] 1.370 + `(proxy-call-with-super (fn [] (. ~'this ~meth ~@args)) ~'this ~(name meth))) 1.371 + 1.372 +(defn bean 1.373 + "Takes a Java object and returns a read-only implementation of the 1.374 + map abstraction based upon its JavaBean properties." 1.375 + {:added "1.0"} 1.376 + [^Object x] 1.377 + (let [c (. x (getClass)) 1.378 + pmap (reduce (fn [m ^java.beans.PropertyDescriptor pd] 1.379 + (let [name (. pd (getName)) 1.380 + method (. pd (getReadMethod))] 1.381 + (if (and method (zero? (alength (. method (getParameterTypes))))) 1.382 + (assoc m (keyword name) (fn [] (clojure.lang.Reflector/prepRet (. method (invoke x nil))))) 1.383 + m))) 1.384 + {} 1.385 + (seq (.. java.beans.Introspector 1.386 + (getBeanInfo c) 1.387 + (getPropertyDescriptors)))) 1.388 + v (fn [k] ((pmap k))) 1.389 + snapshot (fn [] 1.390 + (reduce (fn [m e] 1.391 + (assoc m (key e) ((val e)))) 1.392 + {} (seq pmap)))] 1.393 + (proxy [clojure.lang.APersistentMap] 1.394 + [] 1.395 + (containsKey [k] (contains? pmap k)) 1.396 + (entryAt [k] (when (contains? pmap k) (new clojure.lang.MapEntry k (v k)))) 1.397 + (valAt ([k] (v k)) 1.398 + ([k default] (if (contains? pmap k) (v k) default))) 1.399 + (cons [m] (conj (snapshot) m)) 1.400 + (count [] (count pmap)) 1.401 + (assoc [k v] (assoc (snapshot) k v)) 1.402 + (without [k] (dissoc (snapshot) k)) 1.403 + (seq [] ((fn thisfn [plseq] 1.404 + (lazy-seq 1.405 + (when-let [pseq (seq plseq)] 1.406 + (cons (new clojure.lang.MapEntry (first pseq) (v (first pseq))) 1.407 + (thisfn (rest pseq)))))) (keys pmap)))))) 1.408 + 1.409 + 1.410 +