/
KlassNavigator.java
220 lines (196 loc) · 7.08 KB
/
KlassNavigator.java
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package org.kohsuke.stapler.lang;
import org.kohsuke.stapler.ClassDescriptor;
import org.kohsuke.stapler.Function;
import org.kohsuke.stapler.MetaClassLoader;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import edu.umd.cs.findbugs.annotations.NonNull;
/**
* Strategy pattern to provide navigation across class-like objects in other languages of JVM.
* <p>
* After removal of JRuby support, {@link #JAVA} is the only implementation.
* <p>
* Implementations should be stateless and typically a singleton.
*
* @param <C>
* Variable that represents the type of {@code Class} like object in this language.
*
* @author Kohsuke Kawaguchi
*/
public abstract class KlassNavigator<C> {
/**
* Loads the resources associated with this class.
*
* <p>
* In stapler, the convention is that the "associated" resources live in the directory named after
* the fully qualified class name (as opposed to the behavior of {@link Class#getResource(String)},
* that looks for resources in the same package as the class.)
*
* <p>
* But other languages can choose their own conventions if it makes more sense to do so.
* For example, stapler-jruby uses camelized class name.
*
* <p>
* Implementation must consult {@link MetaClassLoader#debugLoader} if it's available. Implementation
* must not look for resources in the base type. That operation is performed by the caller when
* needed.
*
* @return
* non-null if the resource is found. Otherwise null.
*/
public abstract URL getResource(C clazz, String resourceName);
/**
* Lists up all the ancestor classes, from specific to general, without any duplicate.
*
* This is used to look up a resource.
*/
public abstract Iterable<Klass<?>> getAncestors(C clazz);
/**
* Gets the super class.
*
* @return
* Can be null.
*/
public abstract Klass<?> getSuperClass(C clazz);
/**
* For backward compatibility, map the given class to the closest Java equivalent.
* In the worst case, this is Object.class
*/
public abstract Class toJavaClass(C clazz);
/**
* List methods of this class, regardless of access modifier.
*
* This list excludes methods from super classes.
* @since 1.220
*/
public abstract List<MethodRef> getDeclaredMethods(C clazz);
/**
* List fields of this class.
* This list excludes fields from super classes.
* @param clazz Class
* @return List of the fields declared for the class.
* By default this list is empty, {@link KlassNavigator} implementations are responsible to implement it.
* @since 1.246
*/
@NonNull
public List<FieldRef> getDeclaredFields(C clazz) {
return Collections.emptyList();
}
/**
* Reports all the methods that can be used for routing requests on this class.
* @param clazz Class
* @return List of the fields functions declared for the class.
* By default this list is empty, {@link KlassNavigator} implementations are responsible to implement it.
* @since 1.246
*/
@NonNull
public List<Function> getFunctions(C clazz) {
return Collections.emptyList();
}
/**
* If the given type is an array that supports index retrieval.
* @see #getArrayElement(Object, int)
*/
public boolean isArray(C clazz) {
Class j = toJavaClass(clazz);
return j.isArray() || List.class.isAssignableFrom(j);
}
/**
* Given an instance for which the type reported {@code isArray()==true}, obtains the element
* of the specified index.
* @see #isArray(Object)
*/
public Object getArrayElement(Object o, int index) throws IndexOutOfBoundsException {
if (o instanceof List)
return ((List)o).get(index);
return Array.get(o,index);
}
/**
* If the given type is a map/associative array type that supports lookup by a string key
*/
public boolean isMap(C clazz) {
return Map.class.isAssignableFrom(toJavaClass(clazz));
}
/**
* Given an instance for which the type reported {@code isMap()==true}, obtains the element
* of the specified index.
*/
public Object getMapElement(Object o, String key) {
return ((Map)o).get(key);
}
public static final KlassNavigator<Class> JAVA = new KlassNavigator<Class>() {
@Override
public URL getResource(Class clazz, String resourceName) {
ClassLoader cl = clazz.getClassLoader();
if (cl==null) return null;
String fullName;
if (resourceName.startsWith("/"))
fullName = resourceName.substring(1);
else
fullName = clazz.getName().replace('.','/').replace('$','/')+'/'+resourceName;
if (MetaClassLoader.debugLoader!=null) {
URL res = MetaClassLoader.debugLoader.loader.getResource(fullName);
if (res!=null) return res;
}
return cl.getResource(fullName);
}
@Override
public Klass<Class> getSuperClass(Class clazz) {
return Klass.java(clazz.getSuperclass());
}
@Override
public Iterable<Klass<?>> getAncestors(Class clazz) {
// TODO: shall we support interfaces?
List<Klass<?>> r = new ArrayList<>();
for (; clazz!=null; clazz=clazz.getSuperclass()) {
r.add(Klass.java(clazz));
}
return r;
}
@Override
public Class toJavaClass(Class clazz) {
return clazz;
}
@Override
public List<MethodRef> getDeclaredMethods(Class clazz) {
final Method[] methods = clazz.getDeclaredMethods();
return new AbstractList<MethodRef>() {
@Override
public MethodRef get(int index) {
return MethodRef.wrap(methods[index]);
}
@Override
public int size() {
return methods.length;
}
};
}
@Override
public List<FieldRef> getDeclaredFields(Class clazz) {
final Field[] fields = clazz.getDeclaredFields();
return new AbstractList<FieldRef>() {
@Override
public FieldRef get(int index) {
return FieldRef.wrap(fields[index]);
}
@Override
public int size() {
return fields.length;
}
};
}
@Override
public List<Function> getFunctions(Class clazz) {
// Historically ClassDescriptor used to own this non-trivial logic of computing
// valid functions for the class, so we'll keep it there.
return new ClassDescriptor(clazz).methods;
}
};
}