Currently I'm trying to create sample Wicket page with Clojure (in existing wicket project). Code looks like this:
(ns a.set.of.packages.dataview.info.EmptyNodeInfo2Panel
(:import [a.set.of.packages.tree TreeModelBean]
[a.set.of.packages.dataview.supplemental GenericHeaderPanel]))
(gen-class
:name a.set.of.packages.dataview.info.EmptyNodeInfo2Panel
:extends org.apache.wicket.markup.html.panel.Panel
:state state
:init init
:constructors {[String a.set.of.packages.tree.TreeModelBean] [String]}
:post-init construct)
(defn -init [id model-bean]
[[id] nil])
(defn -construct [this id model-bean]
(.add this (GenericHeaderPanel. "header" model-bean)))
When page being created I get following runtime error:
java.lang.IllegalArgumentException: No matching method found: add for class a.set.of.packages.dataview.info.EmptyNodeInfo2Panel
i.e. I can't call to the superclass methods.
Decompiled construct method looks like this:
public Object invoke(Object this, Object id, Object model_bean)
throws Exception
{
this = null;
id = null;
model_bean = null;
return Reflector.invokeInstanceMethod(this, "add", new Object[] {
new GenericHeaderPanel((String)"header", (TreeModelBean)model_bean)
});
}
Where everything is set to null! Is this problem with constructor parameters mapping, incorrect decompilation or a bug?
I've dug in Clojure internals and found root of the problem. Marked row doesn't work.
// clojure.lang.Reflector
static public boolean paramArgTypeMatch(Class paramType, Class argType)
{
...
if(paramType == argType || paramType.isAssignableFrom(argType)) // <<<
return true;
...
Probably there is class loader problem (I create EmptyNodeInfo2Panel class dynamically with Class.forName).
Classes that was matched are:
a.set.of.packages.dataview.supplemental.GenericHeaderPanel
org.apache.wicket.Component;
Interesting thing: when I print org.apache.wicket.Component class name from servlet respnose processing thread it prints as is, but when I print it from Reflector thead (through paramType.getName()), it prints in following form: [Lorg.apache.wicket.Component.
I use Tomcat 5.5, are there any clues possible?
Related
There is a standard function clojure.core/bean converting POJO to map:
class MyPojo{
public String getFirst(){ return "abc"; }
public int getSecond(){ return 15; }
}
IFn bean = Clojure.var("clojure.core", "bean")
var result = bean.invoke(new MyPojo())
// result => {:first = abc, :second = 15}
For Java 17 record classes this function would not work, because records do not follow POJO convention "get***" for properties.
Is there Clojure support for Java 17 record instances in the same manner?
Java 16 introduces Class.getRecordComponents. So given an instance of a record, you can look up the record's class, and from there its record components. Each record component has a name and a getter Method, which you can use to look up the value of that component. You can put these pieces together to build an analogue of bean.
(defn record->map [r]
(into {} (for [^java.lang.reflect.RecordComponent c (seq (.getRecordComponents (class r)))]
[(keyword (.getName c))
(.invoke (.getAccessor c) r nil)])))
Hi Currently I am using java.data (https://github.com/clojure/java.data) to convert java pojos to clojure compatible types.
It does not work for nested objects.
For ex:
class Abc {
Map<String, Def> someMap;
}
Class Def {
String b;
}
If I pass sample instance of Abc to java.data, I get the output as:
{
:someMap {
"keyString" #object[com.sample.Def 0xb33584d "com.sample.Def#b33584d"]
}
}
But I want the output as:
{
:someMap {
"keyString" {
"b" "value"
}
}
}
How can I fix this?
I tried clojure.core bean (https://clojuredocs.org/clojure.core/bean) and it dint seem to work as well.
Thank you in advance.
In order for this to work, your Java objects need to conform to the JavaBean specification. This means they need methods .getXXX() to read object properties (at least), and also .setXXX() to construct a new object. Example:
Class Inner:
package demo;
public class Inner {
public String secret;
public String getSecret() {
return secret;
}
public Inner(String arg) {
this.secret = arg;
}
}
Class Outer:
package demo;
import java.util.HashMap;
import demo.Inner;
public class Outer {
public HashMap<String, Inner> someMap;
public Outer() {
HashMap<String,Inner> hm = new HashMap<String, Inner>();
hm.put("stuff", new Inner( "happens"));
hm.put("another", new Inner( "thing"));
this.someMap = hm;
}
public HashMap getSomeMap() { return someMap; }
}
and Clojure code to decode the nested JavaBean objects:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[clojure.java.data :as jd])
(:import [demo Calc]))
(dotest
(let [java-obj (Outer.)
obj-shallow (jd/from-java java-obj)
obj-deep (jd/from-java-deep java-obj {})]
(spyx java-obj)
(spyx obj-shallow)
(spyx-pretty obj-deep)
))
The results show what happens:
--------------------------------------
Clojure 1.10.2-alpha1 Java 14
--------------------------------------
lein test tst.demo.core
java-obj => #object[demo.Outer 0x138d8219 "demo.Outer#138d8219"]
obj-shallow => {:someMap {"another" #object[demo.Inner 0x8d86c4d "demo.Inner#8d86c4d"], "stuff" #object[demo.Inner 0x28c92c51 "demo.Inner#28c92c51"]}}
obj-deep => {:someMap {"another" {:secret "thing"},
"stuff" {:secret "happens"}}}
The raw java-obj is opaque to Clojure. Using jd/from-java only unpacks the outer layer using the JavaBean getters. Using jd/from-java-deep (notice the required options map, left empty here) will recursively unpack the JavaBean using the appropriate getters on each object based on its java class.
All of the above code is based on this template project. Enjoy!
Clojure's built-in bean function only does a shallow conversion -- it doesn't recursively convert any nested information. Check out https://github.com/clojure/java.data for a recursive conversion from Java objects to Clojure data structures (and back).
java.data also support "builder"-style Java APIs so you can build Java objects more easily from Clojure.
I am doing a library in ClojureScript that will expose a public JavaScript API. Since it has to mimic the API of an existing JavaScript library, I would like to present the same kind of fluent API :
myLib.prepareToDo()
.something()
.and('also')
.somethingElse()
.run(function(err, result) {
console.log("yay!");
});
In Javascript, one could create a fluent API like this site point):
var MyClass = function(a) {
this.a = a;
}
MyClass.prototype.foo = function(b) {
// Do some complex work
this.a += Math.cos(b);
return this;
}
I can then call it like :
var obj = new MyClass(5);
obj.foo(1).foo(2).foo(3);
Now, as far as I know, there is no notion of this in ClojureScript, although apparently it is possible to access it this-as.
I don't get how to use it though, hence my question.
How can I create a fluent interface in ClojureScript ?
(defrecord) and this answer to the rescue. Extending the "magic" protocol Object to our record or type causes the defined methods to appear as member functions in the JavaScript object. To enable the "fluent interface", some methods return an instance of MyClass.
(defrecord MyClass [a b]
Object
(something [this] this)
(and-then [this s] (assoc this :a s))
(something-else [this] (assoc this :b (str a "-" a)))
(run [this f] (f a b)))
Then we can have a JavaScript client like so:
var myClass = new my_namespace.core.MyClass();
myClass.something()
.and_then("bar")
.something_else()
.run(function(a, b) {
console.log(a + " - " + b) });
I want to load Java class using URLClassLoader and invoke method main. The code below gives an error at the last line and I can not figure out how to fix it.
(def classloader (URLClassLoader. (into-array files)))
(def classname "example.Test")
(def clazz (.. classloader (loadClass classname)))
; I assume not the most effective way to get method "main"
; additional feedback appreciated
(def method (first (filter #(= (. % getName) "main") (. clazz getMethods))))
(def args (make-array String 1))
; specify single command line argument for method main(String[]args)
(aset args 0 "my-file")
; attempt to invoke static method using Java reflection API fails.
(. method invoke nil args)
Exception in thread "main" java.lang.IllegalArgumentException:
argument type mismatch, compiling:
Please note that the class is not otherwise available in the default classloader, so
(example.Test/main "myfile")
would fail.
Thank you,
Pavel
You are confusing the signature of invoke with the signature of the main method.
The signature of "main" is (String[] args1)
The signature of "invoke" is (Object obj, Object... args2)
args2 should be an array with one item per each param in the main method, so it should be an array of 1 element, the element being the String[]. So:
(def invoke-args (into-array Object [args]))
(. method invoke nil invoke-args)
About a simpler way of finding "main" look at http://docs.oracle.com/javase/7/docs/api/java/lang/Class.html#getMethod(java.lang.String,%20java.lang.Class...)
I summarized the result of the discussion below. (Thanks to dAni for help).
(let
[classname "example.Test"
clazz (.. classloader (loadClass classname))
method (. clazz getMethod "main" (into-array Class [(class (into-array String []))]))
args (make-array String 1)]
(aset args 0 "my-file")
(. method invoke nil (into-array Object [args])))
I am trying to get javafx2 working with Clojure - In implementing an abstract class such as DoubleBinding, I am unsure what the equivalent of super.bind(moo) is in Clojure. The class I am implementing can be found here: http://docs.oracle.com/javafx/2/api/index.html.
(def moo (ObservableDoubleValue. ...))
(def foo (proxy [DoubleBinding] []
(computeValue []
(Math/sqrt (.getValue moo)))))
final ObservableDoubleValue moo = ...;
DoubleBinding foo = new DoubleBinding() {
{
super.bind(moo);
}
#Override
protected double computeValue() {
return Math.sqrt(moo.getValue());
}
};
According to proxy documentation, methods in proxy has no access to super... I would recommend you to generate class using gen-class and use it. You can access to super's methods if you'll expose them with :exposes-methods directive. Something, like:
(gen-class :name MyDoubleBinding
:extends DoubleBinding
:exposes-methods {bind my-bind}
....
)
and then call -my-bind from your constructor...
Please, check documentation about class generation on Clojure's site for more details on gen-class