summaryrefslogtreecommitdiff
path: root/src/proxy.clj
blob: 5ca04e64b00cd7979b1b893bf7b734430f29561d (plain)
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
;   The use and distribution terms for this software are covered by the
;   Common Public License 1.0 (http://opensource.org/licenses/cpl.php)
;   which can be found in the file CPL.TXT 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.

(in-ns 'clojure)

(import
 '(clojure.asm ClassWriter ClassVisitor Opcodes Type) 
 '(java.lang.reflect Modifier Constructor)
 '(clojure.asm.commons Method GeneratorAdapter)
 '(clojure.lang IProxy Reflector DynamicClassLoader IPersistentMap PersistentHashMap))

(def *proxy-classes* (ref {}))

(defn get-proxy-class [& bases]
  (let [bases (if (. (first bases) (isInterface))
                (cons Object bases)
                bases)
        [super & interfaces] bases]
    (or (get @*proxy-classes* bases)
        (let [cv (new ClassWriter (. ClassWriter COMPUTE_MAXS))
              cname (str "clojure/lang/" (gensym "Proxy__"))
              ctype (. Type (getObjectType cname))
              iname (fn [c] (.. Type (getType c) (getInternalName)))
              fmap "__clojureFnMap"
              to-types (fn [cs] (if (pos? (count cs))
                                  (into-array (map #(. Type (getType %)) cs))
                                  (make-array Type 0)))
              map-type (. Type (getType PersistentHashMap))]
          ;start class definition
          (. cv (visit (. Opcodes V1_5) (. Opcodes ACC_PUBLIC) 
                       cname nil (iname super) 
                       (into-array (map iname (cons IProxy interfaces)))))
          ;add field for fn mappings
          (. cv (visitField (+ (. Opcodes ACC_PRIVATE) (. Opcodes ACC_VOLATILE))
                            fmap (. map-type (getDescriptor)) nil nil))          
          ;add ctors matching/calling super's
          (doseq #^Constructor ctor (. super (getDeclaredConstructors))
            (when-not (. Modifier (isPrivate (. ctor (getModifiers))))
              (let [ptypes (to-types (. ctor (getParameterTypes)))
                    m (new Method "<init>" (. Type VOID_TYPE) ptypes)
                    gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]
                (. gen (visitCode))
                ;call super ctor
                (. gen (loadThis))
                (. gen (dup))
                (. gen (loadArgs))
                (. gen (invokeConstructor (. Type (getType super)) m))
                ;init fmap
                (. gen (getStatic map-type "EMPTY" map-type))
                (. gen (putField ctype fmap map-type))
                
                (. gen (returnValue))
                (. gen (endMethod)))))
          ;add IProxy methods
          (let [m (. Method (getMethod "void __updateClojureFnMappings(clojure.lang.IPersistentMap)"))
                gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]
            (. gen (visitCode))
            (. gen (loadThis))
            (. gen (dup))
            (. gen (getField ctype fmap map-type))
            (. gen (loadArgs))
            (. gen (invokeInterface (. Type (getType clojure.lang.IPersistentCollection))
                                    (. Method (getMethod "clojure.lang.IPersistentCollection cons(Object)"))))
            (. gen (checkCast map-type))
            (. gen (putField ctype fmap map-type))
                            
            (. gen (returnValue))
            (. gen (endMethod)))
          (let [m (. Method (getMethod "clojure.lang.IPersistentMap __getClojureFnMappings()"))
                gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]
            (. gen (visitCode))
            (. gen (loadThis))
            (. gen (getField ctype fmap map-type))
            (. gen (returnValue))
            (. gen (endMethod)))

          ;add methods matching super's, if no mapping -> call super
          ;add methods matching interfaces', if no mapping -> return void or throw

          ;finish class def
          (. cv (visitEnd))
          ;generate, cache and return class object
          (let [loader (new DynamicClassLoader)
                c (. loader (defineClass (. cname (replace "/" ".")) 
                              (. cv (toByteArray))))]
            (sync nil (commute *proxy-classes* assoc bases c))
            c)))))

(defn construct-proxy [c & ctor-args]
  (. Reflector (invokeConstructor c (to-array ctor-args))))

(defn update-proxy [#^IProxy proxy mappings]
  (. proxy (updateMappings mappings)))

(defn proxy-mappings [#^IProxy proxy]
  (. proxy (getMappings)))