-
Notifications
You must be signed in to change notification settings - Fork 20
/
ReflectionPlume.java
506 lines (469 loc) · 18.6 KB
/
ReflectionPlume.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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
// If you edit this file, you must also edit its tests.
// For tests of this and the entire plume package, see class TestPlume.
package org.plumelib.util;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.List;
import java.util.StringJoiner;
import java.util.StringTokenizer;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.ClassGetName;
import org.checkerframework.checker.signature.qual.ClassGetSimpleName;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.dataflow.qual.Pure;
import org.plumelib.reflection.Signatures;
/**
* Utility functions related to reflection, Class, Method, ClassLoader, and classpath.
*
* @deprecated use org.plumelib.reflection.ReflectionPlume
*/
@Deprecated // use org.plumelib.reflection.ReflectionPlume
public final class ReflectionPlume {
/** This class is a collection of methods; it does not represent anything. */
private ReflectionPlume() {
throw new Error("do not instantiate");
}
///////////////////////////////////////////////////////////////////////////
/// Class
///
/**
* Return true iff sub is a subtype of sup. If sub == sup, then sub is considered a subtype of sub
* and this method returns true.
*
* @param sub class to test for being a subtype
* @param sup class to test for being a supertype
* @return true iff sub is a subtype of sup
*/
@SuppressWarnings({
"all:purity.not.deterministic.call", // getInterfaces() is used as a set
"lock:method.guarantee.violated" // getInterfaces() is used as a set
})
@Pure
public static boolean isSubtype(Class<?> sub, Class<?> sup) {
if (sub == sup) {
return true;
}
// Handle superclasses
Class<?> parent = sub.getSuperclass();
// If parent == null, sub == Object
if ((parent != null) && (parent == sup || isSubtype(parent, sup))) {
return true;
}
// Handle interfaces
for (Class<?> ifc : sub.getInterfaces()) {
if (ifc == sup || isSubtype(ifc, sup)) {
return true;
}
}
return false;
}
/** Used by {@link #classForName}. */
private static HashMap<String, Class<?>> primitiveClasses = new HashMap<>(8);
static {
primitiveClasses.put("boolean", Boolean.TYPE);
primitiveClasses.put("byte", Byte.TYPE);
primitiveClasses.put("char", Character.TYPE);
primitiveClasses.put("double", Double.TYPE);
primitiveClasses.put("float", Float.TYPE);
primitiveClasses.put("int", Integer.TYPE);
primitiveClasses.put("long", Long.TYPE);
primitiveClasses.put("short", Short.TYPE);
}
// TODO: Should create a method that handles any ClassGetName (including primitives), but not
// fully-qualified names. A routine with a polymorphic parameter type is confusing.
/**
* Like {@link Class#forName(String)}, but also works when the string represents a primitive type
* or a fully-qualified name (as opposed to a binary name).
*
* <p>If the given name can't be found, this method changes the last '.' to a dollar sign ($) and
* tries again. This accounts for inner classes that are incorrectly passed in in fully-qualified
* format instead of binary format. (It should try multiple dollar signs, not just at the last
* position.)
*
* <p>Recall the rather odd specification for {@link Class#forName(String)}: the argument is a
* binary name for non-arrays, but a field descriptor for arrays. This method uses the same rules,
* but additionally handles primitive types and, for non-arrays, fully-qualified names.
*
* @param className name of the class
* @return the Class corresponding to className
* @throws ClassNotFoundException if the class is not found
*/
// The annotation encourages proper use, even though this can take a
// fully-qualified name (only for a non-array).
public static Class<?> classForName(@ClassGetName String className)
throws ClassNotFoundException {
Class<?> result = primitiveClasses.get(className);
if (result != null) {
return result;
} else {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
int pos = className.lastIndexOf('.');
if (pos < 0) {
throw e;
}
@SuppressWarnings("signature") // checked below & exception is handled
@ClassGetName String innerName = className.substring(0, pos) + "$" + className.substring(pos + 1);
try {
return Class.forName(innerName);
} catch (ClassNotFoundException ee) {
throw e;
}
}
}
}
/**
* Returns the simple unqualified class name that corresponds to the specified fully qualified
* name. For example, if qualifiedName is java.lang.String, String will be returned.
*
* @param qualifiedName the fully-qualified name of a class
* @return the simple unqualified name of the class
*/
// TODO: does not follow the specification for inner classes (where the
// type name should be empty), but I think this is more informative anyway.
@SuppressWarnings("signature") // string conversion
public static @ClassGetSimpleName String fullyQualifiedNameToSimpleName(
@FullyQualifiedName String qualifiedName) {
int offset = qualifiedName.lastIndexOf('.');
if (offset == -1) {
return (qualifiedName);
}
return (qualifiedName.substring(offset + 1));
}
///////////////////////////////////////////////////////////////////////////
/// ClassLoader
///
/**
* This static nested class has no purpose but to define defineClassFromFile.
* ClassLoader.defineClass is protected, so I subclass ClassLoader in order to call defineClass.
*/
private static class PromiscuousLoader extends ClassLoader {
/**
* Converts the bytes in a file into an instance of class Class, and also resolves (links) the
* class. Delegates the real work to defineClass.
*
* @see ClassLoader#defineClass(String,byte[],int,int)
* @param className the expected binary name of the class to define, or null if not known
* @param pathname the file from which to load the class
* @return the {@code Class} object that was created
* @throws FileNotFoundException if the file does not exist
* @throws IOException if there is trouble reading the file
*/
public Class<?> defineClassFromFile(@BinaryName String className, String pathname)
throws FileNotFoundException, IOException {
FileInputStream fi = new FileInputStream(pathname);
int numbytes = fi.available();
byte[] classBytes = new byte[numbytes];
int bytesRead = fi.read(classBytes);
fi.close();
if (bytesRead < numbytes) {
throw new Error(
String.format(
"Expected to read %d bytes from %s, got %d", numbytes, pathname, bytesRead));
}
Class<?> return_class = defineClass(className, classBytes, 0, numbytes);
resolveClass(return_class); // link the class
return return_class;
}
}
/** A ClassLoader that can call defineClassFromFile. */
private static PromiscuousLoader thePromiscuousLoader = new PromiscuousLoader();
/**
* Converts the bytes in a file into an instance of class Class, and resolves (links) the class.
* Like {@link ClassLoader#defineClass(String,byte[],int,int)}, but takes a file name rather than
* an array of bytes as an argument, and also resolves (links) the class.
*
* @see ClassLoader#defineClass(String,byte[],int,int)
* @param className the name of the class to define, or null if not known
* @param pathname the pathname of a .class file
* @return a Java Object corresponding to the Class defined in the .class file
* @throws FileNotFoundException if the file cannot be found
* @throws IOException if there is trouble reading the file
*/
// Also throws UnsupportedClassVersionError and some other exceptions.
public static Class<?> defineClassFromFile(@BinaryName String className, String pathname)
throws FileNotFoundException, IOException {
return thePromiscuousLoader.defineClassFromFile(className, pathname);
}
///////////////////////////////////////////////////////////////////////////
/// Classpath
///
// Perhaps abstract out the simpler addToPath from this
/**
* Add the directory to the system classpath.
*
* @param dir directory to add to the system classpath
*/
public static void addToClasspath(String dir) {
// If the dir isn't on CLASSPATH, add it.
String pathSep = System.getProperty("path.separator");
// what is the point of the "replace()" call?
String cp = System.getProperty("java.class.path", ".").replace('\\', '/');
StringTokenizer tokenizer = new StringTokenizer(cp, pathSep, false);
boolean found = false;
while (tokenizer.hasMoreTokens() && !found) {
found = tokenizer.nextToken().equals(dir);
}
if (!found) {
System.setProperty("java.class.path", dir + pathSep + cp);
}
}
/**
* Returns the classpath as a multi-line string.
*
* @return the classpath as a multi-line string
*/
public static String classpathToString() {
StringJoiner result = new StringJoiner(System.lineSeparator());
ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
result.add(url.getFile());
}
return result.toString();
}
///////////////////////////////////////////////////////////////////////////
/// Method
///
/**
* Maps from a comma-delimited string of arg types, such as appears in a method signature, to an
* array of Class objects, one for each arg type. Example keys include: "java.lang.String,
* java.lang.String, java.lang.Class[]" and "int,int".
*/
static HashMap<String, Class<?>[]> args_seen = new HashMap<>();
/**
* Given a method signature, return the method.
*
* <p>Example calls are:
*
* <pre>
* UtilPlume.methodForName("org.plumelib.util.UtilPlume.methodForName(java.lang.String, java.lang.String, java.lang.Class[])")
* UtilPlume.methodForName("org.plumelib.util.UtilPlume.methodForName(java.lang.String,java.lang.String,java.lang.Class[])")
* UtilPlume.methodForName("java.lang.Math.min(int,int)")
* </pre>
*
* @param method a method signature
* @return the method corresponding to the given signature
* @throws ClassNotFoundException if the class is not found
* @throws NoSuchMethodException if the method is not found
*/
public static Method methodForName(String method)
throws ClassNotFoundException, NoSuchMethodException, SecurityException {
int oparenpos = method.indexOf('(');
int dotpos = method.lastIndexOf('.', oparenpos);
int cparenpos = method.indexOf(')', oparenpos);
if ((dotpos == -1) || (oparenpos == -1) || (cparenpos == -1)) {
throw new Error(
"malformed method name should contain a period, open paren, and close paren: "
+ method
+ " <<"
+ dotpos
+ ","
+ oparenpos
+ ","
+ cparenpos
+ ">>");
}
for (int i = cparenpos + 1; i < method.length(); i++) {
if (!Character.isWhitespace(method.charAt(i))) {
throw new Error(
"malformed method name should contain only whitespace following close paren");
}
}
@SuppressWarnings("signature") // throws exception if class does not exist
@BinaryName String classname = method.substring(0, dotpos);
String methodname = method.substring(dotpos + 1, oparenpos);
String all_argnames = method.substring(oparenpos + 1, cparenpos).trim();
Class<?>[] argclasses = args_seen.get(all_argnames);
if (argclasses == null) {
String[] argnames;
if (all_argnames.equals("")) {
argnames = new String[0];
} else {
argnames = UtilPlume.split(all_argnames, ',');
}
@MonotonicNonNull Class<?>[] argclasses_tmp = new Class<?>[argnames.length];
for (int i = 0; i < argnames.length; i++) {
String bnArgname = argnames[i].trim();
@SuppressWarnings("signature") // string manipulation: extract part of signature
@ClassGetName String cgnArgname = Signatures.binaryNameToClassGetName(bnArgname);
argclasses_tmp[i] = classForName(cgnArgname);
}
Class<?>[] argclasses_res = (@NonNull Class<?>[]) argclasses_tmp;
argclasses = argclasses_res;
args_seen.put(all_argnames, argclasses_res);
}
return methodForName(classname, methodname, argclasses);
}
/**
* Given a class name and a method name in that class, return the method.
*
* @param classname class in which to find the method
* @param methodname the method name
* @param params the parameters of the method
* @return the method named classname.methodname with parameters params
* @throws ClassNotFoundException if the class is not found
* @throws NoSuchMethodException if the method is not found
*/
public static Method methodForName(
@BinaryName String classname, String methodname, Class<?>[] params)
throws ClassNotFoundException, NoSuchMethodException, SecurityException {
Class<?> c = Class.forName(classname);
Method m = c.getDeclaredMethod(methodname, params);
return m;
}
///////////////////////////////////////////////////////////////////////////
/// Reflection
///
// TODO: add method invokeMethod; see
// java/Translation/src/graph/tests/Reflect.java (but handle returning a
// value).
// TODO: make this restore the access to its original value, such as private?
/**
* Sets the given field, which may be final and/or private. Leaves the field accessible. Intended
* for use in readObject and nowhere else!
*
* @param o object in which to set the field
* @param fieldName name of field to set
* @param value new value of field
* @throws NoSuchFieldException if the field does not exist in the object
*/
public static void setFinalField(Object o, String fieldName, @Nullable Object value)
throws NoSuchFieldException {
Class<?> c = o.getClass();
while (c != Object.class) { // Class is interned
// System.out.printf ("Setting field %s in %s%n", fieldName, c);
try {
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
f.set(o, value);
return;
} catch (NoSuchFieldException e) {
if (c.getSuperclass() == Object.class) { // Class is interned
throw e;
}
} catch (IllegalAccessException e) {
throw new Error("This can't happen: " + e);
}
c = c.getSuperclass();
assert c != null : "@AssumeAssertion(nullness): c was not Object, so is not null now";
}
throw new NoSuchFieldException(fieldName);
}
// TODO: make this restore the access to its original value, such as private?
/**
* Reads the given field, which may be private. Leaves the field accessible. Use with care!
*
* @param o object in which to set the field
* @param fieldName name of field to set
* @return new value of field
* @throws NoSuchFieldException if the field does not exist in the object
*/
public static @Nullable Object getPrivateField(Object o, String fieldName)
throws NoSuchFieldException {
Class<?> c = o.getClass();
while (c != Object.class) { // Class is interned
// System.out.printf ("Setting field %s in %s%n", fieldName, c);
try {
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(o);
} catch (IllegalAccessException e) {
System.out.println("in getPrivateField, IllegalAccessException: " + e);
throw new Error("This can't happen: " + e);
} catch (NoSuchFieldException e) {
if (c.getSuperclass() == Object.class) { // Class is interned
throw e;
}
// nothing to do; will now examine superclass
}
c = c.getSuperclass();
assert c != null : "@AssumeAssertion(nullness): c was not Object, so is not null now";
}
throw new NoSuchFieldException(fieldName);
}
/**
* Returns the least upper bound of the given classes.
*
* @param a a class
* @param b a class
* @return the least upper bound of the two classes, or null if both are null
*/
public static @Nullable Class<?> leastUpperBound(@Nullable Class<?> a, @Nullable Class<?> b) {
if (a == b) {
return a;
} else if (a == null) {
return b;
} else if (b == null) {
return a;
} else if (a == Void.TYPE) {
return b;
} else if (b == Void.TYPE) {
return a;
} else if (a.isAssignableFrom(b)) {
return a;
} else if (b.isAssignableFrom(a)) {
return b;
} else {
// There may not be a unique least upper bound.
// Probably return some specific class rather than a wildcard.
throw new Error("Not yet implemented");
}
}
/**
* Returns the least upper bound of all the given classes.
*
* @param classes a non-empty list of classes
* @return the least upper bound of all the given classes
*/
public static @Nullable Class<?> leastUpperBound(@Nullable Class<?>[] classes) {
Class<?> result = null;
for (Class<?> clazz : classes) {
result = leastUpperBound(result, clazz);
}
return result;
}
/**
* Returns the least upper bound of the classes of the given objects.
*
* @param objects a list of objects
* @return the least upper bound of the classes of the given objects, or null if all arguments are
* null
*/
public static @Nullable Class<?> leastUpperBound(@PolyNull Object[] objects) {
Class<?> result = null;
for (Object obj : objects) {
if (obj != null) {
result = leastUpperBound(result, obj.getClass());
}
}
return result;
}
/**
* Returns the least upper bound of the classes of the given objects.
*
* @param objects a non-empty list of objects
* @return the least upper bound of the classes of the given objects, or null if all arguments are
* null
*/
public static @Nullable Class<?> leastUpperBound(List<? extends @Nullable Object> objects) {
Class<?> result = null;
for (Object obj : objects) {
if (obj != null) {
result = leastUpperBound(result, obj.getClass());
}
}
return result;
}
}