Skip to content

Java 9 Modules

Justin Conklin edited this page Apr 27, 2018 · 3 revisions

As of version 0.2.1, insn supports generating Java 9 module definitions.

For this demonstration, we recreate the examples from the Project Jigsaw quick start.

First, we'll create our deps.edn file. Since the ASM version bundled with Clojure (as of version 1.9) is too old for our purposes, we must add a dependency on a newer version:

{:deps {insn {:mvn/version "0.2.1"}
        org.ow2.asm/asm {:mvn/version "6.0"}}}

(Note: since 0.3.0, the ASM dependency is no longer necessary.)

Next, we start a REPL with our above dependencies on the class path:

$ clj -r

Like with class types, modules are just simple data:

(def greetings-module-data
  {:id :com.greetings
   :requires [:org.astro]})

(def astro-module-data
  {:id :org.astro
   :exports [:org.astro]})

We need some types that will reside in our separate modules:

(def world-class-data
  {:name 'org.astro.World
   :methods [{:flags [:public :static]
              :name "name"
              :desc [String]
              :emit [[:ldc "World"]
                     [:areturn]]}]})

(def main-class-data
  {:name 'com.greetings.Main
   :methods [{:flags [:public :static]
              :name "main"
              :desc [[String] :void]
              :emit [[:getstatic System "out"]
                     [:ldc "Greetings %s!%n"]
                     [:ldc 1]
                     [:anewarray Object]
                     [:dup]
                     [:ldc 0]
                     [:invokestatic 'org.astro.World "name" [String]]
                     [:aastore]
                     [:invokevirtual java.io.PrintStream "format" 2]
                     [:return]]}]})

Now we write our two simple modules named com.greetings and org.astro to the current directory:

(require '[insn.objectweb-asm] ;; load the external version
         '[insn.core :as type]
         '[insn.module :as module])

(set! *compile-path* "mods/com.greetings") ;; used by `insn.core/write`
(-> (module/visit greetings-module-data)
    type/write) ;; to ./mods/com.greetings/module-info.class
(-> (type/visit main-class-data)
    type/write) ;; to ./mods/com.greetings/com/greetings/Main.class

(set! *compile-path* "mods/org.astro")
(-> (module/visit astro-module-data)
    type/write) ;; to ./mods/org.astro/module-info.class
(-> (type/visit world-class-data)
    type/write) ;; to ./mods/org.astro/org/astro/World.class

We can run our main class from within the com.greetings module using Java 9:

$ java -version
# => java version "9.0.1"

$ java --module-path mods -m com.greetings/com.greetings.Main
# => Greetings world!

The final Jigsaw example deals with module services. We simplify it slightly here for brevity, but it is essentially the same. Continuing in our REPL session from above; first the module defs:

(def greetings2-module-data
  {:id :com.greetings2
   :requires [:com.socket]
   :uses ['com.socket.spi.NetworkSocketProvider]})

(def socket-module-data
  {:id :com.socket
   :exports [:com.socket, :com.socket.spi]})

(def fastsocket-module-data
  {:id :org.fastsocket
   :requires [:com.socket]
   :provides {'com.socket.spi.NetworkSocketProvider
              'org.fastsocket.FastNetworkSocketProvider}})

Next, the interface and class types:

(def isocket-class-data
  {:name 'com.socket.NetworkSocket
   :flags [:public :interface]})

(def iprovider-class-data
  {:name 'com.socket.spi.NetworkSocketProvider
   :flags [:public :interface]
   :methods [{:flags [:public :abstract]
              :name "openNetworkSocket"
              :desc ['com.socket.NetworkSocket]}]})

(def socket-class-data
  {:name 'org.fastsocket.FastNetworkSocket
   :flags [:public]
   :interfaces ['com.socket.NetworkSocket]})

(def provider-class-data
  {:name 'org.fastsocket.FastNetworkSocketProvider
   :flags [:public]
   :interfaces ['com.socket.spi.NetworkSocketProvider]
   :methods [{:flags [:public]
              :name "openNetworkSocket"
              :desc ['com.socket.NetworkSocket]
              :emit [[:new 'org.fastsocket.FastNetworkSocket]
                     [:dup]
                     [:invokespecial 'org.fastsocket.FastNetworkSocket :init [:void]]
                     [:areturn]]}]})

(def main-class2-data
  {:name 'com.greetings.Main
   :methods [{:flags [:public :static]
              :name "main"
              :desc [[String] :void]
              :emit [[:getstatic System "out"]
                     [:ldc 'com.socket.spi.NetworkSocketProvider]
                     [:invokestatic java.util.ServiceLoader "load" 1]
                     [:invokeinterface Iterable "iterator"]
                     [:invokeinterface java.util.Iterator "next"]
                     [:checkcast 'com.socket.spi.NetworkSocketProvider]
                     [:invokeinterface 'com.socket.spi.NetworkSocketProvider
                                       "openNetworkSocket" ['com.socket.NetworkSocket]]
                     [:invokevirtual Object "getClass"]
                     [:invokevirtual java.io.PrintStream "println" [Object :void]]
                     [:return]]}]})

Finally, we emit our .class files to disk like before:

(set! *compile-path* "mods/com.greetings2")
(run! type/write [(module/visit greetings2-module-data)
                  (type/visit main-class2-data)])

(set! *compile-path* "mods/com.socket")
(run! type/write [(module/visit socket-module-data)
                  (type/visit isocket-class-data)
                  (type/visit iprovider-class-data)])

(set! *compile-path* "mods/org.fastsocket")
(run! type/write [(module/visit fastsocket-module-data)
                  (type/visit socket-class-data)
                  (type/visit provider-class-data)])

Now we run our main class from within the com.greetings2 module that uses the socket service:

$ java -p mods -m com.greetings2/com.greetings.Main
# => class org.fastsocket.FastNetworkSocket

For more on ASM's module support see the javadocs.