2010-04-03 12 views
7

Estoy intentando envolver una biblioteca Java con un enlace Clojure. Una clase particular en la biblioteca de Java define un grupo de constantes finales estáticas, por ejemplo:¿Cómo se obtienen las constantes `static final` de una clase Java en un espacio de nombre Clojure?

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

tuve un pensamiento que podría ser capaz de inspeccionar la clase y tirar de estas constantes en mi espacio de nombres Clojure sin explícitamente def - ing cada uno.

Por ejemplo, en lugar de cableado explícitamente de esta manera:

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

me gustaría ser capaz de inspeccionar la clase Foo y dinámicamente cablear foo-bar y foo-some-constant en mi espacio de nombres Clojure cuando se carga el módulo.

Veo dos razones para hacer esto:

A) Obtener automáticamente en nuevas constantes a medida que se agregan a la clase Foo. En otras palabras, no tendría que modificar mi envoltorio Clojure en el caso de que la interfaz Java agregara una nueva constante.

B) Puedo garantizar las constantes siguen una convención de nombres más Clojure-esque

Realmente no estoy vendido en hacer esto, pero parece que una buena pregunta para ampliar mis conocimientos de Clojure/Java interoperacion

Gracias

Respuesta

3

Lamentablemente la macro clojure.contrib.import-estática no permite importar todos campos static final. Debe proporcionar una lista de campos para importar.

Esta macro es un envoltorio idiomática para importación-estática:

(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 [email protected]))) 
+0

¿Existe quizás una solución más concisa y completa que use 'clojure.reflect/reflect'? http://clojuredocs.org/clojure_core/clojure.reflect/reflect – noahlz

2

(Esta respuesta ahora incluye dos soluciones de trabajo, uno sobre la base de mi idea inicial con intern y otro basado en la sugerencia de danlei utilizar c.c.import-static. Creo que voy a tener que limpiar esto un poco más tarde , pero no puedo pasar más tiempo en él ahora ...)

Para extraer los campos estáticos:

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

Entonces mapa #(intern *ns* (str "a-prefix-" (.getName %)) (.get YourClass nil)) a través de esa secuencia para obtener el valor ... Tenga en cuenta que este bit no ha sido probado y, en particular, no estoy seguro de eso nil en .get; Experimente con java.lang.Field y vea qué funciona con su clase.

Actualización 2:

Ok, en realidad un enfoque basado intern no es tan mala legibilidad-sabia:

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 

Actualización:(dejando la primera actualización en su lugar como una solución alternativa)

Combinando el conocimiento de danlei de clojure.contrib con los rendimientos por encima de la siguientes:

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 

Se utiliza eval ...bueno, qué, difícilmente va a "matar el rendimiento" y en realidad es bastante legible, lo que podría no ser una expresión elaborada usando intern. (No es tan malo en realidad ... :-)) Si prefiere intern, al menos la implementación de import-static puede darle las ideas correctas si mi bosquejo anterior resulta ser incorrecto de alguna manera.

1

No lo he probado, pero tal vez clojure.contrib.import-static lo puede hacer.

Acaba de marcar: Deberá nombrar los métodos/campos cuando use import-static, pero dejaré esta respuesta aquí porque podría ser útil para las personas que buscan respuestas relacionadas.

+1

'ccimport-static', d'oh ... En realidad, es perfectamente posible tomar ventaja de ello aquí con la ayuda de 'eval'. Me tomé la libertad de usarlo en una edición de mi respuesta que da un enfoque alternativo al truco de la reflexión 'interno '+ Java. –

Cuestiones relacionadas