Java's java.lang.Class class has a getDeclaredFields method which will return all the fields in a given class. Is there something similar for Common Lisp? I came across some helpful functions such as describe, inspect and symbol-plist after reading trying out the instructions in Successful Lisp, Chapter 10 (http://www.psg.com/~dlamkins/sl/chapter10.html). But none of them do what getDeclaredFields does.
You should use class-slots and/or class-direct-slots (both are from CLOS Metaobject Protocol, MOP). class-slots returns all slots that are present in given class, and class-direct-slots returns all slots are declared in class definition.
Different lisp implementations implement MOP slightly differently; use closer-mop package to have uniform interface to MOP.
Example:
(defclass foo ()
(foo-x))
(finalize-inheritance (find-class 'foo)) ;this is needed to be able to query class slots and other properties. Or, class is automatically finalized when its first instance is created
(class-slots (find-class 'foo))
=> (#<STANDARD-EFFECTIVE-SLOT-DEFINITION FOO-X>)
(slot-definition-name (first (class-slots (find-class 'foo))))
=> FOO-X
Example :
(defun inspect (( object standard-object))
(inspect-rec (class-slots (class-of object)) object) )
(defun inspect-rec (slots o)
( if(atom slots) ()
(let ((sn (slot-definition-name (car slots)))) (cons (list sn '=> ( slot-value o sn) ) ( inspect-rec (cdr slots) o)))))
I think you're looking for the MetaObject Protocol for CL.
Related
I'm working on a Clojure wrapper for some Java library.
In order to help me debug, I would like to log all calls to specific Java objects.
After searching how I might do this from a raw Java perspective, I discovered the java.lang.reflect.Proxy class and java.lang.reflect.InvocationHandler interfaces.
This led me to find a small snipped posted by R.H. a few years ago :
(defn debug-proxy [obj]
(java.lang.reflect.Proxy/newProxyInstance
(.. obj getClass getClassLoader)
(.. obj getClass getInterfaces)
(proxy [java.lang.reflect.InvocationHandler] []
(invoke [proxy m args]
(apply println m args)
(.invoke m obj args)))))
Small note: I had to change java.lang.reflect.Proxy.newProxyInstance into java.lang.reflect/newProxyInstance to make it work in my REPL, since newProxyInstance is a static method. Corrected version is given so that you may cut & paste it.
With this version, calling methods on the proxy appear work at first :
youpi.core=> (.charAt (debug-proxy "foo") 2)
#object[java.lang.reflect.Method 0x53ce1eb0 public abstract char java.lang.CharSequence.charAt(int)] 2
=> \o
But sadly, calling a method that accepts no argument will not work:
youpi.core=> (.toUpperCase (debug-proxy "foo"))
IllegalArgumentException No matching field found: toUpperCase for class com.sun.proxy.$Proxy1 clojure.lang.Reflector.getInstanceField (Reflector.java:271)
java.lang.IllegalArgumentException: No matching field found: toUpperCase for class com.sun.proxy.$Proxy1
at clojure.lang.Reflector.getInstanceField(Reflector.java:271)
at clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:315)
at youpi.core$eval5981.invokeStatic(form-init3685547971046661105.clj:1)
at youpi.core$eval5981.invoke(form-init3685547971046661105.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6927)
at clojure.lang.Compiler.eval(Compiler.java:6890)
at clojure.core$eval.invokeStatic(core.clj:3105)
at clojure.core$eval.invoke(core.clj:3101)
at clojure.main$repl$read_eval_print__7408$fn__7411.invoke(main.clj:240)
at clojure.main$repl$read_eval_print__7408.invoke(main.clj:240)
<...>
at java.lang.Thread.run(Thread.java:745)
I tried to update the invoke implementation following advice given in this clojuredocs comment, but it didn't fix.
Here is my (still broken) attempt at implementing the comment's suggestion:
(defn- debug-print-invoke [obj proxy m args]
(apply println m args)
(.invoke m obj args))
(defn debug-proxy [obj]
(java.lang.reflect.Proxy/newProxyInstance
(.. obj getClass getClassLoader)
(.. obj getClass getInterfaces)
(proxy [java.lang.reflect.InvocationHandler] []
(invoke
([proxy m] (debug-print-invoke obj proxy m []))
([proxy m args] (debug-print-invoke obj proxy m args))))))
A good answer would either fix the snippet I've been working with, or suggest an alternative way to achieve the same goal from Clojure.
I'm using Mule ESB (Java Based) and I have some scala components that modify and create data. My Data is represented in Case Classes. I'm trying to convert them to Java, however Just getting them to convert to Scala types is a challenge. Here's a simplified example of what I'm trying to do:
package com.echostar.ese.experiment
import scala.collection.JavaConverters
case class Resource(guid: String, filename: String)
case class Blackboard(name: String, guid:String, resource: Resource)
object CCC extends App {
val res = Resource("4alskckd", "test.file")
val bb = Blackboard("Test", "123asdfs", res)
val myMap = getCCParams(bb)
val result = new java.util.HashMap[String,Object](myMap)
println("Result:"+result)
def getCCParams(cc: AnyRef) =
(Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
f.setAccessible(true)
val value = f.get(cc) match {
// this covers tuples as well as case classes, so there may be a more specific way
case caseClassInstance: Product => getCCParams(caseClassInstance): Map[String, Any]
case x => x
}
a + (f.getName -> value)
}
}
Current Error: Recursive method needs return type.
My Scala Foo isn't very strong. I grabbed this method from another answer here
and basically know what it's doing, but not enough to change this to java.util.HashMap and java.util.List
Expected Output:
Result:{"name"="Test", "guid"="123asdfs", "resource"= {"guid"="4alskckd", "filename"="test.file"}}
UPDATE1:
1. Added getCCParams(caseClassInstance): Map[String, Any] to line 22 Above per #cem-catikkas. IDE syntax error still says "recursive method ... needs result type" and "overloaded method java.util.HashMap cannot be applied to scala.collection.immutable.Map".
2. Changed java.util.HashMap[String, Object]
You should follow what the error tells you. Since getCCParams is a recursive method you need to declare its return type.
def getCCParams(cc: AnyRef): Map[String, Any]
Answering this in case anyone else going through the issue ends up here (as happened to me).
I believe the error you were getting had to do with the fact that the return type was being declared at method invocation (line 22), however the compiler was expecting it at the method's declaration (in your case, line 17). The below seems to have worked:
def getCCParams(cc: AnyRef): Map[String, Any] = ...
Regarding the conversion from Scala Map to Java HashMap, by adding the ._ wildcard to the JavaConverters import statement, you manage to import all the methods of the object as single identifiers, which is a requirement for implicit conversions. This will include the asJava method which can then be used to convert the Scala Map to a Java one, and then this can be passed to the java.util.HashMap(Map<? extends K,? extends V> m) constructor to instantiate a HashMap:
import scala.collection.JavaConverters._
import java.util.{HashMap => JHashMap}
...
val myMap = getCCParams(bb)
val r = myMap.asJava // converting to java.util.Map[String, Any]
val result: JHashMap[String,Any] = new JHashMap(r)
I wonder if you've considered going at it the other way around, by implementing the java.util.Map interface in your case class? Then you wouldn't have to convert back and forth, but any consumers downstream that are using a Map interface will just work (for example if you're using Groovy's field dot-notation).
Is there a concise, idiomatic way (maybe using Apache Commons) to specify common combinations of OpenOption like StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
These are the easy possibilities you have.
Static Imports, to increase readability:
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.WRITE;
OpenOption[] options = new OpenOption[] { WRITE, CREATE_NEW };
Use defaults:
//no Options anyway
Files.newBufferedReader(path, cs)
//default: CREATE, TRUNCATE_EXISTING, and WRITE not allowed: READ
Files.newBufferedWriter(path, cs, options)
//default: READ not allowed: WRITE
Files.newInputStream(path, options)
//default: CREATE, TRUNCATE_EXISTING, and WRITE not allowed: READ
Files.newOutputStream(path, options)
//default: READ do whatever you want
Files.newByteChannel(path, options)
Finally it's possible to specify optionsets like this:
Files.newByteChannel(path, EnumSet.of(CREATE_NEW, WRITE));
The best suggestion I can offer would be to cheat on the equivalence of T... and T[], which one of the other stackoverflow discussions says should work
Can I pass an array as arguments to a method with variable arguments in Java?
So...
OpenOption myOptions[] = {StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
OutputStream foo=OutputStream.newOutputStream(myPath,myOptions);
Caveat: Untested.
java.nio.file.Files has 5 flavours of methods with OpenOption varargs parameters:
Files
.newBufferedWriter(...)
.write(...)
.newOutputStream(...)
.newInputStream(...)
.newByteChannel(...)
They directly don't restrict any OpenOption combination, but all of them under the hood call to some of these 3 methods at java.nio.file.spi.FileSystemProvider:
FileSystemProvider
.newInputStream(Path, OpenOption...)
.newOutputStream(Path, OpenOption...)
.newByteChannel(Path, Set<? extends OpenOption>, FileAttribute<?>...)
FileSystemProvider.newInputStream(...) is called by: Files.newInputStream(...)
FileSystemProvider.newOutputStream(...) is called by:
Files
.newBufferedWriter(...)
.newOutputStream(...)
.write(...)
abstract FileSystemProvider.newByteChannel(...) is called by:
Files.newByteChannel(...)
FileSystemProvider.newInputStream(...)
FileSystemProvider.newOutputStream(...)
OptenOption combination restrictions:
FileSystemProvider.newInputStream(...)
UnsupportedOperationException: WRITE || APPEND
FileSystemProvider.newOutputStream(...)
Implicitly: WRITE
IllegalArgumentException: READ
default (if non options): CREATE && TRUNCATE_EXISTING
The abstract FileSystemProvider.newByteChannel(...) method has a platform dependent implementation, which may extend the OpenOption combination restrictions (as in sun.nio.fs.WindowsFileSystemProvider).
All Files method which uses OpenOption vargars under the hood ends in the abstract FileSystemProvider.newByteChannel(...), which implementation is platform dependent. So, the OpenOption combinations restriction in Files methods are platform dependent.
I'm writing a Clojure wrapper for the Braintree Java library to provide a more concise and idiomatic interface. I'd like to provide functions to instantiate the Java objects quickly and concisely, like:
(transaction-request :amount 10.00 :order-id "user42")
I know I can do this explicitly, as shown in this question:
(defn transaction-request [& {:keys [amount order-id]}]
(doto (TransactionRequest.)
(.amount amount)
(.orderId order-id)))
But this is repetitive for many classes and becomes more complex when parameters are optional. Using reflection, it's possible to define these functions much more concisely:
(defn set-obj-from-map [obj m]
(doseq [[k v] m]
(clojure.lang.Reflector/invokeInstanceMethod
obj (name k) (into-array Object [v])))
obj)
(defn transaction-request [& {:as m}]
(set-obj-from-map (TransactionRequest.) m))
(defn transaction-options-request [tr & {:as m}]
(set-obj-from-map (TransactionOptionsRequest. tr) m))
Obviously, I'd like to avoid reflection if at all possible. I tried defining a macro version of set-obj-from-map but my macro-fu isn't strong enough. It probably requires eval as explained here.
Is there a way to call a Java method specified at runtime, without using reflection?
Thanks in advance!
Updated solution:
Following the advice from Joost, I was able to solve the problem using a similar technique. A macro uses reflection at compile-time to identify which setter methods the class has and then spits out forms to check for the param in a map and call the method with it's value.
Here's the macro and an example use:
; Find only setter methods that we care about
(defn find-methods [class-sym]
(let [cls (eval class-sym)
methods (.getMethods cls)
to-sym #(symbol (.getName %))
setter? #(and (= cls (.getReturnType %))
(= 1 (count (.getParameterTypes %))))]
(map to-sym (filter setter? methods))))
; Convert a Java camelCase method name into a Clojure :key-word
(defn meth-to-kw [method-sym]
(-> (str method-sym)
(str/replace #"([A-Z])"
#(str "-" (.toLowerCase (second %))))
(keyword)))
; Returns a function taking an instance of klass and a map of params
(defmacro builder [klass]
(let [obj (gensym "obj-")
m (gensym "map-")
methods (find-methods klass)]
`(fn [~obj ~m]
~#(map (fn [meth]
`(if-let [v# (get ~m ~(meth-to-kw meth))] (. ~obj ~meth v#)))
methods)
~obj)))
; Example usage
(defn transaction-request [& {:as params}]
(-> (TransactionRequest.)
((builder TransactionRequest) params)
; some further use of the object
))
You can use reflection at compile time ~ as long as you know the class you're dealing with by then ~ to figure out the field names, and generate "static" setters from that. I wrote some code that does pretty much this for getters a while ago that you might find interesting. See https://github.com/joodie/clj-java-fields (especially, the def-fields macro in https://github.com/joodie/clj-java-fields/blob/master/src/nl/zeekat/java/fields.clj).
The macro could be as simple as:
(defmacro set-obj-map [a & r] `(doto (~a) ~#(partition 2 r)))
But this would make your code look like:
(set-obj-map TransactionRequest. .amount 10.00 .orderId "user42")
Which I guess is not what you would prefer :)
I'm very new in learning Clojure. This intended to be my first and very simple Clojure tries in which I call a simple Clojure method from inside java code. Unfortunately it does not work. The Compilation is successful and from the Clojure REPL the written function does as it was ordered, but when calling from Java it says the following:
Exception in thread "main" java.lang.IllegalArgumentException: Wrong number of args (2) passed to: ClojNum$-myinc
at clojure.lang.AFn.throwArity(AFn.java:439)
at clojure.lang.AFn.invoke(AFn.java:43)
at com.experimental.clojure.test.ClojNum.myinc(Unknown Source)
at com.experimental.clojure.java.JavaCaller.main(JavaCaller.java:14)
Here is the very simple Clojure code:
(ns com.experimental.clojure.test.ClojNum
(:gen-class
:init init
:name com.experimental.clojure.test.ClojNum
:methods [
[myinc [int] int]
]))
(defn -init [] [[] (atom [])])
(defn myinc "comment" [x] (+ x 1))
(defn -myinc "comment" [x] (myinc x))
And the java part:
package com.experimental.clojure.java;
import com.experimental.clojure.test.ClojNum;
public class JavaCaller {
/**
* #param args
*/
public static void main(String[] args) {
int i = 0;
System.out.println(i);
ClojNum c = new ClojNum();
i = c.myinc(0);
System.out.println(i);
}
}
What did I do wrong?
(Note again: This is primitve test code just to make a first successful function call)
Thanks for the help, I'm clueless.
Jeremy's link in the comments show you one way to call a static method in a clojure class. If you want to call a clojure function on an object instance, you need to add a parameter to your wrapper method definition:
(defn -myinc "comment" [this x] (myinc x))
The 'this' parameter is required for any non-static wrapper function. Clojure threw an exception because it received two parameters for a function only defined with one. Note, you do not change anything in your :gen-class :methods section or the myinc function definition itself.
The documentation is a bit sparse, but examples of this can be found at:
http://clojure.org/compilation (the last example on the page shows instance methods).