Reputation: 1498
This seems to be a more difficult in Clojure than in Java and Scala. What I want to do is:
This is what I have so far:
package hello.interfaces;
public interface Test {
String doSomething(String input);
}
(ns hello.impl.test
(:gen-class
:implements [hello.interfaces.Test]))
(defn doSomething [v] (str "hello " v))
(ns hello.core
(:import
(org.reflections Reflections)
(org.reflections.util ConfigurationBuilder))
(:gen-class))
(defn -instances []
(let [paths (into-array Object ["hello"])]
(.getSubTypesOf (Reflections. paths) hello.interfaces.Test)))
(defn -main [& args]
(println (-instances)))
I am using org.reflections. This works if I search for classes that are in the classpath (e.g. in the org.reflections
jar) but it does not work for my previously defined class, therefore I think that the problem is not in the last snippet of code but in the previous one, or maybe in the usage, e.g. it requires pre-compiling it.
How can I define classes in Clojure that I can find later with reflection?
Upvotes: 1
Views: 705
Reputation: 8633
I'm not familiar with org.reflections, but if you just want a list of loaded classes, you can get it with the below code:
(let [classloader (.getClassLoader clojure.main)
classes-field (.getDeclaredField java.lang.ClassLoader "classes")]
(.setAccessible classes-field true)
(let [class-list (.get classes-field classloader)
class-vec (reduce conj [] class-list)] ; copy everything into a new vector rather than working directly with the classloader's private field
class-vec))
It sounds like you're familiar with Java, so I guess you can see the above is basically just translated Java. It will only give you the classes which have been loaded with the same class loader as that for the class clojure.main
, but if you haven't done any customisation with your class loaders, that should be enough.
Once you have that list, you can search/filter it however you want. Of course, the class you're looking for does have to have been loaded first. If that's not the case, you'll have to search the classpath instead.
=== UPDATE to respond to your comment ===
OK I see, you're asking how to create a class. The first thing to say is that you don't generally need to create named classes when you're writing Clojure, unless you specifically want to use some existing Java code which requires you to do so. If you're writing pure Clojure, you just write your functions and work with them directly.
However, you can of course do so. The first part of the doc for gen-class
states:
=> (doc gen-class)
clojure.core/gen-class
([& options])
Macro
When compiling, generates compiled bytecode for a class with the given package-qualified :name (which, as all names in these parameters, can be a string or symbol), and writes the .class file to the compile-path directory. When not compiling, does nothing.
So, you need to compile your namespace. I don't normally do this so I don't know if there's a way to do it without creating .class files, and just creating the classes directly in memory, but the below does what you want, if I've understood you correctly:
(ns wip.test)
; looks for wip/himplement.clj on the classpath, and compiles it into .class files
; requires that ../bin is in the classpath
(binding [*compile-path* "../bin"]
(compile 'wip.himplement))
; loads the wip.himplement class from the .class files
(Class/forName "wip.himplement")
; create a list of all loaded classes (could presumably also be done with org.reflections)
(def classes-list (let [classloader (.getClassLoader clojure.main)
classes-field (.getDeclaredField java.lang.ClassLoader "classes")]
(.setAccessible classes-field true)
(java.util.ArrayList. (.get classes-field classloader))))
; Outputs all loaded classes which implement HInterface. Output is:
; (wip.hello.HInterface wip.himplement)
(println (filter #(isa? % wip.hello.HInterface) classes-list))
Upvotes: 1
Reputation: 2968
I'm not totally sure what you're trying to achieve, but there are a few things that I should probably point you in the direction of. Firstly there's a namespace called clojure.reflect
that contains useful functions for getting info about classes. It makes it easier to reflect the Java hierarchy at the REPL. Secondly there's things like clojure.tools.namespace that will help you traverse the code in your repo and find all the namespaces that implement interfaces. However, you tend to use deftype
to implement classes rather than using the AOT compiler features like the :gen-class
option in the ns
macro, unless you're implementing something specific like a servelet or something.
Upvotes: 0