Extending with :gen-class a class that exposes its naked fields - clojure

Suppose there is a Java class that doesn't provide getters and setters for all its fields, and I have to extend it with :gen-class and fiddle with them.
How do I access the superclass fields?
The quickest (and perhaps cleanest...) solution that comes to my mind right now is to create a java class that extends my super class, and extend it instead, but I was wondering if there is an alternative that sounds more direct.
Thanks!

The methods in generated classes can access base class fields with the help of the :exposes option of gen-class. :exposes expects a map where keys are symbols matching base class field names; values are also maps like {:get getterName, :set setterName}. Clojure generates those getter and setter methods automatically. They can be used to read and modify base class fields. This is documented in the docstring for gen-class.
This approach works for public and protected fields. It does not work for private fields.
Assuming the Java base class like this:
package fields;
class Base {
public String baseField = "base";
}
The Clojure code to generate a subclass would be:
(ns fields.core
(:gen-class
:extends fields.Base
:methods [[bar [] String]
[baz [String] Object]]
:exposes { baseField { :get getField :set setField }}))
(defn -bar [this]
(str (.getField this) "-sub"))
(defn -baz [this val]
(.setField this val)
this)
(defn -main
[& args]
(println (.. (fields.core.) (bar)))
(println (.. (fields.core.) (baz "new-base") (bar))))
Assuming all this is AOT compiled and ran, the output is:
base-sub
new-base-sub

I was having a bit of trouble understanding all of the details and decided to try out a minimal version. Here is a file listing:
> d **/*.{clj,java}
-rw-rw-r-- 1 alan alan 501 Jun 29 17:11 project.clj
-rw-rw-r-- 1 alan alan 431 Jun 29 17:10 src/demo/core.clj
-rw-rw-r-- 1 alan alan 63 Jun 29 16:57 src-java/jpak/Base.java
Here is the project.clj
(defproject demo "0.1.0-SNAPSHOT"
:description "demo code"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [
[org.clojure/clojure "1.8.0"]
[tupelo "0.9.55"]
]
:profiles {:dev {:dependencies [ [org.clojure/test.check "0.9.0"] ] }
:uberjar {:aot :all} }
:java-source-paths ["src-java"]
:aot [ demo.core ]
:main ^:skip-aot demo.core
:target-path "target/%s"
jvm-opts ["-Xms500m" "-Xmx500m" ]
)
and the Java class:
package jpak;
public class Base {
public long answer = 41;
}
and our Clojure code:
(ns demo.core
(:gen-class
:extends jpak.Base
:exposes {answer {:get getans :set setans}}
))
(defn -main
[& args]
(let [sub-obj (demo.core.) ; default name of subclass
old-answer (.getans sub-obj)
>> (.setans sub-obj (inc old-answer))
new-answer (.getans sub-obj) ]
(println "old answer = " old-answer)
(println "new answer = " new-answer)
))
We can run using lein run to get:
> lein run
old answer = 41
new answer = 42
Version 2
The above continues to work if the Java variable answer is protected, but fails if it is either private or "package protected" (no qualifier). This makes sense since our subclass is in a different package.
Also, it is a little cleaner if I give the subclass a name different than the default value, which is the clojure namespace name "demo.core":
(ns demo.core
(:gen-class
:name demo.Sub
:extends jpak.Base
:exposes {answer {:get getans :set setans}}
))
(defn -main
[& args]
(let [sub-obj (demo.Sub.) ; new name of subclass
old-answer (.getans sub-obj)
>> (.setans sub-obj (inc old-answer))
new-answer (.getans sub-obj)
]
(println "old answer = " old-answer)
(println "new answer = " new-answer)
))
Version 3: Access private member values
In Java, a subclass cannot normally see private member variables of a superclass; "package protected" members from a different package are also restricted. Here is the pesky Java class:
package jpak;
public class Base {
private long answer = 41;
}
However, Java has a well-known ability to override private access restrictions, and you don't even need a subclass! All you need to do is use reflection. Here is the clojure version:
(ns demo.break
(:import [jpak Base]))
(defn -main
[& args]
(let [base-obj (Base.)
class-obj (.getClass base-obj)
ans-field (.getDeclaredField class-obj "answer")
>> (.setAccessible ans-field true)
old-answer (.get ans-field base-obj)
>> (.set ans-field base-obj 42)
new-answer (.get ans-field base-obj)
]
(println "old answer = " old-answer)
(println "new answer = " new-answer)))
> lein run -m demo.break
old answer = 41
new answer = 42
See the docs for AccessibleObject here. Note that Field & Method, which are the classes returned during reflection, are both included.

Related

Getting the namespace where a form is called

I would like a macro this-ns such that it returns the namespace of the location where it is being called. For instance, if I have this code
(ns nstest.main
(:require [nstest.core :as nstest]))
(defn ns-str [x]
(-> x (.getName) name))
(defn -main [& args]
(println "The ns according to *ns*:" (ns-str *ns*))
(println "The actual ns:" (ns-str (nstest/this-ns))))
I would expect that calling lein run would produce this output:
The ns according to *ns*: user
The actual ns: nstest.main
What I came up with as implementation was the following code:
(ns nstest.core)
(defmacro this-ns []
(let [s (gensym)]
`(do (def ~s)
(-> (var ~s)
(.ns)))))
It does seem to work, but it feels very hacky. Notably, in the above example it will expand to def being invoked inside the -main function which does not feel very clean.
My question: Is there a better way to implement this-ns to obtain the namespace where this-ns is called?
here is one more variant:
(defmacro this-ns []
`(->> (fn []) str (re-find #"^.*?(?=\$|$)") symbol find-ns))
the thing is the anonymous function is compiled to a class named something like
playground.core$_main$fn__181#27a0a5a2, so it starts with the name of the actual namespace the function gets compiled in.
Can't say it looks any less hacky, then your variant, still it avoids the side effect, introduced by def in your case.
Interesting question. I would never have guessed that your code would output user for the first println statement.
The problem is that only the Clojure compiler knows the name of an NS, and that is only when a source file is being compiled. This information is lost before any functions in the NS are called at runtime. That is why we get user from the code: apparently lein calls demo.core/-main from the user ns.
The only way to save the NS information so it is accessible at runtime (vs compile time) is to force an addition to the NS under a known name, as you did with your def in the macro. This is similar to Sean's trick (from Carcingenicate's link):
(def ^:private my-ns *ns*) ; need to paste this into *each* ns
The only other approach I could think of was to somehow get the Java call stack, so we could find out who called our "get-ns" function. Of course, Java provides a simple way to examine the call stack:
(ns demo.core
(:use tupelo.core)
(:require
[clojure.string :as str]))
(defn caller-ns-func []
(let [ex (RuntimeException. "dummy")
st (.getStackTrace ex)
class-names (mapv #(.getClassName %) st)
class-name-this (first class-names)
class-name-caller (first
(drop-while #(= class-name-this %)
class-names))
; class-name-caller is like "tst.demo.core$funky"
[ns-name fn-name] (str/split class-name-caller #"\$")]
(vals->map ns-name fn-name)))
and usage:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[clojure.string :as str]
[demo.core :as core]))
(defn funky [& args]
(spyx (core/caller-ns-func)))
(dotest
(funky))
with result:
(core/caller-ns-func) => {:ns-name "tst.demo.core", :fn-name "funky"}
And we didn't even need a macro!

ClassNotFoundException when trying to use class created using gen-class

My code:
(ns model.document
(:gen-class
:name model.document
:implements java.io.Serializable
:state "state"
:init "init"
:constructors {[String String String] []}
:methods [[getContent [] String]
[getTitle [] String]
[getUrl [] String]]))
(defn -init [content title url]
[[] (atom {:content content
:title title
:url url})])
(defn- get-field [this k]
(#(.state this) k))
(defn getContent [this]
(get-field this :content))
(defn getTitle [this]
(get-field this :title))
(defn getUrl [this]
(get-field this :url))
And it's use:
(ns classification-server.classifier
(:require [model.document :refer :all]))
(new model.document "my-content" "my-title" "my-url")
And I get the unhelpful:
Caused by: java.lang.ClassNotFoundException: model.document, compiling:(classification_server/classifier.clj:13:12)
Please help me SO. You're my only hope...
The gen-class namespace you posted doesn’t compile, because the :implements specification expects a vector of symbols, not a symbol. If you change that line to
:implements [java.io.Serializable]
you will be able to compile (and instantiate) the class – however, it will not be functional, because there are other issues with your gen-class spec,such as the prefixed functions being absent (-getContent etc.).
I suggest you read the gen-class documentation and simplify the
problem further.

ClassNotFoundException: clojure.algo.monads

I'm trying require clojure.algo.monads, I'm a little confused with clojure import/require/use
; at lein...
:main monads.core/-main
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/algo.monads "0.1.5"]])
install deps: lein deps
and this simpele code to test
(ns monads.core
(require clojure.algo.monads))
(defn -main [& args]
(clojure.algo.monads)
(println "Hello, World!"))
lein run
Exception in thread "main" java.lang.ClassNotFoundException: clojure.algo.monads, compiling:(monads/core.clj:6:3)
at clojure.lang.Compiler.analyze(Compiler.java:6464)
at clojure.lang.Compiler.analyze(Compiler.java:6406)
at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:3665)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6646)
...
I'm doing something wrong?
The expression (clojure.algo.monads) in your -main function is considered a call to clojure.algo.monads, which should be either a special form, a macro, or a function. However, clojure.algo.monads is a namespace. There is no Java class file corresponding to it. This results in ClassNotFoundException.
I suggest that you require the clojure.algo.monads namespace and refer only limited functions or macros that you want to use. Here is an example.
user> (require '[clojure.algo.monads :refer [domonad maybe-m]])
nil
user> (defn f
[x]
(domonad maybe-m
[a x
b (inc x)]
(* a b)))
#'user/f
user> (f 3)
12
user> (f nil)
nil

clojure gen-class generated classes invocation issue

I defined the following MyCache.clj
(ns abcd.MyCache
(:gen-class
:name "abcd.MyCache"
:init "init"
:constructors { [java.text.DateFormat][] }
:methods [ [now [] void] [myformat [long] String] ]
:state "state"
:main false))
(defn -init[format]
([[] (atom {:format format})]))
(defn -now[this] ( (:format #(.state this)) (System/currentTimeMillis)))
(defn -myformat[this time]
( (:format #(.state this) (new java.util.Date time))))
I compiled the above file using (compile 'abcd.MyCache) successfully.
When I am trying to use the generated classes as shown below..I am getting errors. Please help.
user=> (new abcd.MyCache (new java.text.SimpleDateFormat "mmDDyyyy"))
IllegalArgumentException Key must be integer clojure.lang.APersistentVector.invoke (APersistentVector.java:265)
I don't feel well about this:
(defn -init[format]
([] [atom {:format format}]))
You are trying to get an element from a vector and it is expects an index (number).
What is correct is to deref the atom and get its value as the index of the vector. But again in your case, you are trying to query an empty vector.
Notice also, that [atom {:format format}] isn't the correct way to create an atom. You should use:
(atom {:format format})
And by the way, the following form is the preferred one to create Java objects (nothing wrong with (new) of course):
(Date.)
(DateFormat.)

Determining Why Clojure REPL (load-file) and cake build yield error

Since Clojure 1.3, I am confused about the directory structure needed to build something in Clojure. I am using cake to build and cake repl.
Here is what works. I have a working build directory addr_verify. The main's and ns's name is addr-verify. The project.clj refers to addr-verify as main, and in addr_verify/src there is addr_verify.clj. The ns inside addr_verify.clj refers to the addr-verify name space.
Now, I had a directory mr1, but cake won't compile it right at line 1
(ns mr1
(use ['clojure.string :only '(split)])
(use ['clojure.string :only '(join)])
)
If mr1 is a bad name, what naming convention should I use?
I have tried mr1_app as a directory structure using mr1-app as the main name and ns name. I
For mr1 as the directory and ns name, I get
Caused by: clojure.lang.Compiler$CompilerException: java.lang.ClassCastException: clojure.lang.PersistentList cannot be cast to java.lang.Comparable, compiling:(mr1.clj:1)
I'm just not getting what I'm doing wrong here, and I know it's probably something really simple.
Edit:
Why does the binary mr1 not have a main?
mr1/project.clj
(defproject mr1 "0.0.1-SNAPSHOT"
:description "TODO: add summary of your project"
:dependencies [[org.clojure/clojure "1.3.0"]
[org.clojure/tools.cli "0.1.0"]]
:main mr1)
mr1/src/mr1.clj
(ns mr1
(:use [clojure.string :only [split]]
[clojure.string :only [join]]))
(def grid-dim (atom '(0 0)))
(def mr1-pos (atom '(0 0)))
(def mr2-pos (atom '(0 0)))
(defn cvt-str-to-int
[string]
(map #(Integer/parseInt %)
(split string #" ")))
(defn prompt-for-grid-dim
[]
(do
(println "Enter the dimensions of the grid (10 10) ")
(cvt-str-to-int (read-line))
))
(defn prompt-for-rover-pos
[rover-num]
(do
(println "Enter rover's initial position on the grid (2 4) ")
(cvt-str-to-int (read-line))
))
(defn prompt-for-rover-moves
[]
(do
(println "Enter rover's moves LMMRM ")
(read-line)
))
(defn -main
[& args]
(do
(reset! grid-dim (cvt-str-to-int (prompt-for-grid-dim)))
(reset! mr1-pos (cvt-str-to-int (prompt-for-rover-pos)))
)
)
Since the first part has already been answered, I will answer the part about :main.
As sample.project.clj file says, :main key should have as an assigned value a namespace which contains -main function. So you should have such function
(defn -main [& args]
(do-things-you-want-to-do-on-program-start))
in your mr1.clj. Also AFAIR if you want to use your program as a standalone jar you have to have this namespace gen-classed. By this I mean that you have to:
Include :gen-class option in your namespace definition like this:
(ns mr1
(:gen-class)
...other options...)
Make the namespace AOT-compiled (AOT stands for Ahead Of Time). To do this you need to specify your namespace in the list of AOT-compiled namespaces in project.clj:
(defproject mr1 "0.0.1-SNAPSHOT"
...other definitions...
:aot [mr1]
:main mr1)
After you've done this, you can use cake to generate executable jar for you.
ADD:
I think it is worth to note that you don't have to have a :main at all. If all you want to do is to run your program in repl or if you want to create a library, there can be no gen-classes namespaces (except if you want to interact with plain java code in such a way that java code can call your clojure code) and no :main namespace, which are required only for executable jars.
I think there is something wrong with the "syntax" of your namespace declaration. Instead, try this:
(ns mr1
(:use [clojure.string :only [split]]
[clojure.string :only [join]]))
Change your :main setting in project.clj accordingly: it should just be mr1, contrary to what I said earlier.
Edited according the comment of googolplex