aboutsummaryrefslogtreecommitdiff
path: root/gen_interface/gen_interface.clj
diff options
context:
space:
mode:
Diffstat (limited to 'gen_interface/gen_interface.clj')
-rw-r--r--gen_interface/gen_interface.clj142
1 files changed, 80 insertions, 62 deletions
diff --git a/gen_interface/gen_interface.clj b/gen_interface/gen_interface.clj
index 0cf8d619..2ab9a601 100644
--- a/gen_interface/gen_interface.clj
+++ b/gen_interface/gen_interface.clj
@@ -15,7 +15,7 @@
(import '(clojure.asm ClassWriter Opcodes Type)
'(java.io File FileOutputStream IOException))
-(defn asm-type
+(defn- asm-type
"Returns an asm Type object for c, which may be a primitive class
(such as Integer/TYPE), any other class (such as Double), or a
fully-qualified class name given as a string or symbol
@@ -25,56 +25,60 @@
(Type/getType c)
(Type/getObjectType (.replace (str c) "." "/"))))
-(defn iname
+(defn- iname
"Returns the internal name of given class or class name. Cannot be
used for primitive types."
[c] (.getInternalName (asm-type c)))
-(defn make-spec
+(defstruct #^{:private true} spec-map :cname :iname :extends :methods)
+
+(defn- make-spec
"Returns an interface spec object based on the given description.
cname is the fully-qualified classname (string or symbol) of the
- interface to be created (note that the appropriate sub-directories
- under path must already exist or this will throw an exception).
+ interface to be created.
extends is a collection of classes this interface will extend (each
may be a string, symbol, or a class). These are followed by the
method descriptions, each of which is a vector: [methodName,
arg types, return type]"
[cname extends & methods]
- {:cname (str cname)
- :iname (iname cname)
- :extends (set (map iname extends))
- :methods (set (map (fn [[mname pclasses rclass]]
- [(str mname)
- (map asm-type pclasses)
- (asm-type rclass)])
- methods))})
-
-(defn spec-from-class
+ (struct spec-map
+ (str cname)
+ (iname cname)
+ (set (map iname extends))
+ (set (map (fn [[mname pclasses rclass]]
+ [(str mname)
+ (map asm-type pclasses)
+ (asm-type rclass)])
+ methods))))
+
+(defn- spec-from-class
"Returns an interface spec object based on the given class."
[c]
- {:cname (.getName c)
- :iname (iname c)
- :extends (set (map iname (.getInterfaces c)))
- :methods (set (map (fn [m]
- [(.getName m)
- (map asm-type (.getParameterTypes m))
- (asm-type (.getReturnType m))])
- (.getDeclaredMethods c)))})
-
-
-(defn spec-bytecode
+ (struct spec-map
+ (.getName c)
+ (iname c)
+ (set (map iname (.getInterfaces c)))
+ (set (map (fn [m]
+ [(.getName m)
+ (map asm-type (.getParameterTypes m))
+ (asm-type (.getReturnType m))])
+ (.getDeclaredMethods c)))))
+
+(def #^{:private true} object-iname (iname Object))
+
+(defn- spec-bytecode
"Uses the given interface spec object (such as created by make-spec)
to generate a Java interface. Returns a byte array containing the
Java bytecode for the interface. You'll almost always want to use
gen-interface instead."
- [spec]
+ [{:keys [iname extends methods]}]
(let [cv (ClassWriter. ClassWriter/COMPUTE_MAXS)]
(. cv visit Opcodes/V1_5 (+ Opcodes/ACC_PUBLIC Opcodes/ACC_ABSTRACT
Opcodes/ACC_INTERFACE)
- (:iname spec) nil "java/lang/Object"
- (when (seq (:extends spec))
- (into-array (:extends spec))))
- (doseq [mname pclasses rclass] (:methods spec)
+ iname nil object-iname
+ (when (seq extends)
+ (into-array extends)))
+ (doseq [mname pclasses rclass] methods
(. cv visitMethod (+ Opcodes/ACC_PUBLIC Opcodes/ACC_ABSTRACT)
mname
(Type/getMethodDescriptor rclass (if pclasses
@@ -85,37 +89,50 @@
(. cv toByteArray)))
-(defn load-interface
- "Uses the given interface spec object (such as created by make-spec)
- to generate a Java interface and immediately load it. This is not
- generally useful since you'll usually want a .class file in order
- to write Java code that uses the generated interface. See
- gen-interface instead."
- [spec]
- (let [old-class (try (Class/forName (:cname spec)) (catch Throwable t nil))]
+(defn- load-interface-bytecode
+ [{:keys [cname] :as spec} bytecode]
+ (let [old-class (try (Class/forName cname) (catch Throwable t nil))]
(if old-class
(when-not (= spec (spec-from-class old-class))
(throw (Exception. (str "A different class named "
- (:cname spec) " already loaded"))))
+ cname " already loaded"))))
(.. clojure.lang.RT
- ROOT_CLASSLOADER (defineClass (:cname spec) (spec-bytecode spec))))))
+ ROOT_CLASSLOADER (defineClass cname bytecode)))))
-(defn save-interface
- "Uses the given interface spec object (such as created by make-spec)
- to generate a Java interface and save it to a .class file. The
- .class file will be written into a sub-directory of the given base
- path. If you intend to use this interface immediately (for example
- to refer to it in a later gen-interface or gen-class call), you'll
- want to use gen-interface instead."
- [path spec]
- (let [file (File. path (str (.replace (:cname spec) \. File/separatorChar)
+(defn- save-interface-bytecode
+ [path {:keys [cname]} bytecode]
+ (let [file (File. path (str (.replace cname \. File/separatorChar)
".class"))]
(try
(.createNewFile file)
(catch IOException e
(throw (Exception. (str "Failed to create " file) e))))
(with-open f (FileOutputStream. file)
- (.write f (spec-bytecode spec)))))
+ (.write f bytecode))))
+
+(defn gen-and-load-interface
+ "Uses the given interface description to generate a Java interface
+ and immediately load it. make-spec-args is the interface
+ description as documented in make-spec. This function is not
+ generally useful since you'll usually want a .class file in order
+ to write Java code that uses the generated interface -- see
+ gen-interface instead."
+ [& make-spec-args]
+ (let [spec (apply make-spec make-spec-args)]
+ (load-interface-bytecode spec (spec-bytecode spec))))
+
+(defn gen-and-save-interface
+ "Uses the given interface description to generate a Java interface
+ and save it to a .class file. make-spec-args is the interface
+ description as documented in make-spec. The .class file will be
+ written into a sub-directory of the given base path (note that the
+ appropriate sub-directories under path must already exist or this
+ will throw an exception). If you intend to use this interface
+ immediately (for example to refer to it in a later gen-interface or
+ gen-class call), you'll want to use gen-interface instead."
+ [path & make-spec-args]
+ (let [spec (apply make-spec make-spec-args)]
+ (save-interface-bytecode path spec (spec-bytecode spec))))
(defn gen-interface
"Uses the given interface description to generate a Java interface,
@@ -125,9 +142,10 @@
make-spec-args is the interface description as documented in
make-spec."
[path & make-spec-args]
- (let [spec (apply make-spec make-spec-args)]
- (load-interface spec)
- (save-interface path spec)))
+ (let [spec (apply make-spec make-spec-args)
+ bytecode (spec-bytecode spec)]
+ (load-interface-bytecode spec bytecode)
+ (save-interface-bytecode path spec bytecode)))
(comment
@@ -144,14 +162,14 @@
;(gen-interface "/tmp" 'net.n01se.Foo [Appendable]
; ['foo [] Integer])
-; save-interface is used directly in this example because I want
-; to refer to a class that's not yet defined in this runtime (Other).
-; It's possible by not loading the interface and specifying the class
-; via a quoted symbol, but this isn't really recommended. Instead,
-; why not make sure Other is defined -- then you can use gen-interface.
-(save-interface "/tmp"
- (make-spec 'net.n01se.Bar ['net.n01se.Other Iterable]
- [['baz [] net.n01se.Foo]]))
+; gen-and-save-interface is used directly in this example because I
+; want to refer to a class that's not yet defined in this runtime
+; (Other). This is possible because I specify the class as a quoted
+; symbol, and then don't load it -- but this isn't really recommended.
+; Instead, why not make sure Other is defined -- then you can use
+; gen-interface.
+(gen-and-save-interface "/tmp" 'net.n01se.Bar ['net.n01se.Other Iterable]
+ ['baz [] net.n01se.Foo])
(prn :isInterface (.isInterface (identity net.n01se.Foo)))
(prn :interfaces (seq (.getGenericInterfaces (identity net.n01se.Foo))))