Search code examples
clojureclojure-java-interop

clojure gen-class returning own class


I'm now making a class object with Clojure which has a method returning the object itself.

Written with Java, the object that I'd like to make is like,

class Point {
    public double x;
    public double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public Point copy() {
        return new Point(this.x, this.y);
    }
}

The current clojure code that I wrote is like,

(ns myclass.Point
  :gen-class
  :prefix "point-"
  :init init
  :state state
  :constructors {[double double] []}
  :methods [[copy [] myclass.Point]]))

(defn point-init [x y]
   [[] {:x x :y y}])

(defn point-copy [this]
   this)

However, I got an error as follows.

java.lang.ClassNotFoundException: myclass.Point

While I have googled about this issue, I couldn't find any answers. Does anyone know the solution for this issue?

Thank you in advance for your help.


Solution

  • I'm not sure it's the only way, but, in order to use the generated class type in a method signature, you can generate the class in 2 steps - though it's still in one file and compiled in one pass:

    • call gen-class with only the constructors
    • call gen-class again with state, full set of constructors and methods.

    I tried with various methods including forward declaration, but only the above was working eventually. I did extend your example a little bit. Note That the copy method is not very useful as-is since Point is immutable, but you may want to provide mutators to your class.

    Full listing:

    (ns points.Point)
    
    ;; generate a simple class with the constructors used in the copy method
    (gen-class
     :name points.Point
     :init init
     :constructors {[] []
                    [double double] []})
    
    ;; generate the full class 
    (gen-class
     :name points.Point
     :prefix pt-
     :main true
     :state coordinates
     :init init
     :constructors {[] []
                    [double double] []}
     :methods [[distance [points.Point] double]
               [copy [] points.Point]])
    
    (defn pt-init
      ([] (pt-init 0 0))
      ([x y]
       [[] {:x x :y y}]))
    
    (defn pt-copy 
      "Return a copy of this point"
      [this]
      (points.Point. (:x (.coordinates this)) (:y (.coordinates this))))
    
    (defn pt-distance [^points.Point this ^points.Point p]
      (let [dx (- (:x (.coordinates this)) (:x (.coordinates p)))
            dy (- (:y (.coordinates this)) (:y (.coordinates p)))]
        (Math/sqrt (+ (* dx dx) (* dy dy)))))
    
    (defn pt-toString [this]
      (str "Point: " (.coordinates this)))
    
    ;; Testing Java constructors and method call on Point class
    (import (points Point))
    (defn pt-main []
      (let [o (Point.)
            p (points.Point. 3 4)]
        (println (.toString o))
        (println (.toString p))
        (println (.distance o p))
        (println (.distance p (.copy p)))))
    

    In order to generate the classes, configure project.clj with the line

    :aot [points.Point]
    

    Testing with lein gives:

    tgo$ lein clean
    tgo$ lein compile
    Compiling points.Point
    tgo$ lein run
    Point: {:x 0, :y 0}
    Point: {:x 3.0, :y 4.0}
    5.0
    0.0