3

i created a sample polyglot program. i have a sensor and a robot implemented in java and AI implemented in clojure. and i can't connect maven properly

--src/main/java/clojuretest
                          |
                           DistanceSensor.java
                           AI.clj       (uses DistanceSensor)
                           Robot.java   (uses AI)

DistanceSensor.java:

package clojuretest;

public class DistanceSensor {

    public int getValue() {return 5;}
}

AI.clj:

(ns clojuretest.AI
  (:gen-class :methods [[isObstacleAhead [] boolean]]))

(defn -isObstacleAhead [this] (< (.getValue (clojuretest.DistanceSensor.)) 10))

Robot.java:

package clojuretest;

public class Robot {

    public boolean shouldStop() {
        return new AI().isObstacleAhead();
    }
}

i can even manually force maven to compile it: mvn clean clojure:compile produces error - no DistanceSensor class (but for some reason creates AI.class). so then mvn compile sees AI.class and compiles everything correctly and tests pass. but what can i do to make mvn clean compile pass? how should my pom.xml look like? also what can i do to make eclipse stop complaining about non existing AI.class?

piotrek
  • 13,982
  • 13
  • 79
  • 165

3 Answers3

2

You need to change layout of source code in your project. Clojure maven plugin requires, that clojure code went to separate directory, so you should have following layout:

 src/
    main/
      java/
        java-code
      clojure/
        clojure code
    test/
      java/
        java tests code
      clojure/
        clojure tests code

More details you can find in following article

Alex Ott
  • 80,552
  • 8
  • 87
  • 132
  • it's not the problem. i set clojure-maven-plugin sources to src/main/java. but even after changing the structure as you suggested, i still have the same error. when clojure:compile is done first then no DistanceSensor.class is found. if java compile is done first then no AI.class is found – piotrek Jan 01 '13 at 14:06
  • I'm sorry, incorrectly read your question. Please, see my comments to Sergiu's answer – Alex Ott Jan 01 '13 at 19:56
1

You have an inter-dependency between Java code and Clojure code. No matter which type of classes you'll compile first, you'll get an error.

Since it's not an actual cyclic dependency, you can still fix this by splitting the Java compilation part in two.

First, compile DistanceSensor, which doesn't depend on anything else.

Second, compile AI, which depends on DistanceSensor.

Finally, compile Robot which depends on AI.

To split the java compilation in two steps, you need to configure the default execution of the maven-compiler-plugin so that it excludes Robot, and add another execution after the clujure:compile goal that excludes DistanceSensor. You'll probably have to misuse the phases to properly order the three executions.

Sergiu Dumitriu
  • 11,455
  • 3
  • 39
  • 62
  • Maybe it would be possible to use maven-antrun-plugin to compile DistanceSensor first, during `generate-sources` phase, and then compile the rest. – Alex Ott Jan 01 '13 at 19:53
  • Another, maybe possible solution, is to split project into submodules, and use provide corresponding ordering of executions. In Leiningen it's possible to use hooks to perform some tasks before other, and I'm not sure about such possibility in Maven (although, there is Invoker plugin that is able to run some script before and/or after phases – Alex Ott Jan 01 '13 at 19:56
  • but then i would have to exclude also all classes that depends on Robot. what is more, there could be many layers of java calling clojure and vice versa. and i'm afraid there is no point in using automated build system as everything has to be done manually. java can compile classes even if there are cycles between them. can't lein do the same with java and clojure somehow? – piotrek Jan 01 '13 at 20:02
  • Indeed, splitting the project in modules is the better approach. If you're mixing languages just for the sake of mixing languages, then you're not doing something useful. If, however, you do need/want to write different parts of the program in different languages (_use the best tool for each job_), then you should properly think about how your project should be organized, and it should be possible to have separate layers without interdependencies between them. – Sergiu Dumitriu Jan 01 '13 at 20:10
  • 1
    One option is to separate interfaces from actual implementations, so that interfaces don't depend on the upper layers, and the actual implementations only depend on other interfaces. This way, the interdependencies are hidden and will only surface at runtime, when all the classes are compiled. – Sergiu Dumitriu Jan 01 '13 at 20:12
1

I think :gen-class is usually a code smell, as is trying to instantiate such a class from Java code with new AI().

Here's an alternative approach that can solve this problem of cyclic dependencies:

  1. Define AI as a Java interface in your Java code
  2. Write a Clojure function to create an instance conforming to the interface using reify
  3. Dynamically invoke the Clojure function from Java (e.g. using the technique outlined in this blog post)
  4. You now have an instance of the AI interface that you can use however you like in Java

The advantage is this approach is that everything will work smoothly, in particular:

  • The Java code base can be compiled independently of the Clojure code
  • The Clojure code base can access all the defined Java classes and interfaces
  • You don't need any special IDE / Maven config. In fact, you can treat is as just a regular Java app that happens to include clojure.jar as a dependency.
mikera
  • 105,238
  • 25
  • 256
  • 415