September 2, 2019

Useful function for inspect Java methods from Clojure

Problems

While working on Clojure project, you want to inspect the function from Java library and work with it effectively.

Solutions

We can write simple Clojure functions that make it easy to inspect and see available Java method/function at runtime (from the REPL or our favorite editor (Emacs/Vim/Cursive/etc))

(ns b12n.swiza.interop.core
  (:require
   [clojure.pprint :refer [pprint print-table]]
   [clojure.reflect :refer [reflect]]))

;; Inspired by:  https://gist.github.com/Sh4pe/eea52891dbeca5d0614d
(defn java-methods
  [object]
  (let [result (->> (reflect object)
                    :members
                    (filter :return-type)
                    (sort-by :name)
                    (map #(select-keys % [:flags :return-type :name :parameter-types])))]
    result))

(defn jmethods
  [object & [{:keys [show-public?
                     short-format?
                     show-getter?
                     show-setter?
                     show-private?]
              :or {show-public? true
                   short-format? true
                   show-getter? false
                   show-setter? false
                   show-private? false}}]]
  (let [result (java-methods object)]
    (as-> result $
      ;; include public methods if one specified
      (if show-public? (filter #(contains? (:flags %) :public) $) $)

      ;; include private methods if one specified
      (if show-private? (filter #(contains? (:flags %) :private) $) $)

      ;; include getter methods if one specified
      (if show-getter? (filter #(clojure.string/starts-with? (:name %) "get") $) $)

      ;; include setter methods if one specified
      (if show-setter? (filter #(clojure.string/starts-with? (:name %) "set") $) $)

      ;; Finally print out the signature of the class
      (if short-format?
        ;; print the function name once when it takes more than one way
        (-> (map #(:name %) $) set seq)
        (map #(format "%s %s %s(%s)"
                      (clojure.string/join " " (map name (:flags %)))
                      (:return-type %)
                      (:name %)
                      (clojure.string/join " " (map str (:parameter-types %)))) $)))))

(defn print-methods
  "Print the method of a given Java class.

  Examples:
  (show-methods java.util.UUID) ;; see your REPL
  (show-methods java.lang.String)"
  [clazz]
  (let [declared-methods (seq (:declaredMethods (bean clazz)))
        methods (map #(.toString %) declared-methods)]
    (doseq [m methods]
      (println m))
    methods))

Example Usages

  • Using print-methods on java.util.UUID
    (print-methods java.util.UUID)
    
    ;; Reformat using pprint
    ;;=>
    ("public boolean java.util.UUID.equals(java.lang.Object)"
     "public java.lang.String java.util.UUID.toString()"
     "public int java.util.UUID.hashCode()"
     "public int java.util.UUID.compareTo(java.util.UUID)"
     "public int java.util.UUID.compareTo(java.lang.Object)"
     "public long java.util.UUID.timestamp()"
     "private static java.lang.String java.util.UUID.digits(long,int)"
     "public int java.util.UUID.version()"
     "public int java.util.UUID.variant()"
     "public static java.util.UUID java.util.UUID.randomUUID()"
     "public static java.util.UUID java.util.UUID.nameUUIDFromBytes(byte[])"
     "public static java.util.UUID java.util.UUID.fromString(java.lang.String)"
     "public long java.util.UUID.getLeastSignificantBits()"
     "public long java.util.UUID.getMostSignificantBits()"
     "public int java.util.UUID.clockSequence()"
     "public long java.util.UUID.node()")
    
  • Using jmethods on java.util.UUID
    (jmethods java.util.UUID)
    

You will get

(compareTo
 version
 timestamp
 randomUUID
 nameUUIDFromBytes
 fromString
 getMostSignificantBits
 getLeastSignificantBits
 variant
 toString
 node
 equals
 hashCode
 clockSequence)

If you just want to see the getter methods only then you could use

(jmethods java.util.UUID {:show-getter? true})

Which should only show you the getter methods from the given Java class.

(getMostSignificantBits getLeastSignificantBits)
  • Using java-methods on java.util.UUID
    (java-methods java.util.UUID)
    

Will give you something like:

({:flags #{:public},
  :return-type int,
  :name clockSequence,
  :parameter-types []}
 {:flags #{:public :bridge :synthetic},
  :return-type int,
  :name compareTo,
  :parameter-types [java.lang.Object]}
 {:flags #{:public},
  :return-type int,
  :name compareTo,
  :parameter-types [java.util.UUID]}
 {:flags #{:private :static},
  :return-type java.lang.String,
  :name digits,
  :parameter-types [long int]}
 {:flags #{:public},
  :return-type boolean,
  :name equals,
  :parameter-types [java.lang.Object]}
 {:flags #{:public :static},
  :return-type java.util.UUID,
  :name fromString,
  :parameter-types [java.lang.String]}
 {:flags #{:public},
  :return-type long,
  :name getLeastSignificantBits,
  :parameter-types []}
 {:flags #{:public},
  :return-type long,
  :name getMostSignificantBits,
  :parameter-types []}
 {:flags #{:public},
  :return-type int,
  :name hashCode,
  :parameter-types []}
 {:flags #{:public :static},
  :return-type java.util.UUID,
  :name nameUUIDFromBytes,
  :parameter-types [byte<>]}
 {:flags #{:public},
  :return-type long,
  :name node,
  :parameter-types []}
 {:flags #{:public :static},
  :return-type java.util.UUID,
  :name randomUUID,
  :parameter-types []}
 {:flags #{:public},
  :return-type long,
  :name timestamp,
  :parameter-types []}
 {:flags #{:public},
  :return-type java.lang.String,
  :name toString,
  :parameter-types []}
 {:flags #{:public},
  :return-type int,
  :name variant,
  :parameter-types []}
 {:flags #{:public},
  :return-type int,
  :name version,
  :parameter-types []})

Personally I use jmethods more often than others due to the ability to get specific value that I need via the options.

Hope this make your interactive development with Clojure easier and fun.

PS: all of the following are now published as Clojure library via Clojars. You can find the full source code in swiza-interop

Tags: clojure java repl interops jvm