1

I am relatively novice at Clojure and Java. I have an existing Clojure project someone else wrote that I am trying to embed in NodeJS using node-java.

Clojure

The project defines a namespace that provides certain public functions, like so:

(ns my.namespace
  (:require ...etc...))

(defn dosomething ...)
(defn dosomethingelse ...)

I have built the project with leiningen (lein jar and lein uberjar).

Questions

The #import() docs on node-java say I need to import a java class like so:

const java = require('java');
var Test = java.import('Test');
  1. How can I access these functions (presumably as Java class static methods?)
  2. Am I approaching this all wrong? =)

Update

Thanks to Magos (answer below) I made a little more progress. It turns out I can use (:gen-class :name my.name) in the (ns ...) scope to tell it to generate a class. If I add a profile to the project.clj like so:

...
:profiles {
  ...
  :uberjar {:aot :all}
}
...

It will compile and I can now see the class in Node. I still haven't figured out how to export the methods, though. Working on that part now.

Sir Robert
  • 4,686
  • 7
  • 41
  • 57
  • I would think that there wouldn't be a difference between importing a .jar made from a Java project, and a .jar made from a Clojure project. Have you been able to import any .jars from Java projects? The Clojure factor may be a red herring. – Carcigenicate Aug 04 '17 at 15:54
  • @Carcigenicate It doesn't given an error when I add the jar. But the bigger point (I think) is that I don't know what the class is in the generated .jar. Can I find out what classes are available in a jar somehow? – Sir Robert Aug 04 '17 at 15:56
  • That, I don't know. I'd open it in a good IDE, try to import it, then see if the autocompletion gives you any hints. I would expect that they'd all be under `Test`, and accessed using JS's member access operator. If you can't find an in depth guide, just playing around may prove useful. – Carcigenicate Aug 04 '17 at 16:02
  • If all you want to do is export some static functions from a clojure jar, one of my old answers (https://stackoverflow.com/questions/2181774/calling-clojure-from-java) shows how to do so, but the method Magos discusses is more general. – clartaq Aug 04 '17 at 18:06
  • To see what classes are in a jar file, you can just open the jar with something like WinZip since jars are essentially just zip files. – clartaq Aug 04 '17 at 18:07

2 Answers2

3

Since someone else wrote the Clojure, I'll assume you aren't in control of it. The recommended approach for using Clojure code from another JVM language is bootstrapping from the class clojure.java.api.Clojure. This class allows you to resolve Vars from Clojure, and by resolving and invoking the other core Clojure functions you can load your own code. Based on your example it might look something like this:

const java = require('java');
var clojure = java.import('clojure.java.api.Clojure');
IFn require = clojure.var("clojure.core", "require");
require.invoke(clojure.read("my.namespace"));
IFn doSomething = clojure.var("my.namespace","dosomething");
//doSomething.invoke(....

If you do control the Clojure, :gen-class allows you to export functions as methods of the namespace's generated class.

Magos
  • 3,004
  • 1
  • 20
  • 20
  • Thanks, this is a good starting point. I actually do have control over the software, but a former member the team wrote it and I don't have access to him about it right now. – Sir Robert Aug 04 '17 at 16:35
0

Note: I arrived at this through a combination of Magos's answer and clartaq's comment to the question.

Here are simple instructions for how to do it. Let's assume you have this (simple) clojure code:

(ns my.namespace
  "a trivial library"
  (:require [something.else :as other]))

(defn excite
  "make things more exciting"
  [mystr]
  (print-str mystr "!"))

Use these steps to expose the excite method.

  1. Create an exposed version of the method with the same signature by prefixing it with -. It should simply call the function you wish to expose.

    (defn -excite [mystr] (excite mystr))
    
  2. Declare in (ns ...) that you want to generate a class and export methods.

    (ns my.namespace
      "a trivial library"
      (:require [something.else :as other])
      (:gen-class
        :name my.classname
        :methods [
          ;   metadata      mtd.name signature  returns
          #^{:static true} [excite   [String]   void]
        ]))
    

    Note that you may optionally remove the #^{:static true} if you do not wish to provide this as a static method.

  3. In your project.clj (assuming you are using leiningen), add ahead-of-time compilation instructions to your :profiles entry:

    :profiles {:uberjar {:aot :all}}
    
  4. Compile your uberjar:

    lein uberjar
    

The resulting .jar file will have a class my.classname with a static method excite.

Sir Robert
  • 4,686
  • 7
  • 41
  • 57