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



.% ^

[obj]
Added 3.0

lists class information

v 3.0
(defmacro .%
  ([obj]
   `(element/class-info ~obj)))
link
(.% String) => (contains {:modifiers #{:instance :public :final :class}, :name "java.lang.String"})

.%> ^

[obj]
Added 3.0

lists the class and interface hierarchy for the class

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}]]

.& ^

[obj]
Added 3.0

pretty prints the data representation of a value

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})

.* ^

[obj & selectors]
Added 3.0

lists what methods could be applied to a particular instance

v 3.0
(defmacro .*
  ([obj & selectors]
   `(query-printed "INSTANCE" query/query-instance ~obj (vector ~@selectors))))
link
(.* "abc" #"^to") (.* String :name #"^to") => (contains ["toString"])

.*> ^

[obj & selectors]
Added 3.0

lists what methods could be applied to a particular instance

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]
Added 3.0

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")

.? ^

[obj & selectors]
Added 3.0

queries the java view of the class declaration

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"]

.?* ^

[obj & selectors]
Added 3.0

queries the the class hierarchy

v 3.0
(defmacro .?*
  ([obj & selectors]
   `(query-printed "HIERARCHY" query/query-hierarchy ~obj (vector ~@selectors))))
link
(.?* String #"^to" :name) => (contains ["toString"])

.?> ^

[obj & selectors]
Added 3.0

queries the the class hierarchy

v 3.0
(defmacro .?>
  ([obj & selectors]
   `(query-printed "SUPERS" query/query-supers ~obj (vector ~@selectors))))
link
(.?> String #"^to" :name) => (contains ["toString"])

print-hierarchy ^

[tree]
Added 3.0

helper function to `.%>`

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]
Added 3.0

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+ ^

NONE
(defonce +col-alignment+  (cons :right (repeat :left)))
link

+col-colors+ ^

NONE
(defonce +col-colors+)
link

+col-lengths+ ^

NONE
(defonce +col-lengths+)
link

+col-options+ ^

NONE
(defonce +col-options+)
link

+customiser+ ^

NONE
(defonce +customiser+)
link

+element-comp+ ^

NONE
(defonce +element-comp+
  (juxt :name
        (comp (fn [params]
                (if-let [p (first params)]
                  (format-type p)
                  ""))
              :params)
        (comp count :params)))
link

+matrix+ ^

NONE
(defonce +matrix+)
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

category-title ^

[ks] [ks {:keys [matrix class]}]
Added 3.0

creates a category title

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]
Added 3.0

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?

col-color ^

[i highlight runtime access label colors matrix]
Added 3.0

returns the column color

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]}]
Added 3.0

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]

format-type ^

[class] [class shorthand]
Added 3.0

returns a nice looking version of the class

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[]"

order-modifiers ^

[modifiers]
Added 3.0

orders elements based on modifiers for printing

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"

print-category ^

[elems ks] [elems ks opts]
Added 3.0

prints a given category

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?

print-class ^

[elems] [elems opts]
Added 3.0

prints a given class

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?

print-classname ^

[title cls]
Added 3.0

prints the classname with title

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]}]
Added 3.0

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

sort-elements ^

[elems]
Added 3.0

sorts elements given a comparator

v 3.0
(defn sort-elements
  ([elems]
   (sort-by +element-comp+ elems)))
link
(->> (sort-elements [{:name "b" :params []} {:name "a" :params []}]) (map :name)) => ["a" "b"]