Mercurial > lasercutter
view 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 source
1 ; Copyright (c) Rich Hickey. All rights reserved.2 ; The use and distribution terms for this software are covered by the3 ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)4 ; which can be found in the file epl-v10.html at the root of this distribution.5 ; By using this software in any fashion, you are agreeing to be bound by6 ; the terms of this license.7 ; You must not remove this notice, or any other, from this software.9 (in-ns 'clojure.core)11 ;;;;;;;;;;;;;;;;;;;;;;;;;;;; proxy ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;13 (import14 '(clojure.asm ClassWriter ClassVisitor Opcodes Type)15 '(java.lang.reflect Modifier Constructor)16 '(clojure.asm.commons Method GeneratorAdapter)17 '(clojure.lang IProxy Reflector DynamicClassLoader IPersistentMap PersistentHashMap RT))19 (defn method-sig [^java.lang.reflect.Method meth]20 [(. meth (getName)) (seq (. meth (getParameterTypes))) (. meth getReturnType)])22 (defn- most-specific [rtypes]23 (or (some (fn [t] (when (every? #(isa? t %) rtypes) t)) rtypes)24 (throw (Exception. "Incompatible return types"))))26 (defn- group-by-sig [coll]27 "takes a collection of [msig meth] and returns a seq of maps from return-types to meths."28 (vals (reduce (fn [m [msig meth]]29 (let [rtype (peek msig)30 argsig (pop msig)]31 (assoc m argsig (assoc (m argsig {}) rtype meth))))32 {} coll)))34 (defn proxy-name35 {:tag String}36 [^Class super interfaces]37 (let [inames (into (sorted-set) (map #(.getName ^Class %) interfaces))]38 (apply str (.replace (str *ns*) \- \_) ".proxy"39 (interleave (repeat "$")40 (concat41 [(.getName super)]42 (map #(subs % (inc (.lastIndexOf ^String % "."))) inames)43 [(Integer/toHexString (hash inames))])))))45 (defn- generate-proxy [^Class super interfaces]46 (let [cv (new ClassWriter (. ClassWriter COMPUTE_MAXS))47 cname (.replace (proxy-name super interfaces) \. \/) ;(str "clojure/lang/" (gensym "Proxy__"))48 ctype (. Type (getObjectType cname))49 iname (fn [^Class c] (.. Type (getType c) (getInternalName)))50 fmap "__clojureFnMap"51 totype (fn [^Class c] (. Type (getType c)))52 to-types (fn [cs] (if (pos? (count cs))53 (into-array (map totype cs))54 (make-array Type 0)))55 super-type ^Type (totype super)56 imap-type ^Type (totype IPersistentMap)57 ifn-type (totype clojure.lang.IFn)58 obj-type (totype Object)59 sym-type (totype clojure.lang.Symbol)60 rt-type (totype clojure.lang.RT)61 ex-type (totype java.lang.UnsupportedOperationException)62 gen-bridge63 (fn [^java.lang.reflect.Method meth ^java.lang.reflect.Method dest]64 (let [pclasses (. meth (getParameterTypes))65 ptypes (to-types pclasses)66 rtype ^Type (totype (. meth (getReturnType)))67 m (new Method (. meth (getName)) rtype ptypes)68 dtype (totype (.getDeclaringClass dest))69 dm (new Method (. dest (getName)) (totype (. dest (getReturnType))) (to-types (. dest (getParameterTypes))))70 gen (new GeneratorAdapter (bit-or (. Opcodes ACC_PUBLIC) (. Opcodes ACC_BRIDGE)) m nil nil cv)]71 (. gen (visitCode))72 (. gen (loadThis))73 (dotimes [i (count ptypes)]74 (. gen (loadArg i)))75 (if (-> dest .getDeclaringClass .isInterface)76 (. gen (invokeInterface dtype dm))77 (. gen (invokeVirtual dtype dm)))78 (. gen (returnValue))79 (. gen (endMethod))))80 gen-method81 (fn [^java.lang.reflect.Method meth else-gen]82 (let [pclasses (. meth (getParameterTypes))83 ptypes (to-types pclasses)84 rtype ^Type (totype (. meth (getReturnType)))85 m (new Method (. meth (getName)) rtype ptypes)86 gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)87 else-label (. gen (newLabel))88 end-label (. gen (newLabel))89 decl-type (. Type (getType (. meth (getDeclaringClass))))]90 (. gen (visitCode))91 (if (> (count pclasses) 18)92 (else-gen gen m)93 (do94 (. gen (loadThis))95 (. gen (getField ctype fmap imap-type))97 (. gen (push (. meth (getName))))98 ;lookup fn in map99 (. gen (invokeStatic rt-type (. Method (getMethod "Object get(Object, Object)"))))100 (. gen (dup))101 (. gen (ifNull else-label))102 ;if found103 (.checkCast gen ifn-type)104 (. gen (loadThis))105 ;box args106 (dotimes [i (count ptypes)]107 (. gen (loadArg i))108 (. clojure.lang.Compiler$HostExpr (emitBoxReturn nil gen (nth pclasses i))))109 ;call fn110 (. gen (invokeInterface ifn-type (new Method "invoke" obj-type111 (into-array (cons obj-type112 (replicate (count ptypes) obj-type))))))113 ;unbox return114 (. gen (unbox rtype))115 (when (= (. rtype (getSort)) (. Type VOID))116 (. gen (pop)))117 (. gen (goTo end-label))119 ;else call supplied alternative generator120 (. gen (mark else-label))121 (. gen (pop))123 (else-gen gen m)125 (. gen (mark end-label))))126 (. gen (returnValue))127 (. gen (endMethod))))]129 ;start class definition130 (. cv (visit (. Opcodes V1_5) (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_SUPER))131 cname nil (iname super)132 (into-array (map iname (cons IProxy interfaces)))))133 ;add field for fn mappings134 (. cv (visitField (+ (. Opcodes ACC_PRIVATE) (. Opcodes ACC_VOLATILE))135 fmap (. imap-type (getDescriptor)) nil nil))136 ;add ctors matching/calling super's137 (doseq [^Constructor ctor (. super (getDeclaredConstructors))]138 (when-not (. Modifier (isPrivate (. ctor (getModifiers))))139 (let [ptypes (to-types (. ctor (getParameterTypes)))140 m (new Method "<init>" (. Type VOID_TYPE) ptypes)141 gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]142 (. gen (visitCode))143 ;call super ctor144 (. gen (loadThis))145 (. gen (dup))146 (. gen (loadArgs))147 (. gen (invokeConstructor super-type m))149 (. gen (returnValue))150 (. gen (endMethod)))))151 ;add IProxy methods152 (let [m (. Method (getMethod "void __initClojureFnMappings(clojure.lang.IPersistentMap)"))153 gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]154 (. gen (visitCode))155 (. gen (loadThis))156 (. gen (loadArgs))157 (. gen (putField ctype fmap imap-type))159 (. gen (returnValue))160 (. gen (endMethod)))161 (let [m (. Method (getMethod "void __updateClojureFnMappings(clojure.lang.IPersistentMap)"))162 gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]163 (. gen (visitCode))164 (. gen (loadThis))165 (. gen (dup))166 (. gen (getField ctype fmap imap-type))167 (.checkCast gen (totype clojure.lang.IPersistentCollection))168 (. gen (loadArgs))169 (. gen (invokeInterface (totype clojure.lang.IPersistentCollection)170 (. Method (getMethod "clojure.lang.IPersistentCollection cons(Object)"))))171 (. gen (checkCast imap-type))172 (. gen (putField ctype fmap imap-type))174 (. gen (returnValue))175 (. gen (endMethod)))176 (let [m (. Method (getMethod "clojure.lang.IPersistentMap __getClojureFnMappings()"))177 gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]178 (. gen (visitCode))179 (. gen (loadThis))180 (. gen (getField ctype fmap imap-type))181 (. gen (returnValue))182 (. gen (endMethod)))184 ;calc set of supers' non-private instance methods185 (let [[mm considered]186 (loop [mm {} considered #{} c super]187 (if c188 (let [[mm considered]189 (loop [mm mm190 considered considered191 meths (concat192 (seq (. c (getDeclaredMethods)))193 (seq (. c (getMethods))))]194 (if (seq meths)195 (let [^java.lang.reflect.Method meth (first meths)196 mods (. meth (getModifiers))197 mk (method-sig meth)]198 (if (or (considered mk)199 (not (or (Modifier/isPublic mods) (Modifier/isProtected mods)))200 ;(. Modifier (isPrivate mods))201 (. Modifier (isStatic mods))202 (. Modifier (isFinal mods))203 (= "finalize" (.getName meth)))204 (recur mm (conj considered mk) (next meths))205 (recur (assoc mm mk meth) (conj considered mk) (next meths))))206 [mm considered]))]207 (recur mm considered (. c (getSuperclass))))208 [mm considered]))209 ifaces-meths (into {}210 (for [^Class iface interfaces meth (. iface (getMethods))211 :let [msig (method-sig meth)] :when (not (considered msig))]212 {msig meth}))213 mgroups (group-by-sig (concat mm ifaces-meths))214 rtypes (map #(most-specific (keys %)) mgroups)215 mb (map #(vector (%1 %2) (vals (dissoc %1 %2))) mgroups rtypes)216 bridge? (reduce into #{} (map second mb))217 ifaces-meths (remove bridge? (vals ifaces-meths))218 mm (remove bridge? (vals mm))]219 ;add methods matching supers', if no mapping -> call super220 (doseq [[^java.lang.reflect.Method dest bridges] mb221 ^java.lang.reflect.Method meth bridges]222 (gen-bridge meth dest))223 (doseq [^java.lang.reflect.Method meth mm]224 (gen-method meth225 (fn [^GeneratorAdapter gen ^Method m]226 (. gen (loadThis))227 ;push args228 (. gen (loadArgs))229 ;call super230 (. gen (visitMethodInsn (. Opcodes INVOKESPECIAL)231 (. super-type (getInternalName))232 (. m (getName))233 (. m (getDescriptor)))))))235 ;add methods matching interfaces', if no mapping -> throw236 (doseq [^java.lang.reflect.Method meth ifaces-meths]237 (gen-method meth238 (fn [^GeneratorAdapter gen ^Method m]239 (. gen (throwException ex-type (. m (getName))))))))241 ;finish class def242 (. cv (visitEnd))243 [cname (. cv toByteArray)]))245 (defn- get-super-and-interfaces [bases]246 (if (. ^Class (first bases) (isInterface))247 [Object bases]248 [(first bases) (next bases)]))250 (defn get-proxy-class251 "Takes an optional single class followed by zero or more252 interfaces. If not supplied class defaults to Object. Creates an253 returns an instance of a proxy class derived from the supplied254 classes. The resulting value is cached and used for any subsequent255 requests for the same class set. Returns a Class object."256 {:added "1.0"}257 [& bases]258 (let [[super interfaces] (get-super-and-interfaces bases)259 pname (proxy-name super interfaces)]260 (or (RT/loadClassForName pname)261 (let [[cname bytecode] (generate-proxy super interfaces)]262 (. ^DynamicClassLoader (deref clojure.lang.Compiler/LOADER) (defineClass pname bytecode [super interfaces]))))))264 (defn construct-proxy265 "Takes a proxy class and any arguments for its superclass ctor and266 creates and returns an instance of the proxy."267 {:added "1.0"}268 [c & ctor-args]269 (. Reflector (invokeConstructor c (to-array ctor-args))))271 (defn init-proxy272 "Takes a proxy instance and a map of strings (which must273 correspond to methods of the proxy superclass/superinterfaces) to274 fns (which must take arguments matching the corresponding method,275 plus an additional (explicit) first arg corresponding to this, and276 sets the proxy's fn map."277 {:added "1.0"}278 [^IProxy proxy mappings]279 (. proxy (__initClojureFnMappings mappings)))281 (defn update-proxy282 "Takes a proxy instance and a map of strings (which must283 correspond to methods of the proxy superclass/superinterfaces) to284 fns (which must take arguments matching the corresponding method,285 plus an additional (explicit) first arg corresponding to this, and286 updates (via assoc) the proxy's fn map. nil can be passed instead of287 a fn, in which case the corresponding method will revert to the288 default behavior. Note that this function can be used to update the289 behavior of an existing instance without changing its identity."290 {:added "1.0"}291 [^IProxy proxy mappings]292 (. proxy (__updateClojureFnMappings mappings)))294 (defn proxy-mappings295 "Takes a proxy instance and returns the proxy's fn map."296 {:added "1.0"}297 [^IProxy proxy]298 (. proxy (__getClojureFnMappings)))300 (defmacro proxy301 "class-and-interfaces - a vector of class names303 args - a (possibly empty) vector of arguments to the superclass304 constructor.306 f => (name [params*] body) or307 (name ([params*] body) ([params+] body) ...)309 Expands to code which creates a instance of a proxy class that310 implements the named class/interface(s) by calling the supplied311 fns. A single class, if provided, must be first. If not provided it312 defaults to Object.314 The interfaces names must be valid interface types. If a method fn315 is not provided for a class method, the superclass methd will be316 called. If a method fn is not provided for an interface method, an317 UnsupportedOperationException will be thrown should it be318 called. Method fns are closures and can capture the environment in319 which proxy is called. Each method fn takes an additional implicit320 first arg, which is bound to 'this. Note that while method fns can321 be provided to override protected methods, they have no other access322 to protected members, nor to super, as these capabilities cannot be323 proxied."324 {:added "1.0"}325 [class-and-interfaces args & fs]326 (let [bases (map #(or (resolve %) (throw (Exception. (str "Can't resolve: " %))))327 class-and-interfaces)328 [super interfaces] (get-super-and-interfaces bases)329 compile-effect (when *compile-files*330 (let [[cname bytecode] (generate-proxy super interfaces)]331 (clojure.lang.Compiler/writeClassFile cname bytecode)))332 pc-effect (apply get-proxy-class bases)333 pname (proxy-name super interfaces)]334 ;remember the class to prevent it from disappearing before use335 (intern *ns* (symbol pname) pc-effect)336 `(let [;pc# (get-proxy-class ~@class-and-interfaces)337 p# (new ~(symbol pname) ~@args)] ;(construct-proxy pc# ~@args)]338 (init-proxy p#339 ~(loop [fmap {} fs fs]340 (if fs341 (let [[sym & meths] (first fs)342 meths (if (vector? (first meths))343 (list meths)344 meths)345 meths (map (fn [[params & body]]346 (cons (apply vector 'this params) body))347 meths)]348 (if-not (contains? fmap (name sym))349 (recur (assoc fmap (name sym) (cons `fn meths)) (next fs))350 (throw (IllegalArgumentException.351 (str "Method '" (name sym) "' redefined")))))352 fmap)))353 p#)))355 (defn proxy-call-with-super [call this meth]356 (let [m (proxy-mappings this)]357 (update-proxy this (assoc m meth nil))358 (let [ret (call)]359 (update-proxy this m)360 ret)))362 (defmacro proxy-super363 "Use to call a superclass method in the body of a proxy method.364 Note, expansion captures 'this"365 {:added "1.0"}366 [meth & args]367 `(proxy-call-with-super (fn [] (. ~'this ~meth ~@args)) ~'this ~(name meth)))369 (defn bean370 "Takes a Java object and returns a read-only implementation of the371 map abstraction based upon its JavaBean properties."372 {:added "1.0"}373 [^Object x]374 (let [c (. x (getClass))375 pmap (reduce (fn [m ^java.beans.PropertyDescriptor pd]376 (let [name (. pd (getName))377 method (. pd (getReadMethod))]378 (if (and method (zero? (alength (. method (getParameterTypes)))))379 (assoc m (keyword name) (fn [] (clojure.lang.Reflector/prepRet (. method (invoke x nil)))))380 m)))381 {}382 (seq (.. java.beans.Introspector383 (getBeanInfo c)384 (getPropertyDescriptors))))385 v (fn [k] ((pmap k)))386 snapshot (fn []387 (reduce (fn [m e]388 (assoc m (key e) ((val e))))389 {} (seq pmap)))]390 (proxy [clojure.lang.APersistentMap]391 []392 (containsKey [k] (contains? pmap k))393 (entryAt [k] (when (contains? pmap k) (new clojure.lang.MapEntry k (v k))))394 (valAt ([k] (v k))395 ([k default] (if (contains? pmap k) (v k) default)))396 (cons [m] (conj (snapshot) m))397 (count [] (count pmap))398 (assoc [k v] (assoc (snapshot) k v))399 (without [k] (dissoc (snapshot) k))400 (seq [] ((fn thisfn [plseq]401 (lazy-seq402 (when-let [pseq (seq plseq)]403 (cons (new clojure.lang.MapEntry (first pseq) (v (first pseq)))404 (thisfn (rest pseq)))))) (keys pmap))))))