Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Stuart Halloway October 15, 2010
file 124 lines (100 sloc) 4.833 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.

(ns ^{:author "Stuart Halloway"
      :added "1.3"
      :doc "Reflection on Host Types
Alpha - subject to change.

Two main entry points:

* type-reflect reflects on something that implements TypeReference.
* reflect (for REPL use) reflects on the class of an instance, or
on a class if passed a class

Key features:

* Exposes the read side of reflection as pure data. Reflecting
on a type returns a map with keys :bases, :flags, and :members.

* Canonicalizes class names as Clojure symbols. Types can extend
to the TypeReference protocol to indicate that they can be
unambiguously resolved as a type name. The canonical format
requires one non-Java-ish convention: array brackets are <>
instead of [] so they can be part of a Clojure symbol.

* Pluggable Reflectors for different implementations. The default
JavaReflector is good when you have a class in hand, or use
the AsmReflector for \"hands off\" reflection without forcing
classes to load.

Platform implementers must:

* Create an implementation of Reflector.
* Create one or more implementations of TypeReference.
* def default-reflector to be an instance that satisfies Reflector."}
  clojure.reflect
  (:require [clojure.set :as set]))

(defprotocol Reflector
  "Protocol for reflection implementers."
  (do-reflect [reflector typeref]))

(defprotocol TypeReference
  "A TypeReference can be unambiguously converted to a type name on
the host platform.

All typerefs are normalized into symbols. If you need to
normalize a typeref yourself, call typesym."
  (typename [o] "Returns Java name as returned by ASM getClassName, e.g. byte[], java.lang.String[]"))

(declare default-reflector)

(defn type-reflect
  "Alpha - subject to change.
Reflect on a typeref, returning a map with :bases, :flags, and
:members. In the discussion below, names are always Clojure symbols.

:bases a set of names of the type's bases
:flags a set of keywords naming the boolean attributes
of the type.
:members a set of the type's members. Each membrer is a map
and can be a constructor, method, or field.

Keys common to all members:
:name name of the type
:declaring-class name of the declarer
:flags keyword naming boolean attributes of the member

Keys specific to constructors:
:parameter-types vector of parameter type names
:exception-types vector of exception type names

Key specific to methods:
:parameter-types vector of parameter type names
:exception-types vector of exception type names
:return-type return type name

Keys specific to fields:
:type type name

Options:

:ancestors in addition to the keys described above, also
include an :ancestors key with the entire set of
ancestors, and add all ancestor members to
:members.
:reflector implementation to use. Defaults to JavaReflector,
AsmReflector is also an option."
  {:added "1.3"}
  [typeref & options]
  (let [{:keys [ancestors reflector]}
        (merge {:reflector default-reflector}
               (apply hash-map options))
        refl (partial do-reflect reflector)
        result (refl typeref)]
    ;; could make simpler loop of two args: names an
    (if ancestors
      (let [make-ancestor-map (fn [names]
                            (zipmap names (map refl names)))]
        (loop [reflections (make-ancestor-map (:bases result))]
          (let [ancestors-visited (set (keys reflections))
                ancestors-to-visit (set/difference (set (mapcat :bases (vals reflections)))
                                               ancestors-visited)]
            (if (seq ancestors-to-visit)
              (recur (merge reflections (make-ancestor-map ancestors-to-visit)))
              (apply merge-with into result {:ancestors ancestors-visited}
                     (map #(select-keys % [:members]) (vals reflections)))))))
      result)))

(defn reflect
  "Alpha - subject to change.
Reflect on the type of obj (or obj itself if obj is a class).
Return value and options are the same as for type-reflect. "
  {:added "1.3"}
  [obj & options]
  (apply type-reflect (if (class? obj) obj (class obj)) options))

(load "reflect/java")
Something went wrong with that request. Please try again.