How to pull `static final` constants from a Java class into a Clojure namespace?

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

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

I had the thought that I could test the class and pull these constants into the Clojure namespace without an explicit def in each.

For example, instead of explicitly connecting it as follows:

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

I could check the Foo class and dynamically connect to foo-bar and foo-some-constant in my Clojure namespace when loading the module.

I see two reasons for this:

A) Automatically retract new constants as they are added to the Foo class. In other words, I would not have to change my Clojure shell if the Java interface added a new constant.

B) I can guarantee that constants follow a more than Clojure-known naming convention

I'm actually not for sale, but it seems like a good question to ask to expand your knowledge of Clojure / Java interop.

thanks

+7
clojure
source share
3 answers

Unfortunately, the clojure.contrib.import-static macro does not allow you to import all static end fields. You must specify a list of fields to import.

This macro is an 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))) 
+3
source share

(This answer now includes two working solutions, one based on my original idea with intern and one based on danlei's suggestion for using ccimport-static . I think I will need to clean this up a bit later, but now I can’t spend anymore it takes more time ...)

To extract static fields:

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

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

Update 2:

Well, actually the intern based approach is not that badly readable:

 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 the knowledge of danlei clojure.contrib with the above, we get 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 it’s unlikely that it will "kill performance", and in fact it is readable enough that a complex expression using intern may not be. (This is not so bad ... :-)) If you prefer intern , although at least the import-static implementation can give you the right ideas if my sketch above turns out to be incorrect.

+2
source share

I have not tried, but maybe clojure.contrib.import-static can do this.

Just checked: you will need to specify methods / fields when using import-static, but I will leave this answer here because it can be useful for people who are looking for answers to them.

+1
source share

All Articles