blob: 18b89a90adff8bd7e1d2eb9f42951fe2dd479fcb [file] [log] [blame]
Stuart McCullochf3173222012-06-07 21:57:32 +00001/*
2 * Copyright (c) OSGi Alliance (2009, 2010). All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package aQute.bnd.compatibility;
18
19import java.lang.reflect.*;
20import java.util.*;
21
22/**
23 * This class is compiled against 1.5 or later to provide access to the generic
24 * signatures. It can convert a Class, Field, Method or constructor to a generic
25 * signature and it can normalize a signature. Both are methods. Normalized
26 * signatures can be string compared and match even if the type variable names
27 * differ.
28 *
29 * @version $Id$
30 */
31public class Signatures {
Stuart McCulloch4482c702012-06-15 13:27:53 +000032
Stuart McCullochf3173222012-06-07 21:57:32 +000033 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +000034 * Check if the environment has generics, i.e. later than Java 5 VM.
Stuart McCullochf3173222012-06-07 21:57:32 +000035 *
36 * @return true if generics are supported
37 * @throws Exception
38 */
39 public boolean hasGenerics() throws Exception {
40 try {
Stuart McCulloch4482c702012-06-15 13:27:53 +000041 call(Signatures.class, "getGenericSuperClass");
Stuart McCullochf3173222012-06-07 21:57:32 +000042 return true;
Stuart McCulloch4482c702012-06-15 13:27:53 +000043 }
44 catch (NoSuchMethodException mnfe) {
Stuart McCullochf3173222012-06-07 21:57:32 +000045 return false;
46 }
47 }
Stuart McCulloch4482c702012-06-15 13:27:53 +000048
Stuart McCullochf3173222012-06-07 21:57:32 +000049 /**
50 * Helper class to track an index in a string.
51 */
52 static class Rover {
53 final String s;
54 int i;
55
56 public Rover(String s) {
57 this.s = s;
58 i = 0;
59 }
60
61 char peek() {
62 return s.charAt(i);
63 }
64
65 char take() {
66 return s.charAt(i++);
67 }
68
69 char take(char c) {
70 char x = s.charAt(i++);
71 if (c != x)
Stuart McCulloch4482c702012-06-15 13:27:53 +000072 throw new IllegalStateException("get() expected " + c + " but got + " + x);
Stuart McCullochf3173222012-06-07 21:57:32 +000073 return x;
74 }
75
76 public String upTo(String except) {
77 int start = i;
78 while (except.indexOf(peek()) < 0)
79 take();
80 return s.substring(start, i);
81 }
82
83 public boolean isEOF() {
84 return i >= s.length();
85 }
86
87 }
88
89 /**
90 * Calculate the generic signature of a Class,Method,Field, or Constructor.
Stuart McCulloch4482c702012-06-15 13:27:53 +000091 *
Stuart McCullochf3173222012-06-07 21:57:32 +000092 * @param f
93 * @return
Stuart McCulloch4482c702012-06-15 13:27:53 +000094 * @throws Exception
Stuart McCullochf3173222012-06-07 21:57:32 +000095 */
96 public String getSignature(Object c) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +000097 if (c instanceof Class< ? >)
98 return getSignature((Class< ? >) c);
99 if (c instanceof Constructor< ? >)
100 return getSignature((Constructor< ? >) c);
101 if (c instanceof Method)
102 return getSignature((Method) c);
103 if (c instanceof Field)
104 return getSignature((Field) c);
105
Stuart McCullochf3173222012-06-07 21:57:32 +0000106 throw new IllegalArgumentException(c.toString());
107 }
108
109 /**
110 * Calculate the generic signature of a Class. A Class consists of:
111 *
112 * <pre>
113 * class ::= declaration? reference reference*
114 * </pre>
115 *
Stuart McCullochf3173222012-06-07 21:57:32 +0000116 * @param f
117 * @return
Stuart McCulloch4482c702012-06-15 13:27:53 +0000118 * @throws Exception
Stuart McCullochf3173222012-06-07 21:57:32 +0000119 */
120 public String getSignature(Class< ? > c) throws Exception {
121 StringBuilder sb = new StringBuilder();
122 declaration(sb, c);
123 reference(sb, call(c, "getGenericSuperclass"));
Stuart McCulloch4482c702012-06-15 13:27:53 +0000124 for (Object type : (Object[]) call(c, "getGenericInterfaces")) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000125 reference(sb, type);
126 }
127 return sb.toString();
128 }
129
130 /**
131 * Calculate the generic signature of a Method. A Method consists of:
132 *
133 * <pre>
134 * method ::= declaration? '(' reference* ')' reference
135 * </pre>
136 *
137 * @param c
138 * @return
Stuart McCulloch4482c702012-06-15 13:27:53 +0000139 * @throws Exception
Stuart McCullochf3173222012-06-07 21:57:32 +0000140 */
141 public String getSignature(Method m) throws Exception {
142 StringBuilder sb = new StringBuilder();
143 declaration(sb, m);
144 sb.append('(');
Stuart McCulloch4482c702012-06-15 13:27:53 +0000145 for (Object type : (Object[]) call(m, "getGenericParameterTypes")) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000146 reference(sb, type);
147 }
148 sb.append(')');
Stuart McCulloch4482c702012-06-15 13:27:53 +0000149 reference(sb, call(m, "getGenericReturnType"));
Stuart McCullochf3173222012-06-07 21:57:32 +0000150 return sb.toString();
151 }
152
153 /**
154 * Calculate the generic signature of a Constructor. A Constructor consists
155 * of:
156 *
157 * <pre>
158 * constructor ::= declaration? '(' reference* ')V'
159 * </pre>
160 *
161 * @param c
162 * @return
Stuart McCulloch4482c702012-06-15 13:27:53 +0000163 * @throws Exception
Stuart McCullochf3173222012-06-07 21:57:32 +0000164 */
165 public String getSignature(Constructor< ? > c) throws Exception {
166 StringBuilder sb = new StringBuilder();
167 declaration(sb, c);
168 sb.append('(');
Stuart McCulloch4482c702012-06-15 13:27:53 +0000169 for (Object type : (Object[]) call(c, "getGenericParameterTypes")) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000170 reference(sb, type);
171 }
172 sb.append(')');
173 reference(sb, void.class);
174 return sb.toString();
175 }
176
177 /**
178 * Calculate the generic signature of a Field. A Field consists of:
179 *
180 * <pre>
181 * constructor ::= reference
182 * </pre>
183 *
184 * @param c
185 * @return
Stuart McCulloch4482c702012-06-15 13:27:53 +0000186 * @throws Exception
Stuart McCullochf3173222012-06-07 21:57:32 +0000187 */
188 public String getSignature(Field f) throws Exception {
189 StringBuilder sb = new StringBuilder();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000190 Object t = call(f, "getGenericType");
Stuart McCullochf3173222012-06-07 21:57:32 +0000191 reference(sb, t);
192 return sb.toString();
193 }
194
195/**
196 * Classes, Methods, or Constructors can have a declaration that provides
197 * nested a scope for type variables. A Method/Constructor inherits
198 * the type variables from its class and a class inherits its type variables
199 * from its outer class. The declaration consists of the following
200 * syntax:
201 * <pre>
202 * declarations ::= '<' declaration ( ',' declaration )* '>'
203 * declaration ::= identifier ':' declare
204 * declare ::= types | variable
205 * types ::= ( 'L' class ';' )? ( ':' 'L' interface ';' )*
206 * variable ::= 'T' id ';'
207 * </pre>
208 *
209 * @param sb
210 * @param gd
211 * @throws Exception
212 */
213 private void declaration(StringBuilder sb, Object gd) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000214 Object[] typeParameters = (Object[]) call(gd, "getTypeParameters");
Stuart McCullochf3173222012-06-07 21:57:32 +0000215 if (typeParameters.length > 0) {
216 sb.append('<');
217 for (Object tv : typeParameters) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000218 sb.append(call(tv, "getName"));
Stuart McCullochf3173222012-06-07 21:57:32 +0000219
Stuart McCulloch4482c702012-06-15 13:27:53 +0000220 Object[] bounds = (Object[]) call(tv, "getBounds");
Stuart McCullochf3173222012-06-07 21:57:32 +0000221 if (bounds.length > 0 && isInterface(bounds[0])) {
222 sb.append(':');
223 }
224 for (int i = 0; i < bounds.length; i++) {
225 sb.append(':');
226 reference(sb, bounds[i]);
227 }
228 }
229 sb.append('>');
230 }
231 }
232
233 /**
234 * Verify that the type is an interface.
235 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000236 * @param type
237 * the type to check.
Stuart McCullochf3173222012-06-07 21:57:32 +0000238 * @return true if this is a class that is an interface or a Parameterized
239 * Type that is an interface
Stuart McCulloch4482c702012-06-15 13:27:53 +0000240 * @throws Exception
Stuart McCullochf3173222012-06-07 21:57:32 +0000241 */
242 private boolean isInterface(Object type) throws Exception {
243 if (type instanceof Class)
244 return (((Class< ? >) type).isInterface());
245
Stuart McCulloch4482c702012-06-15 13:27:53 +0000246 if (isInstance(type.getClass(), "java.lang.reflect.ParameterizedType"))
247 return isInterface(call(type, "getRawType"));
Stuart McCullochf3173222012-06-07 21:57:32 +0000248
249 return false;
250 }
251
Stuart McCullochf3173222012-06-07 21:57:32 +0000252/**
253 * This is the heart of the signature builder. A reference is used
254 * in a lot of places. It referes to another type.
255 * <pre>
256 * reference ::= array | class | primitive | variable
257 * array ::= '[' reference
258 * class ::= 'L' body ( '.' body )* ';'
259 * body ::= id ( '<' ( wildcard | reference )* '>' )?
260 * variable ::= 'T' id ';'
261 * primitive ::= PRIMITIVE
262 * </pre>
263 *
264 * @param sb
265 * @param t
266 * @throws Exception
267 */
268 private void reference(StringBuilder sb, Object t) throws Exception {
269
Stuart McCulloch4482c702012-06-15 13:27:53 +0000270 if (isInstance(t.getClass(), "java.lang.reflect.ParameterizedType")) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000271 sb.append('L');
272 parameterizedType(sb, t);
273 sb.append(';');
274 return;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000275 } else if (isInstance(t.getClass(), "java.lang.reflect.GenericArrayType")) {
276 sb.append('[');
277 reference(sb, call(t, "getGenericComponentType"));
278 } else if (isInstance(t.getClass(), "java.lang.reflect.WildcardType")) {
279 Object[] lowerBounds = (Object[]) call(t, "getLowerBounds");
280 Object[] upperBounds = (Object[]) call(t, "getUpperBounds");
Stuart McCullochf3173222012-06-07 21:57:32 +0000281
Stuart McCulloch4482c702012-06-15 13:27:53 +0000282 if (upperBounds.length == 1 && upperBounds[0] == Object.class)
283 upperBounds = new Object[0];
Stuart McCullochf3173222012-06-07 21:57:32 +0000284
Stuart McCulloch4482c702012-06-15 13:27:53 +0000285 if (upperBounds.length != 0) {
286 // extend
287 for (Object upper : upperBounds) {
288 sb.append('+');
289 reference(sb, upper);
Stuart McCullochf3173222012-06-07 21:57:32 +0000290 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000291 } else if (lowerBounds.length != 0) {
292 // super, can only be one by the language
293 for (Object lower : lowerBounds) {
294 sb.append('-');
295 reference(sb, lower);
296 }
297 } else
298 sb.append('*');
299 } else if (isInstance(t.getClass(), "java.lang.reflect.TypeVariable")) {
300 sb.append('T');
301 sb.append(call(t, "getName"));
302 sb.append(';');
303 } else if (t instanceof Class< ? >) {
304 Class< ? > c = (Class< ? >) t;
305 if (c.isPrimitive()) {
306 sb.append(primitive(c));
307 } else {
308 sb.append('L');
309 String name = c.getName().replace('.', '/');
310 sb.append(name);
311 sb.append(';');
312 }
313 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000314 }
315
316 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +0000317 * Creates the signature for a Parameterized Type. A Parameterized Type has
318 * a raw class and a set of type variables.
Stuart McCullochf3173222012-06-07 21:57:32 +0000319 *
320 * @param sb
321 * @param pt
Stuart McCulloch4482c702012-06-15 13:27:53 +0000322 * @throws Exception
Stuart McCullochf3173222012-06-07 21:57:32 +0000323 */
324 private void parameterizedType(StringBuilder sb, Object pt) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000325 Object owner = call(pt, "getOwnerType");
326 String name = ((Class< ? >) call(pt, "getRawType")).getName().replace('.', '/');
Stuart McCullochf3173222012-06-07 21:57:32 +0000327 if (owner != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000328 if (isInstance(owner.getClass(), "java.lang.reflect.ParameterizedType"))
Stuart McCullochf3173222012-06-07 21:57:32 +0000329 parameterizedType(sb, owner);
330 else
331 sb.append(((Class< ? >) owner).getName().replace('.', '/'));
332 sb.append('.');
333 int n = name.lastIndexOf('$');
334 name = name.substring(n + 1);
335 }
336 sb.append(name);
337
338 sb.append('<');
Stuart McCulloch4482c702012-06-15 13:27:53 +0000339 for (Object parameterType : (Object[]) call(pt, "getActualTypeArguments")) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000340 reference(sb, parameterType);
341 }
342 sb.append('>');
343
344 }
345
346 /**
347 * Handle primitives, these need to be translated to a single char.
348 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000349 * @param type
350 * the primitive class
Stuart McCullochf3173222012-06-07 21:57:32 +0000351 * @return the single char associated with the primitive
352 */
353 private char primitive(Class< ? > type) {
354 if (type == byte.class)
355 return 'B';
Stuart McCulloch4482c702012-06-15 13:27:53 +0000356 else if (type == char.class)
357 return 'C';
358 else if (type == double.class)
359 return 'D';
360 else if (type == float.class)
361 return 'F';
362 else if (type == int.class)
363 return 'I';
364 else if (type == long.class)
365 return 'J';
366 else if (type == short.class)
367 return 'S';
368 else if (type == boolean.class)
369 return 'Z';
370 else if (type == void.class)
371 return 'V';
Stuart McCullochf3173222012-06-07 21:57:32 +0000372 else
Stuart McCulloch4482c702012-06-15 13:27:53 +0000373 throw new IllegalArgumentException("Unknown primitive type " + type);
Stuart McCullochf3173222012-06-07 21:57:32 +0000374 }
375
376 /**
377 * Normalize a signature to make sure the name of the variables are always
378 * the same. We change the names of the type variables to _n, where n is an
379 * integer. n is incremented for every new name and already used names are
380 * replaced with the _n name.
381 *
382 * @return a normalized signature
383 */
384
385 public String normalize(String signature) {
386 StringBuilder sb = new StringBuilder();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000387 Map<String,String> map = new HashMap<String,String>();
Stuart McCullochf3173222012-06-07 21:57:32 +0000388 Rover rover = new Rover(signature);
389 declare(sb, map, rover);
390
391 if (rover.peek() == '(') {
392 // method or constructor
393 sb.append(rover.take('('));
394 while (rover.peek() != ')') {
395 reference(sb, map, rover, true);
396 }
397 sb.append(rover.take(')'));
398 reference(sb, map, rover, true); // return type
Stuart McCulloch4482c702012-06-15 13:27:53 +0000399 } else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000400 // field or class
401 reference(sb, map, rover, true); // field type or super class
402 while (!rover.isEOF()) {
403 reference(sb, map, rover, true); // interfaces
404 }
405 }
406 return sb.toString();
407 }
408
409 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +0000410 * The heart of the routine. Handle a reference to a type. Can be an array,
411 * a class, a type variable, or a primitive.
Stuart McCullochf3173222012-06-07 21:57:32 +0000412 *
413 * @param sb
414 * @param map
415 * @param rover
416 * @param primitivesAllowed
417 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000418 private void reference(StringBuilder sb, Map<String,String> map, Rover rover, boolean primitivesAllowed) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000419
420 char type = rover.take();
421 sb.append(type);
422
423 if (type == '[') {
424 reference(sb, map, rover, true);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000425 } else if (type == 'L') {
426 String fqnb = rover.upTo("<;.");
427 sb.append(fqnb);
428 body(sb, map, rover);
429 while (rover.peek() == '.') {
430 sb.append(rover.take('.'));
431 sb.append(rover.upTo("<;."));
Stuart McCullochf3173222012-06-07 21:57:32 +0000432 body(sb, map, rover);
Stuart McCullochf3173222012-06-07 21:57:32 +0000433 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000434 sb.append(rover.take(';'));
435 } else if (type == 'T') {
436 String name = rover.upTo(";");
437 name = assign(map, name);
438 sb.append(name);
439 sb.append(rover.take(';'));
440 } else {
441 if (!primitivesAllowed)
442 throw new IllegalStateException("Primitives are not allowed without an array");
443 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000444 }
445
446 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +0000447 * Because classes can be nested the body handles the part that can be
448 * nested, the reference handles the enclosing L ... ;
Stuart McCullochf3173222012-06-07 21:57:32 +0000449 *
450 * @param sb
451 * @param map
452 * @param rover
453 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000454 private void body(StringBuilder sb, Map<String,String> map, Rover rover) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000455 if (rover.peek() == '<') {
456 sb.append(rover.take('<'));
457 while (rover.peek() != '>') {
458 switch (rover.peek()) {
459 case 'L' :
460 case '[' :
461 reference(sb, map, rover, false);
462 break;
463
464 case 'T' :
465 String name;
466 sb.append(rover.take('T')); // 'T'
467 name = rover.upTo(";");
468 sb.append(assign(map, name));
469 sb.append(rover.take(';'));
470 break;
471
472 case '+' : // extends
473 case '-' : // super
474 sb.append(rover.take());
475 reference(sb, map, rover, false);
476 break;
477
478 case '*' : // wildcard
479 sb.append(rover.take());
480 break;
481
482 }
483 }
484 sb.append(rover.take('>'));
485 }
486 }
487
488 /**
489 * Handle the declaration part.
490 *
491 * @param sb
492 * @param map
493 * @param rover
494 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000495 private void declare(StringBuilder sb, Map<String,String> map, Rover rover) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000496 char c = rover.peek();
497 if (c == '<') {
498 sb.append(rover.take('<'));
499
500 while (rover.peek() != '>') {
501 String name = rover.upTo(":");
502 name = assign(map, name);
503 sb.append(name);
504 typeVar: while (rover.peek() == ':') {
505 sb.append(rover.take(':'));
506 switch (rover.peek()) {
507 case ':' : // empty class cases
508 continue typeVar;
509
510 default :
511 reference(sb, map, rover, false);
512 break;
513 }
514 }
515 }
516 sb.append(rover.take('>'));
517 }
518 }
519
520 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +0000521 * Handles the assignment of type variables to index names so that we have a
522 * normalized name for each type var.
Stuart McCullochf3173222012-06-07 21:57:32 +0000523 *
Stuart McCulloch4482c702012-06-15 13:27:53 +0000524 * @param map
525 * the map with variables.
526 * @param name
527 * The name of the variable
Stuart McCullochf3173222012-06-07 21:57:32 +0000528 * @return the index name, like _1
529 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000530 private String assign(Map<String,String> map, String name) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000531 if (map.containsKey(name))
532 return map.get(name);
533 else {
534 int n = map.size();
535 map.put(name, "_" + n);
536 return "_" + n;
537 }
538 }
539
Stuart McCulloch4482c702012-06-15 13:27:53 +0000540 private boolean isInstance(Class< ? > type, String string) {
541 if (type == null)
Stuart McCullochf3173222012-06-07 21:57:32 +0000542 return false;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000543
544 if (type.getName().equals(string))
Stuart McCullochf3173222012-06-07 21:57:32 +0000545 return true;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000546
547 if (isInstance(type.getSuperclass(), string))
Stuart McCullochf3173222012-06-07 21:57:32 +0000548 return true;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000549
550 for (Class< ? > intf : type.getInterfaces()) {
551 if (isInstance(intf, string))
Stuart McCullochf3173222012-06-07 21:57:32 +0000552 return true;
553 }
554 return false;
555 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000556
Stuart McCullochf3173222012-06-07 21:57:32 +0000557 private Object call(Object gd, String string) throws Exception {
558 Method m = gd.getClass().getMethod(string);
559 return m.invoke(gd);
560 }
561
562}