-
Notifications
You must be signed in to change notification settings - Fork 8
/
reflect.clj
143 lines (128 loc) · 4.94 KB
/
reflect.clj
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
(ns lambdaisland.witchcraft.reflect
(:require [clojure.string :as str]
[lambdaisland.witchcraft.classpath-hacks :as classpath-hacks])
(:import (org.reflections Reflections Store)
(org.reflections.util ConfigurationBuilder
ClasspathHelper
FilterBuilder)
(javassist.bytecode MethodInfo ClassFile)
(java.lang.reflect Modifier)
(org.reflections.vfs Vfs Vfs$File)
(org.reflections.adapters MetadataAdapter)
(org.reflections.scanners Scanner
AbstractScanner
TypeElementsScanner
SubTypesScanner
MethodParameterScanner)))
(set! *warn-on-reflection* true)
(def packages
["org.bukkit"
"net.kyori"
#_"net.minecraft"
"co.aikar"
"com.destroystokyo"
"io.papermc"
"org.spigotmc"
"net.glowstone"
"net.citizensnpcs"])
(defn assoc-sig [m sig klassname]
(assoc! m sig ((fnil conj #{}) (get m sig) klassname)))
(defn classloaders []
;; shenanigans because paper's pluginclassloader hides what its parents
;; provide
(let [plugin-loader (when-let [instance-var (resolve 'lambdaisland.witchcraft.plugin/instance)]
(.getClassLoader (.getClass ^Object @@instance-var)))]
(cond-> [(classpath-hacks/context-classloader)]
plugin-loader
(conj plugin-loader
(.getParent plugin-loader)))))
(defn reflect-config ^ConfigurationBuilder []
(let [config (ConfigurationBuilder.)
loaders (into-array ClassLoader (classloaders))]
(doseq [pkg packages]
(.addUrls config (ClasspathHelper/forPackage pkg loaders)))
config))
(defn load-reflections []
(let [config (reflect-config)
files (mapcat #(.getFiles (Vfs/fromURL %)) (.getUrls config))
adapt (.getMetadataAdapter config)]
(persistent!
(reduce
(fn [m ^Vfs$File file]
(if (.acceptsInput adapt (.getRelativePath file))
(let [^ClassFile classfile (.getOrCreateClassObject adapt file)
klassname (.getClassName adapt classfile)]
(if-not (and (Modifier/isPublic (.getAccessFlags classfile))
(some #(.startsWith klassname %) packages))
m
(reduce
(fn [m method]
(if (.isPublic adapt method)
(-> m
(assoc-sig
(str
(when (Modifier/isStatic (.getAccessFlags ^MethodInfo method))
"static ")
(.getMethodName adapt method)
"("
(apply str
(interpose ","
(.getParameterNames adapt method)))
")")
klassname)
(assoc-sig
(str
(when (Modifier/isStatic (.getAccessFlags ^MethodInfo method))
"static ")
(.getReturnTypeName adapt method)
" "
(.getMethodName adapt method)
"("
(apply str
(interpose ","
(.getParameterNames adapt method)))
")")
klassname))
m))
m
(.getMethods adapt classfile))))
m))
(transient {})
files))))
(defonce reflections
(delay (load-reflections)))
(defn exclude-descendants
"We don't extend a protocol to a type if a supertype of the type already
implements the given method, this mainly means that we extend Bukkit interface
like Block, and not concrete implementations, like CraftBlock, unless the
latter has methods we care about that are not provided by an interface."
[klasses]
(remove
(fn [klass]
(try
(some (set klasses) (map (memfn ^Class getName)
(ancestors (Class/forName klass))))
(catch Error _
true)))
klasses))
(defmacro extend-signatures
{:style/indent [1 :form [1]]}
[protocol & sig-impl]
`(extend-protocol ~protocol
~@(as-> sig-impl $
(for [[sig impl] (partition 2 $)
klass (exclude-descendants (get @reflections sig))]
{:class (symbol klass)
:fntail impl})
(group-by :class $)
(for [[klass impls] $
form (cons klass (map :fntail impls))]
form))))
(comment
(filter #(re-find #"openInventory\(" (key %)) @reflections)
(exclude-descendants
(get @reflections "getInventory()"))
(ancestors (Class/forName "org.bukkit.entity.HumanEntity"))
(load-reflections)
(clojure.reflect/reflect org.bukkit.inventory.Inventory)
)