blob: 70abede2d1c976d3e6209b6bc04a94a4ac558faa [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.bnd.compatibility;
2
3import java.lang.reflect.*;
4import java.util.*;
5
6/**
7 * This class is compiled against 1.5 or later to provide access to the generic
8 * signatures. It can convert a Class, Field, Method or constructor to a generic
9 * signature and it can normalize a signature. Both are methods. Normalized
10 * signatures can be string compared and match even if the type variable names
11 * differ.
12 *
13 * @version $Id$
14 */
15public class Signatures {
Stuart McCulloch2286f232012-06-15 13:27:53 +000016
Stuart McCullochbb014372012-06-07 21:57:32 +000017 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +000018 * Check if the environment has generics, i.e. later than Java 5 VM.
Stuart McCullochbb014372012-06-07 21:57:32 +000019 *
20 * @return true if generics are supported
21 * @throws Exception
22 */
23 public boolean hasGenerics() throws Exception {
24 try {
Stuart McCulloch2286f232012-06-15 13:27:53 +000025 call(Signatures.class, "getGenericSuperClass");
Stuart McCullochbb014372012-06-07 21:57:32 +000026 return true;
Stuart McCulloch2286f232012-06-15 13:27:53 +000027 }
28 catch (NoSuchMethodException mnfe) {
Stuart McCullochbb014372012-06-07 21:57:32 +000029 return false;
30 }
31 }
Stuart McCulloch2286f232012-06-15 13:27:53 +000032
Stuart McCullochbb014372012-06-07 21:57:32 +000033 /**
34 * Helper class to track an index in a string.
35 */
36 static class Rover {
37 final String s;
38 int i;
39
40 public Rover(String s) {
41 this.s = s;
42 i = 0;
43 }
44
45 char peek() {
46 return s.charAt(i);
47 }
48
49 char take() {
50 return s.charAt(i++);
51 }
52
53 char take(char c) {
54 char x = s.charAt(i++);
55 if (c != x)
Stuart McCulloch2286f232012-06-15 13:27:53 +000056 throw new IllegalStateException("get() expected " + c + " but got + " + x);
Stuart McCullochbb014372012-06-07 21:57:32 +000057 return x;
58 }
59
60 public String upTo(String except) {
61 int start = i;
62 while (except.indexOf(peek()) < 0)
63 take();
64 return s.substring(start, i);
65 }
66
67 public boolean isEOF() {
68 return i >= s.length();
69 }
70
71 }
72
73 /**
74 * Calculate the generic signature of a Class,Method,Field, or Constructor.
Stuart McCulloch2286f232012-06-15 13:27:53 +000075 *
Stuart McCullochbb014372012-06-07 21:57:32 +000076 * @param f
77 * @return
Stuart McCulloch2286f232012-06-15 13:27:53 +000078 * @throws Exception
Stuart McCullochbb014372012-06-07 21:57:32 +000079 */
80 public String getSignature(Object c) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +000081 if (c instanceof Class< ? >)
82 return getSignature((Class< ? >) c);
83 if (c instanceof Constructor< ? >)
84 return getSignature((Constructor< ? >) c);
85 if (c instanceof Method)
86 return getSignature((Method) c);
87 if (c instanceof Field)
88 return getSignature((Field) c);
89
Stuart McCullochbb014372012-06-07 21:57:32 +000090 throw new IllegalArgumentException(c.toString());
91 }
92
93 /**
94 * Calculate the generic signature of a Class. A Class consists of:
95 *
96 * <pre>
97 * class ::= declaration? reference reference*
98 * </pre>
99 *
Stuart McCullochbb014372012-06-07 21:57:32 +0000100 * @param f
101 * @return
Stuart McCulloch2286f232012-06-15 13:27:53 +0000102 * @throws Exception
Stuart McCullochbb014372012-06-07 21:57:32 +0000103 */
104 public String getSignature(Class< ? > c) throws Exception {
105 StringBuilder sb = new StringBuilder();
106 declaration(sb, c);
107 reference(sb, call(c, "getGenericSuperclass"));
Stuart McCulloch2286f232012-06-15 13:27:53 +0000108 for (Object type : (Object[]) call(c, "getGenericInterfaces")) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000109 reference(sb, type);
110 }
111 return sb.toString();
112 }
113
114 /**
115 * Calculate the generic signature of a Method. A Method consists of:
116 *
117 * <pre>
118 * method ::= declaration? '(' reference* ')' reference
119 * </pre>
120 *
121 * @param c
122 * @return
Stuart McCulloch2286f232012-06-15 13:27:53 +0000123 * @throws Exception
Stuart McCullochbb014372012-06-07 21:57:32 +0000124 */
125 public String getSignature(Method m) throws Exception {
126 StringBuilder sb = new StringBuilder();
127 declaration(sb, m);
128 sb.append('(');
Stuart McCulloch2286f232012-06-15 13:27:53 +0000129 for (Object type : (Object[]) call(m, "getGenericParameterTypes")) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000130 reference(sb, type);
131 }
132 sb.append(')');
Stuart McCulloch2286f232012-06-15 13:27:53 +0000133 reference(sb, call(m, "getGenericReturnType"));
Stuart McCullochbb014372012-06-07 21:57:32 +0000134 return sb.toString();
135 }
136
137 /**
138 * Calculate the generic signature of a Constructor. A Constructor consists
139 * of:
140 *
141 * <pre>
142 * constructor ::= declaration? '(' reference* ')V'
143 * </pre>
144 *
145 * @param c
146 * @return
Stuart McCulloch2286f232012-06-15 13:27:53 +0000147 * @throws Exception
Stuart McCullochbb014372012-06-07 21:57:32 +0000148 */
149 public String getSignature(Constructor< ? > c) throws Exception {
150 StringBuilder sb = new StringBuilder();
151 declaration(sb, c);
152 sb.append('(');
Stuart McCulloch2286f232012-06-15 13:27:53 +0000153 for (Object type : (Object[]) call(c, "getGenericParameterTypes")) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000154 reference(sb, type);
155 }
156 sb.append(')');
157 reference(sb, void.class);
158 return sb.toString();
159 }
160
161 /**
162 * Calculate the generic signature of a Field. A Field consists of:
163 *
164 * <pre>
165 * constructor ::= reference
166 * </pre>
167 *
168 * @param c
169 * @return
Stuart McCulloch2286f232012-06-15 13:27:53 +0000170 * @throws Exception
Stuart McCullochbb014372012-06-07 21:57:32 +0000171 */
172 public String getSignature(Field f) throws Exception {
173 StringBuilder sb = new StringBuilder();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000174 Object t = call(f, "getGenericType");
Stuart McCullochbb014372012-06-07 21:57:32 +0000175 reference(sb, t);
176 return sb.toString();
177 }
178
179/**
180 * Classes, Methods, or Constructors can have a declaration that provides
181 * nested a scope for type variables. A Method/Constructor inherits
182 * the type variables from its class and a class inherits its type variables
183 * from its outer class. The declaration consists of the following
184 * syntax:
185 * <pre>
186 * declarations ::= '<' declaration ( ',' declaration )* '>'
187 * declaration ::= identifier ':' declare
188 * declare ::= types | variable
189 * types ::= ( 'L' class ';' )? ( ':' 'L' interface ';' )*
190 * variable ::= 'T' id ';'
191 * </pre>
192 *
193 * @param sb
194 * @param gd
195 * @throws Exception
196 */
197 private void declaration(StringBuilder sb, Object gd) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000198 Object[] typeParameters = (Object[]) call(gd, "getTypeParameters");
Stuart McCullochbb014372012-06-07 21:57:32 +0000199 if (typeParameters.length > 0) {
200 sb.append('<');
201 for (Object tv : typeParameters) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000202 sb.append(call(tv, "getName"));
Stuart McCullochbb014372012-06-07 21:57:32 +0000203
Stuart McCulloch2286f232012-06-15 13:27:53 +0000204 Object[] bounds = (Object[]) call(tv, "getBounds");
Stuart McCullochbb014372012-06-07 21:57:32 +0000205 if (bounds.length > 0 && isInterface(bounds[0])) {
206 sb.append(':');
207 }
208 for (int i = 0; i < bounds.length; i++) {
209 sb.append(':');
210 reference(sb, bounds[i]);
211 }
212 }
213 sb.append('>');
214 }
215 }
216
217 /**
218 * Verify that the type is an interface.
219 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000220 * @param type
221 * the type to check.
Stuart McCullochbb014372012-06-07 21:57:32 +0000222 * @return true if this is a class that is an interface or a Parameterized
223 * Type that is an interface
Stuart McCulloch2286f232012-06-15 13:27:53 +0000224 * @throws Exception
Stuart McCullochbb014372012-06-07 21:57:32 +0000225 */
226 private boolean isInterface(Object type) throws Exception {
227 if (type instanceof Class)
228 return (((Class< ? >) type).isInterface());
229
Stuart McCulloch2286f232012-06-15 13:27:53 +0000230 if (isInstance(type.getClass(), "java.lang.reflect.ParameterizedType"))
231 return isInterface(call(type, "getRawType"));
Stuart McCullochbb014372012-06-07 21:57:32 +0000232
233 return false;
234 }
235
Stuart McCullochbb014372012-06-07 21:57:32 +0000236/**
237 * This is the heart of the signature builder. A reference is used
238 * in a lot of places. It referes to another type.
239 * <pre>
240 * reference ::= array | class | primitive | variable
241 * array ::= '[' reference
242 * class ::= 'L' body ( '.' body )* ';'
243 * body ::= id ( '<' ( wildcard | reference )* '>' )?
244 * variable ::= 'T' id ';'
245 * primitive ::= PRIMITIVE
246 * </pre>
247 *
248 * @param sb
249 * @param t
250 * @throws Exception
251 */
252 private void reference(StringBuilder sb, Object t) throws Exception {
253
Stuart McCulloch2286f232012-06-15 13:27:53 +0000254 if (isInstance(t.getClass(), "java.lang.reflect.ParameterizedType")) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000255 sb.append('L');
256 parameterizedType(sb, t);
257 sb.append(';');
258 return;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000259 } else if (isInstance(t.getClass(), "java.lang.reflect.GenericArrayType")) {
260 sb.append('[');
261 reference(sb, call(t, "getGenericComponentType"));
262 } else if (isInstance(t.getClass(), "java.lang.reflect.WildcardType")) {
263 Object[] lowerBounds = (Object[]) call(t, "getLowerBounds");
264 Object[] upperBounds = (Object[]) call(t, "getUpperBounds");
Stuart McCullochbb014372012-06-07 21:57:32 +0000265
Stuart McCulloch2286f232012-06-15 13:27:53 +0000266 if (upperBounds.length == 1 && upperBounds[0] == Object.class)
267 upperBounds = new Object[0];
Stuart McCullochbb014372012-06-07 21:57:32 +0000268
Stuart McCulloch2286f232012-06-15 13:27:53 +0000269 if (upperBounds.length != 0) {
270 // extend
271 for (Object upper : upperBounds) {
272 sb.append('+');
273 reference(sb, upper);
Stuart McCullochbb014372012-06-07 21:57:32 +0000274 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000275 } else if (lowerBounds.length != 0) {
276 // super, can only be one by the language
277 for (Object lower : lowerBounds) {
278 sb.append('-');
279 reference(sb, lower);
280 }
281 } else
282 sb.append('*');
283 } else if (isInstance(t.getClass(), "java.lang.reflect.TypeVariable")) {
284 sb.append('T');
285 sb.append(call(t, "getName"));
286 sb.append(';');
287 } else if (t instanceof Class< ? >) {
288 Class< ? > c = (Class< ? >) t;
289 if (c.isPrimitive()) {
290 sb.append(primitive(c));
291 } else {
292 sb.append('L');
293 String name = c.getName().replace('.', '/');
294 sb.append(name);
295 sb.append(';');
296 }
297 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000298 }
299
300 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +0000301 * Creates the signature for a Parameterized Type. A Parameterized Type has
302 * a raw class and a set of type variables.
Stuart McCullochbb014372012-06-07 21:57:32 +0000303 *
304 * @param sb
305 * @param pt
Stuart McCulloch2286f232012-06-15 13:27:53 +0000306 * @throws Exception
Stuart McCullochbb014372012-06-07 21:57:32 +0000307 */
308 private void parameterizedType(StringBuilder sb, Object pt) throws Exception {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000309 Object owner = call(pt, "getOwnerType");
310 String name = ((Class< ? >) call(pt, "getRawType")).getName().replace('.', '/');
Stuart McCullochbb014372012-06-07 21:57:32 +0000311 if (owner != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000312 if (isInstance(owner.getClass(), "java.lang.reflect.ParameterizedType"))
Stuart McCullochbb014372012-06-07 21:57:32 +0000313 parameterizedType(sb, owner);
314 else
315 sb.append(((Class< ? >) owner).getName().replace('.', '/'));
316 sb.append('.');
317 int n = name.lastIndexOf('$');
318 name = name.substring(n + 1);
319 }
320 sb.append(name);
321
322 sb.append('<');
Stuart McCulloch2286f232012-06-15 13:27:53 +0000323 for (Object parameterType : (Object[]) call(pt, "getActualTypeArguments")) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000324 reference(sb, parameterType);
325 }
326 sb.append('>');
327
328 }
329
330 /**
331 * Handle primitives, these need to be translated to a single char.
332 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000333 * @param type
334 * the primitive class
Stuart McCullochbb014372012-06-07 21:57:32 +0000335 * @return the single char associated with the primitive
336 */
337 private char primitive(Class< ? > type) {
338 if (type == byte.class)
339 return 'B';
Stuart McCulloch2286f232012-06-15 13:27:53 +0000340 else if (type == char.class)
341 return 'C';
342 else if (type == double.class)
343 return 'D';
344 else if (type == float.class)
345 return 'F';
346 else if (type == int.class)
347 return 'I';
348 else if (type == long.class)
349 return 'J';
350 else if (type == short.class)
351 return 'S';
352 else if (type == boolean.class)
353 return 'Z';
354 else if (type == void.class)
355 return 'V';
Stuart McCullochbb014372012-06-07 21:57:32 +0000356 else
Stuart McCulloch2286f232012-06-15 13:27:53 +0000357 throw new IllegalArgumentException("Unknown primitive type " + type);
Stuart McCullochbb014372012-06-07 21:57:32 +0000358 }
359
360 /**
361 * Normalize a signature to make sure the name of the variables are always
362 * the same. We change the names of the type variables to _n, where n is an
363 * integer. n is incremented for every new name and already used names are
364 * replaced with the _n name.
365 *
366 * @return a normalized signature
367 */
368
369 public String normalize(String signature) {
370 StringBuilder sb = new StringBuilder();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000371 Map<String,String> map = new HashMap<String,String>();
Stuart McCullochbb014372012-06-07 21:57:32 +0000372 Rover rover = new Rover(signature);
373 declare(sb, map, rover);
374
375 if (rover.peek() == '(') {
376 // method or constructor
377 sb.append(rover.take('('));
378 while (rover.peek() != ')') {
379 reference(sb, map, rover, true);
380 }
381 sb.append(rover.take(')'));
382 reference(sb, map, rover, true); // return type
Stuart McCulloch2286f232012-06-15 13:27:53 +0000383 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000384 // field or class
385 reference(sb, map, rover, true); // field type or super class
386 while (!rover.isEOF()) {
387 reference(sb, map, rover, true); // interfaces
388 }
389 }
390 return sb.toString();
391 }
392
393 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +0000394 * The heart of the routine. Handle a reference to a type. Can be an array,
395 * a class, a type variable, or a primitive.
Stuart McCullochbb014372012-06-07 21:57:32 +0000396 *
397 * @param sb
398 * @param map
399 * @param rover
400 * @param primitivesAllowed
401 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000402 private void reference(StringBuilder sb, Map<String,String> map, Rover rover, boolean primitivesAllowed) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000403
404 char type = rover.take();
405 sb.append(type);
406
407 if (type == '[') {
408 reference(sb, map, rover, true);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000409 } else if (type == 'L') {
410 String fqnb = rover.upTo("<;.");
411 sb.append(fqnb);
412 body(sb, map, rover);
413 while (rover.peek() == '.') {
414 sb.append(rover.take('.'));
415 sb.append(rover.upTo("<;."));
Stuart McCullochbb014372012-06-07 21:57:32 +0000416 body(sb, map, rover);
Stuart McCullochbb014372012-06-07 21:57:32 +0000417 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000418 sb.append(rover.take(';'));
419 } else if (type == 'T') {
420 String name = rover.upTo(";");
421 name = assign(map, name);
422 sb.append(name);
423 sb.append(rover.take(';'));
424 } else {
425 if (!primitivesAllowed)
426 throw new IllegalStateException("Primitives are not allowed without an array");
427 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000428 }
429
430 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +0000431 * Because classes can be nested the body handles the part that can be
432 * nested, the reference handles the enclosing L ... ;
Stuart McCullochbb014372012-06-07 21:57:32 +0000433 *
434 * @param sb
435 * @param map
436 * @param rover
437 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000438 private void body(StringBuilder sb, Map<String,String> map, Rover rover) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000439 if (rover.peek() == '<') {
440 sb.append(rover.take('<'));
441 while (rover.peek() != '>') {
442 switch (rover.peek()) {
443 case 'L' :
444 case '[' :
445 reference(sb, map, rover, false);
446 break;
447
448 case 'T' :
449 String name;
450 sb.append(rover.take('T')); // 'T'
451 name = rover.upTo(";");
452 sb.append(assign(map, name));
453 sb.append(rover.take(';'));
454 break;
455
456 case '+' : // extends
457 case '-' : // super
458 sb.append(rover.take());
459 reference(sb, map, rover, false);
460 break;
461
462 case '*' : // wildcard
463 sb.append(rover.take());
464 break;
465
466 }
467 }
468 sb.append(rover.take('>'));
469 }
470 }
471
472 /**
473 * Handle the declaration part.
474 *
475 * @param sb
476 * @param map
477 * @param rover
478 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000479 private void declare(StringBuilder sb, Map<String,String> map, Rover rover) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000480 char c = rover.peek();
481 if (c == '<') {
482 sb.append(rover.take('<'));
483
484 while (rover.peek() != '>') {
485 String name = rover.upTo(":");
486 name = assign(map, name);
487 sb.append(name);
488 typeVar: while (rover.peek() == ':') {
489 sb.append(rover.take(':'));
490 switch (rover.peek()) {
491 case ':' : // empty class cases
492 continue typeVar;
493
494 default :
495 reference(sb, map, rover, false);
496 break;
497 }
498 }
499 }
500 sb.append(rover.take('>'));
501 }
502 }
503
504 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +0000505 * Handles the assignment of type variables to index names so that we have a
506 * normalized name for each type var.
Stuart McCullochbb014372012-06-07 21:57:32 +0000507 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000508 * @param map
509 * the map with variables.
510 * @param name
511 * The name of the variable
Stuart McCullochbb014372012-06-07 21:57:32 +0000512 * @return the index name, like _1
513 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000514 private String assign(Map<String,String> map, String name) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000515 if (map.containsKey(name))
516 return map.get(name);
Stuart McCullochd4826102012-06-26 16:34:24 +0000517 int n = map.size();
518 map.put(name, "_" + n);
519 return "_" + n;
Stuart McCullochbb014372012-06-07 21:57:32 +0000520 }
521
Stuart McCulloch2286f232012-06-15 13:27:53 +0000522 private boolean isInstance(Class< ? > type, String string) {
523 if (type == null)
Stuart McCullochbb014372012-06-07 21:57:32 +0000524 return false;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000525
526 if (type.getName().equals(string))
Stuart McCullochbb014372012-06-07 21:57:32 +0000527 return true;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000528
529 if (isInstance(type.getSuperclass(), string))
Stuart McCullochbb014372012-06-07 21:57:32 +0000530 return true;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000531
532 for (Class< ? > intf : type.getInterfaces()) {
533 if (isInstance(intf, string))
Stuart McCullochbb014372012-06-07 21:57:32 +0000534 return true;
535 }
536 return false;
537 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000538
Stuart McCullochbb014372012-06-07 21:57:32 +0000539 private Object call(Object gd, String string) throws Exception {
540 Method m = gd.getClass().getMethod(string);
541 return m.invoke(gd);
542 }
543
544}