Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Access user created Java package from Clojure

132 views
Skip to first unread message

Michael Rice

unread,
May 16, 2016, 10:51:43 AM5/16/16
to Clojure
I'm working through "Clojure for the Brave and True." Right now I'm focused on Java interop, in particular creating a simple Java package and accessing it from Clojure. My applicable directory structure is listed below along with some code, examples of it executing, and my classpath.

For starters, two questions:

1) Is it possible to run PirateConversation from Clojure, and if so, how?

2) If I move my pirate_phrases directory from phrasebook directory to my HOME directory (on my classpath), how do I change the import statement in PirateConversation.java to access it for compilation?

Michael

----------------------  

Home
  phrasebook
    pirate_phrases
      Greetings.class
      Farewells.class
    PirateConversation.class

PirateConversation.java:

import pirate_phrases.*;

public class PirateConversation
{
  public static void main(String[] args)
  {
    Greetings greetings = new Greetings();
    greetings.hello();

    Farewells farewells = new Farewells();
    farewells.goodbye();
  }
}

[mrice@localhost phrasebook]$ java PirateConversation
Shiver me timbers!!!
A fair turn of the tide for ye thar, ye magnificent sea friend!!
[mrice@localhost phrasebook]$
[mrice@localhost phrasebook]$ cd
[mrice@localhost ~]$ java PirateConversation
Shiver me timbers!!!
A fair turn of the tide for ye thar, ye magnificent sea friend!!
[mrice@localhost ~]$ echo $CLASSPATH
.:/home/mrice/myclasses:/home/mrice/phrasebook
    

Gary Verhaegen

unread,
Jun 1, 2016, 7:06:08 PM6/1/16
to clo...@googlegroups.com
As long as you're working purely in Clojure, Leiningen mostly protects you from having to know about the JVM and its infamous classpath. But when you're trying to interoperate with Java code, you need to know a few things.

So, here's the very minimum I think you need to know to understand what is happening here. When you start a JVM, it has a notion of a classpath. This can come from your environment variables, but usually does not, especially when your code is started by Leiningen, so printing out your $CLASSPATH variable would not help you there. There is a `lein classpath` command that can help you see what's happening.

The classpath is a series of locations (mostly directories, but also often JAR files) in which the JVM is going to look for code. When you give a fully-qualified Java class name, the JVM will look for the equivalent path into every directory of the classpath. So if your classpath is /dir1:/dir2/code, for example, and your code is referencing my.package.MyClass, the JVM will look for /dir1/my/package/MyClass.class, and if that does not exist, it will then look for /dir2/code/my/package/MyClass.class.

Where your files are on your disk does not matter; what matters is where they are relative to your classpath root folders.

Note that your home directory is *not* in your $CLASSPATH in your example (the current directory is).

Now, as to accessing these from Clojure:

$ cd $(mktemp -d)
$ lein new app t
Generating a project called t based on the 'app' template.
$ cd t
$ vim project.clj
$ vim src/MyClass.java
$ cat project.clj
(defproject t "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"}
  :dependencies [[org.clojure/clojure "1.8.0"]]
  :main ^:skip-aot t.core
  :target-path "target/%s"
  :java-source-paths ["src"]
  :profiles {:uberjar {:aot :all}})
$ cat src/MyClass.java
public class MyClass {
    public static void hello() {
        System.out.println("hello");
    }
}
$ vim src/t/core.clj
$ cat src/t/core.clj
(ns t.core
  (:import [MyClass])
  (:gen-class))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (MyClass/hello))
$ lein run
Compiling 1 source files to target/base+system+user+dev/classes
hello
$ tree target
target
├── base+system+user+dev
│   ├── classes
│   │   ├── META-INF
│   │   │   └── maven
│   │   │       └── t
│   │   │           └── t
│   │   │               └── pom.properties
│   │   └── MyClass.class
│   └── stale
│       └── leiningen.core.classpath.extract-native-dependencies
├── base+system+user+dev+b9e56754
│   └── stale
│       └── leiningen.core.classpath.extract-native-dependencies
└── base+system+user+dev+offline
    └── stale
        └── leiningen.core.classpath.extract-native-dependencies

11 directories, 5 files
$ lein classpath
test:src:dev-resources:resources:target/base+system+user+dev/classes:~/.m2/repository/cider/cider-nrepl/0.10.2/cider-nrepl-0.10.2.jar:~/.m2/repository/org/tcrawley/dynapath/0.2.3/dynapath-0.2.3.jar:~/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar:~/.m2/repository/org/clojure/tools.nrepl/0.2.12/tools.nrepl-0.2.12.jar:~/.m2/repository/clojure-complete/clojure-complete/0.2.4/clojure-complete-0.2.4.jar
$

In this case, Leiningen is arranging for the MyClass.java file to be compiled to MyClass.class, and for the result to be available in the classpath of the running Clojure program. So, really, all you have to do to access your Java code from Clojure is make sure it is on the classpath and import it. One easy way to put existing, compiled .class files in the classpath generated from Leiningen is to put them in resources, though it would be better to either have the Java code as part of the project (in :java-source-paths) or to package it as a Maven dependency.

I should mention, though, that using the default package (i.e. no package declaration, i.e. having your java code directly at one of the roots of the classpath) like that is generally considered bad form, as much in Java as in Clojure, partly due to the risk of name clashes and partly because some tooling just does not support it.

So, in summary:

1) Yes, as long as the .class files are on your classpath. You can also call (import '[MyClass]) from the REPL if you are not running code from a namespace.
2) It does not matter where on your disk these files are; your current import statement is correct for any XXX/pirate_phrases/ directory as long as XXX itself is on the classpath.


Hope that helped.
Reply all
Reply to author
Forward
0 new messages