aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChouser <chouser@n01se.net>2008-08-12 18:21:29 +0000
committerChouser <chouser@n01se.net>2008-08-12 18:21:29 +0000
commit0a6e10e4fc7f34de99fe6a090eccb0092e548f3e (patch)
tree79eb2b12335ddd190a4e8e25a6f0c80b040f0a9b
parent126d5ec374840c809684e9b780c4eea2ddf9beb6 (diff)
Update gen-interface -- each method description is a vector, and now
gen-interface allows re-loading of an idential interface.
-rw-r--r--gen_interface/gen_interface.clj203
1 files changed, 128 insertions, 75 deletions
diff --git a/gen_interface/gen_interface.clj b/gen_interface/gen_interface.clj
index fc66fc16..0cf8d619 100644
--- a/gen_interface/gen_interface.clj
+++ b/gen_interface/gen_interface.clj
@@ -13,92 +13,145 @@
(clojure/refer 'clojure)
(import '(clojure.asm ClassWriter Opcodes Type)
- '(clojure.asm.commons Method GeneratorAdapter)
- '(java.io File))
-
-(defn gen-and-return-interface
- "Uses the given specs to generate a Java interface. Returns a map
- with :iname being the Java internal name of the interface, and
- :bytecode being an array of Bytes of the Java bytecode. You'll
- almost always want to use gen-interface instead."
+ '(java.io File FileOutputStream IOException))
+
+(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
+ (such as 'java.lang.String)"
+ [c]
+ (if (instance? Class c)
+ (Type/getType c)
+ (Type/getObjectType (.replace (str c) "." "/"))))
+
+(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
+ "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).
+ 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]
- (let [to-iname (fn [c] (if (instance? Class c)
- (.. Type (getType c) getInternalName)
- (.replace (str c) "." "/")))
- to-types (fn [cs] (if (seq cs)
- (into-array (map #(Type/getType %) cs))
- (make-array Type 0)))
- iname (to-iname cname)
- cv (ClassWriter. ClassWriter/COMPUTE_MAXS)]
- (.visit cv Opcodes/V1_5 (+ Opcodes/ACC_PUBLIC Opcodes/ACC_ABSTRACT
- Opcodes/ACC_INTERFACE)
- iname nil (to-iname Object)
- (when (seq extends)
- (into-array (map to-iname extends))))
- (doseq [mname pclasses rclass :as msig] (partition 3 methods)
- (GeneratorAdapter. (+ Opcodes/ACC_PUBLIC Opcodes/ACC_ABSTRACT)
- (Method. (str mname)
- (Type/getType rclass)
- (to-types pclasses))
- nil nil cv))
- (.visitEnd cv)
- {:iname iname :bytecode (.toByteArray cv)}))
-
-(defn gen-and-load-interface
- "Uses the given specs to generate and immediately load a Java
- interface. 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."
- [cname & args]
- (let [{:keys [iname bytecode]} (apply gen-and-return-interface cname args)]
- (.. clojure.lang.RT ROOT_CLASSLOADER (defineClass (str cname) bytecode))))
-
-(defn gen-and-save-interface
- "Uses the given specs to generate a Java interface and save it to a
- .class file. Returns the bytecode of the interface. If you intend
- to use this interface immediately (for example to refer to it in a
- later gen-interface call as a parameter type, return type, or super
- class), you'll want to use gen-interface instead."
- [path cname & args]
- (let [{:keys [iname bytecode]} (apply gen-and-return-interface cname args)
- file (File. path (str (.replace (str cname) \. File/separatorChar)
+ {: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
+ "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
+ "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]
+ (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)
+ (. cv visitMethod (+ Opcodes/ACC_PUBLIC Opcodes/ACC_ABSTRACT)
+ mname
+ (Type/getMethodDescriptor rclass (if pclasses
+ (into-array pclasses)
+ (make-array Type 0)))
+ nil nil))
+ (. cv visitEnd)
+ (. 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))]
+ (if old-class
+ (when-not (= spec (spec-from-class old-class))
+ (throw (Exception. (str "A different class named "
+ (:cname spec) " already loaded"))))
+ (.. clojure.lang.RT
+ ROOT_CLASSLOADER (defineClass (:cname spec) (spec-bytecode spec))))))
+
+(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)
".class"))]
(try
(.createNewFile file)
- (catch java.io.IOException e
+ (catch IOException e
(throw (Exception. (str "Failed to create " file) e))))
- (with-open f (java.io.FileOutputStream. file)
- (.write f bytecode))
- bytecode))
+ (with-open f (FileOutputStream. file)
+ (.write f (spec-bytecode spec)))))
(defn gen-interface
- "Uses the given specs to generate a Java interface, save it to a
- .class file, and immediately load it so it's ready for use by
- subsequent gen-interface calls. The path is the base classpath
- under which the .class file will be written. 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). The next arg must
- be a collection of classes this interface will extend (each may be
- a string, symbol, or a class). This may be followed by any number
- of: methodName, arg types, return type."
- [path cname & args]
- (.. clojure.lang.RT ROOT_CLASSLOADER
- (defineClass (str cname) (apply gen-and-save-interface path cname args))))
+ "Uses the given interface description to generate a Java interface,
+ save it to a .class file, and immediately load it so it's ready
+ for use by subsequent gen-interface or gen-class calls. The .class
+ file will be written into a sub-directory of the given base path.
+ 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)))
(comment
(gen-interface "/tmp" 'net.n01se.Foo [Appendable]
- 'foo [] Integer
- 'bar [Integer String] Double)
-
-; save is used 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. Why not make sure Other
-; is defined and then just use gen-interface?
-(gen-and-save-interface "/tmp" 'net.n01se.Bar ['net.n01se.Other Iterable]
- 'baz [] net.n01se.Foo)
+ ['foo [] Integer]
+ ['bar [Integer/TYPE String] Double])
+
+; re-genning an identical interface doesn't try to load anything
+(gen-interface "/tmp" 'net.n01se.Foo [Appendable]
+ ['foo [] Integer]
+ ['bar [Integer/TYPE String] Double])
+
+; re-genning a different interface throws an exception
+;(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]]))
(prn :isInterface (.isInterface (identity net.n01se.Foo)))
(prn :interfaces (seq (.getGenericInterfaces (identity net.n01se.Foo))))