views:

208

answers:

3

I am trying to wrap a Java library with a Clojure binding. One particular class in the Java library defines a bunch of static final constants, for example:

class Foo {
    public static final int BAR = 0;
    public static final int SOME_CONSTANT = 1;
    ...
}

I had a thought that I might be able to inspect the class and pull these constants into my Clojure namespace without explicitly def-ing each one.

For example, instead of explicitly wiring it up like this:

(def foo-bar Foo/BAR)
(def foo-some-constant Foo/SOME_CONSTANT)

I'd be able to inspect the Foo class and dynamically wire up foo-bar and foo-some-constant in my Clojure namespace when the module is loaded.

I see two reasons for doing this:

A) Automatically pull in new constants as they are added to the Foo class. In other words, I wouldn't have to modify my Clojure wrapper in the case that the Java interface added a new constant.

B) I can guarantee the constants follow a more Clojure-esque naming convention

I'm not really sold on doing this, but it seems like a good question to ask to expand my knowledge of Clojure/Java interop.

Thanks

+1  A: 

(This answer now includes two working solutions, one based on my initial idea with intern and one based on danlei's suggestion to use c.c.import-static. I guess I'll need to clean this up a bit later, but I can't spend more time on it now...)

To extract static fields:

(filter #(bit-and java.lang.reflect.Modifier/STATIC (.getModifiers %))
        (.getFields YourClass))

Then map #(intern *ns* (str "a-prefix-" (.getName %)) (.get YourClass nil)) across that sequence to obtain the value... Note that this bit is untested and, in particular, I'm not sure about that nil in .get; experiment with java.lang.Field and see what works with your class.

Update 2:

Ok, actually an intern based approach is not that bad readability-wise:

user> (map #(intern *ns* (symbol (str "integer-" (.getName %))) (.get % java.lang.Integer))
           (filter #(bit-and java.lang.reflect.Modifier/STATIC
                             (.getModifiers %))
                   (.getFields java.lang.Integer)))
(#'user/integer-MIN_VALUE #'user/integer-MAX_VALUE #'user/integer-TYPE #'user/integer-SIZE)
user> integer-MIN_VALUE
-2147483648
user> integer-MAX_VALUE
2147483647
user> integer-TYPE
int
user> integer-SIZE
32

Update: (leaving the first update in place as an alternative solution)

Combining danlei's knowledge of clojure.contrib with the above yields the following:

user> (map #(eval `(import-static java.lang.Integer ~(symbol (.getName %))))
           (filter #(bit-and java.lang.reflect.Modifier/STATIC
                             (.getModifiers %))
                   (.getFields java.lang.Integer)))
(#'user/MIN_VALUE #'user/MAX_VALUE #'user/TYPE #'user/SIZE)
user> MIN_VALUE
-2147483648
user> MAX_VALUE
2147483647
user> TYPE
int
user> SIZE
32

It uses eval... well, so what, it's hardly going to "kill performance" and it's actually fairly readable, which an elaborate expression using intern might not be. (It's not so bad actually... :-)) If you prefer intern, though, at least the implementation of import-static can give you the proper ideas if my sketch above turns out to be incorrect somehow.

Michał Marczyk
+1  A: 

I haven't tried it, but maybe clojure.contrib.import-static can do it.

Just checked: You will have to name the methods/fields when using import-static, but I'll leave this answer here because it might be helpful for people searching for related answers.

danlei
`c.c.import-static`, d'oh... Actually it's perfectly possible to take advantage of it here with the help of `eval`. I took the liberty of using it in an edit to my answer which gives an alternative approach to `intern` + Java reflection trickery.
Michał Marczyk
+2  A: 

Sadly the macro clojure.contrib.import-static doesn't allow to import all static final fields. You must provide a list of fields to import.

This macro is a idiomatic wrapper for import-static:

(ns stackoverflow
  (:use clojure.contrib.import-static)
  (:import (java.lang.reflect Modifier)))

(defmacro import-static-fields
  "Imports all static final fields of the class as (private) symbols
  in the current namespace.

  Example: 
      user> (import-static-fields java.lang.Integer)
      #'user/TYPE
      user> MAX_VALUE
      2147483647

  Note: The class name must be fully qualified, even if it has already
  been imported."
  [class]
  (let [final-static-field? (fn [field]
                  (let [modifiers (.getModifiers field)]
                (and (Modifier/isStatic modifiers) (Modifier/isFinal modifiers))))
    static-fields (map #(.getName %)
               (filter
                final-static-field?
                (.. Class (forName (str class)) getFields)))]
    `(import-static ~class ~@static-fields)))
Jürgen Hötzel