diff options
author | Chouser <chouser@n01se.net> | 2008-08-12 18:21:29 +0000 |
---|---|---|
committer | Chouser <chouser@n01se.net> | 2008-08-12 18:21:29 +0000 |
commit | 0a6e10e4fc7f34de99fe6a090eccb0092e548f3e (patch) | |
tree | 79eb2b12335ddd190a4e8e25a6f0c80b040f0a9b | |
parent | 126d5ec374840c809684e9b780c4eea2ddf9beb6 (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.clj | 203 |
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)))) |