waechtertroll
waechtertroll

Reputation: 617

Clojure: gen-class and import it in java; packages, paths, classpaths

I want to call Clojure code compiled to a class from Java.

Clojure class:

(ns clj.core)

(gen-class
 :name de.wt.TestClj
 :state state
 :init init
 :prefix "-"
 :main false
 :methods [[setValue [String] void]
           [getValue [] String]])

(defn -init []
  [[] (atom {:value ""})])

(defn -setValue [this val]
  (swap! (.state this) assoc :value val))

(defn -getValue [this]
  (@(.state this) :value))

Compiled with lein uberjar and copied the output -standalone.jar into the Java project under subdirs de/wt.

The .java file:

import de.wt.TestClj;

class CljTest {
    public static void main(String args[]) {
        TestClj test = new TestClj();
        System.out.println("Pre: " + test.getValue());
        test.setValue("foo");
        System.out.println("Post: " + test.getValue());
    }
}

Now when I try to compile

~/testbed/cljava/java % tree                                                                 [1 14:44:53]
.
├── CljTest.java
├── de
│   └── wt
│       └── TestClj.jar
└── TestClj.jar

2 directories, 3 files
~/testbed/cljava/java % javac -cp ./:./de/wt/TestClj.jar CljTest.java                        

CljTest.java:1: error: package de.wt does not exist
import de.wt.TestClj;
^
CljTest.java:5: error: cannot find symbol
TestClj test = new TestClj();
^
symbol:   class TestClj
location: class CljTest
CljTest.java:5: error: cannot find symbol
TestClj test = new TestClj();
^
symbol:   class TestClj
location: class CljTest
3 errors

What scheme do I need to follow when naming files/packages, creating directories and setting class paths?

Upvotes: 1

Views: 597

Answers (1)

jas
jas

Reputation: 10865

You should be able to see the java class in your jar, something like:

 $ jar tvf target/default-0.1.0-SNAPSHOT-standalone.jar | grep TestClj
 2090 Mon Jan 01 18:43:12 CET 2018 de/wt/TestClj.class

If not, make sure you have an :aot (ahead-of-time compilation) directive in your project.clj:

(defproject default "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
        :url "http://www.eclipse.org/legal/epl-v10.html"}
  :aot [clj.core]
  :dependencies [[org.clojure/clojure "1.7.0"]])

Once you see the .class file in your jar, with the jar in your classpath the import you have in your Java code should work fine.

As noted in the docs (https://clojure.org/reference/compilation):

Clojure compiles all code you load on-the-fly into JVM bytecode, but sometimes it is advantageous to compile ahead-of-time (AOT). Some reasons to use AOT compilation are:

To deliver your application without source

To speed up application startup

To generate named classes for use by Java

To create an application that does not need runtime bytecode generation and custom classloaders

Also see http://clojure-doc.org/articles/language/interop.html:

AOT

gen-class requires ahead-of-time (AOT) compilation. It means that before using the classes defined with gen-class, the Clojure compiler needs to produce .class files from gen-class definitions.

Upvotes: 2

Related Questions