jvm.reflect
interactive Java reflection and member queries
Inspect class metadata and hierarchies, query methods and fields, and pretty-print object views.
1 Overview
The namespace exposes compact REPL-oriented macros over std.object. Selectors can filter members by name, type, modifiers, or regular expression, and output is formatted for interactive exploration.
2 Walkthrough
2.1 Inspect a class
(require '[jvm.reflect :as reflect])
(reflect/.% String)
(reflect/.%> String)
(reflect/.? String #"^char" :name)
2.2 Inspect an instance
(reflect/.* "hello" #"^to" :name)
(reflect/.& (StringBuilder. "hello"))
2.3 Thread member access
.> behaves like -> while resolving keyword or dot-prefixed member names through the object query system.
(reflect/.> "hello" :value String.)
3 Supporting output
jvm.reflect.print contains the tabular and class-name formatting used by the query macros.
4 API
v 3.0
(defmacro .%
([obj]
`(element/class-info ~obj)))
link
(.% String) => (contains {:modifiers #{:instance :public :final :class}, :name "java.lang.String"})
v 3.0
(defmacro .%>
([obj]
`(let [~'result (element/class-hierarchy ~obj)]
(print-hierarchy ~'result)
~'result)))
link
(.%> String) => [java.lang.String [java.lang.Object #{java.lang.constant.ConstantDesc java.io.Serializable java.lang.Comparable java.lang.constant.Constable java.lang.CharSequence}]]
v 3.0
(defmacro .&
([obj]
`(let [~'o ~obj
~'res (if (map? ~'o) ~'o (query/delegate ~'o))]
(env/local :println (str "n#" (type ~'o)))
(doto ~'res
(->> (into {})
(pretty/pprint-str)
(env/local :println))))))
link
(.& {:a 1 :b 2 :c 3}) => (contains {:a 1 :b 2 :c 3})
v 3.0
(defmacro .*
([obj & selectors]
`(query-printed "INSTANCE" query/query-instance ~obj (vector ~@selectors))))
link
(.* "abc" #"^to") (.* String :name #"^to") => (contains ["toString"])
v 3.0
(defmacro .*>
([obj & selectors]
`(query-printed "INSTANCE" query/query-instance ~obj (vector ~@selectors))))
link
(.*> "abc" #"^to") (.*> String :name #"^to") => (contains ["toString"])
.> ^
[obj] [obj method] [obj method & more]
threads the first input into the rest of the functions. Same as `->` but allows access to private fields using both `:keyword` and `.symbol` lookup:
v 3.0
(defmacro .>
([obj] obj)
([obj method]
(cond (not (list? method))
`(.> ~obj (~method))
(list? method)
(let [[method & args] method]
(cond
(#{'.% '.%>} method)
`(~(symbol (str "std.object.element/" method)) ~obj ~@args)
(#{'.* '.? '.&} method)
`(~(symbol (str "std.object.query/" method)) ~obj ~@args)
(and (symbol? method) (.startsWith (name method) "."))
`(query/apply-element ~obj ~(subs (name method) 1) ~(vec args))
(keyword? method)
`(or (~method ~obj ~@args)
(let [nm# ~(subs (str method) 1)]
(if (some #{nm#} (query/query-instance ~obj [:name]))
(query/apply-element ~obj nm# ~(vec args)))))
:else
`(~method ~obj ~@args)))))
([obj method & more]
`(.> (.> ~obj ~method) ~@more)))
link
(.> "abcd" :value String.) => "abcd" (.> "abcd" .value String.) => "abcd" (comment "BROKEN" (def -b- (byte-array (.getBytes "world"))) (let [a "hello" _ (.> "hello" (.value -b-))] a) => "world")
v 3.0
(defmacro .?
([obj & selectors]
`(query-printed "INSTANCE" query/query-class ~obj (vector ~@selectors))))
link
(.? String #"^c" :name) => (contains ["charAt"]) ;;["charAt" "chars" "checkBoundsBeginEnd" ;; "checkBoundsOffCount" "checkIndex" "checkOffset" ;; "codePointAt" "codePointBefore" "codePointCount" "codePoints" ;; "coder" "compareTo" "compareToIgnoreCase" "concat" "contains" ;; "contentEquals" "copyValueOf"]
v 3.0
(defmacro .?*
([obj & selectors]
`(query-printed "HIERARCHY" query/query-hierarchy ~obj (vector ~@selectors))))
link
(.?* String #"^to" :name) => (contains ["toString"])
v 3.0
(defmacro .?>
([obj & selectors]
`(query-printed "SUPERS" query/query-supers ~obj (vector ~@selectors))))
link
(.?> String #"^to" :name) => (contains ["toString"])
v 3.0
(defn print-hierarchy
([tree]
(let [out (-> (walk/postwalk (fn [form]
(cond (class? form)
(.getName ^Class form)
(set? form)
(vec (sort form))
:else form))
tree)
(fmt/tree-graph))]
(env/local :println out)
out)))
link
(print-hierarchy [String [Object #{}]]) => string?
query-printed ^
[type query-fn obj selectors]
prints the output on the result to display
v 3.0
(defn query-printed
([type query-fn obj selectors]
(let [cls (common/context-class obj)
elems (query-fn obj (input/args-convert selectors))
title (str "QUERY "
type
(if (seq selectors) (str " " selectors)))]
(cond (common/element? elems)
elems
:else
(when (and (seq elems) (common/element? (first elems)))
(print/print-classname title cls)
(when (or (= query-fn query/query-supers)
(= query-fn query/query-hierarchy))
(env/local :print "n")
(print-hierarchy (element/class-hierarchy cls)))
(print/print-class elems)))
elems)))
link
(query-printed "CLASS" query/query-class String [#"^c" :name]) => (contains ["charAt"]) (query-printed "CLASS" query/query-class String [#"^c"]) => seq?
- +col-alignment+
- +col-colors+
- +col-lengths+
- +col-options+
- +customiser+
- +element-comp+
- +matrix+
- +shorthand+
- category-title
- class-elements
- col-color
- col-settings
- format-type
- order-modifiers
- print-category
- print-class
- print-classname
- print-elements
- sort-elements
+element-comp+ ^
NONE
(defonce +element-comp+
(juxt :name
(comp (fn [params]
(if-let [p (first params)]
(format-type p)
""))
:params)
(comp count :params)))
link
+shorthand+ ^
NONE
(def +shorthand+
[["java.lang.reflect." "j.l.r."]
["java.lang.annotation." "j.l.a."]
["java.lang." ""]
["java.security." "j.sc."]
["java.fx." "j.fx."]
["java.io." "j.io."]
["java.net." "j.n."]
["java.util." "j.u."]
["java." "j."]
["clojure.lang." ""]
["clojure." "c."]
["jdk.internal.reflect" "jdk.i.r."]
["jdk.internal." "jdk.i."]
["sun.reflect.annotation." "sun.r.a."]
["sun.reflect.generics.repository" "sun.r.g.r"]
["sun.reflect.generics.factory" "sun.r.g.f"]
["sun.reflect.generics." "sun.r.g."]
["scala.collection.immutable" "s.c.i"]
["scala.collection.mutable" "s.c.m"]
["scala.collection.parallel.immutable" "s.c.p.i"]
["scala.collection.parallel.mutable" "s.c.p.m"]
["scala.collection" "s.c"]
["scala." "s."]
["hara." "h."]])
link
v 3.0
(defn category-title
([ks]
(category-title ks {}))
([ks {:keys [matrix class]}]
(let [matrix (merge +matrix+ matrix)
title (-> (map (comp :title matrix) (reverse ks))
(common/joinl " "))]
title)))
link
(category-title [:method :instance]) => "INSTANCE METHOD"
class-elements ^
[elems ks] [elems [k & more :as ks] opts]
returns all class elements for a given category
v 3.0
(invoke/definvoke class-elements
[:memoize]
([elems ks]
(class-elements elems ks {}))
([elems [k & more :as ks] opts]
(let [matrix (merge +matrix+ opts)]
(cond (empty? ks)
elems
(empty? more)
(filter (get-in matrix [k :predicate]) elems)
:else
(let [nelems (class-elements elems [k] opts)]
(class-elements nelems more opts))))))
link
(class-elements (query/query-class String []) [:method :instance]) => seq?
v 3.0
(defn col-color
([i highlight runtime access label colors matrix]
(let [selection (fn [k]
(->> (map (comp k matrix) [runtime access])
(cons (if (zero? i)
(if (= k :highlight) highlight)
(k (get colors label))))
(filter identity)
(first)))
highlight (selection :highlight)
weight (selection :weight)]
(disj (set [highlight weight]) nil))))
link
(col-color 0 :cyan :instance :public :name +col-colors+ +matrix+) => #{:cyan :bold}
col-settings ^
[ks] [[elem runtime access] {:keys [matrix lengths colors]}]
returns the column settings
v 3.0
(defn col-settings
([ks]
(col-settings ks {}))
([[elem runtime access] {:keys [matrix lengths colors]}]
(let [matrix (merge +matrix+ matrix)
lengths (merge +col-lengths+ lengths)
colors (merge +col-colors+ colors)
{:keys [labels alignment highlight]
:or {alignment +col-alignment+}} (get matrix elem)]
(map-indexed (fn [i label]
{:id label
:align (nth alignment i)
:length (get lengths label)
:color (col-color i highlight runtime access label colors matrix)})
labels))))
link
(mapv :id (col-settings [:method :instance :public])) => [:name :type :params :modifiers]
v 3.0
(defn format-type
([class]
(format-type class +shorthand+))
([^Class class shorthand]
(cond (.isArray class)
(str (format-type (.getComponentType class)) "[]")
(.isPrimitive class)
(str class)
:else
(reduce (fn [^String s [^String full ^String short]]
(.replace s full short))
(.getName class)
shorthand))))
link
(format-type String) => "String" (format-type (type {})) => "PersistentArrayMap" (format-type Byte/TYPE) => "byte" (format-type (type (char-array ()))) => "char[]"
v 3.0
(defn order-modifiers
([modifiers]
(cond-> modifiers
(:final modifiers) (->> (disj modifiers :final)
(sort)
(cons :final))
:then (-> sort (common/joinl " ")))))
link
(order-modifiers #{:public :static :final}) => ":final :public :static"
v 3.0
(defn print-category
([elems ks]
(print-category elems ks {}))
([elems ks opts]
(let [categories [:public :private :protected :plain]
selems (mapcat (fn [k]
(class-elements elems (conj ks k)))
categories)]
(if (seq selems)
(print/print-title (category-title ks opts)))
(mapv (fn [k]
(print-elements (category-title [k] opts)
(class-elements elems (conj ks k))
(col-settings (conj ks k))
{:options {:padding 5}}))
categories))))
link
(print-category (query/query-class String []) [:method :instance]) => vector?
v 3.0
(defn print-class
([elems]
(print-class elems {}))
([elems opts]
(mapv (fn [ks]
(print-category elems ks opts))
[[:constructor]
[:field :static]
[:method :static]
[:field :instance]
[:method :instance]])))
link
(print-class (query/query-class String [])) => vector?
v 3.0
(defn print-classname
([title ^Class cls]
(print/print-title (str title " - " (clojure.string/upper-case (.getName cls))))))
link
(print-classname "CLASS" String) => nil
print-elements ^
[title elems col-settings] [title elems col-settings {:keys [options]}]
prints all elements in a given category
v 3.0
(defn print-elements
([title elems col-settings]
(print-elements title elems col-settings {}))
([title elems col-settings {:keys [options]}]
(let [options (merge +col-options+ options)]
(when (seq elems)
(env/local :print "n")
(print/print-subtitle title)
(env/local :print "n"))
(doseq [elem elems]
(let [row (map (fn [{:keys [id]}]
(let [data (get elem id)
func (get +customiser+ id)]
(if func
(func data)
data)))
col-settings)]
(print/print-row row (merge options {:columns col-settings})))))))
link
(print-elements "PUBLIC" [] (col-settings [:method :instance :public])) => nil