1 /*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16 package javassist.bytecode;
17
18 import javassist.ClassPool;
19 import javassist.CtClass;
20 import javassist.CtPrimitiveType;
21 import javassist.NotFoundException;
22 import java.util.Map;
23
24 /**
25 * A support class for dealing with descriptors.
26 *
27 * <p>See chapter 4.3 in "The Java Virtual Machine Specification (2nd ed.)"
28 */
29 public class Descriptor {
30 /**
31 * Converts a class name into the internal representation used in
32 * the JVM.
33 *
34 * <p>Note that <code>toJvmName(toJvmName(s))</code> is equivalent
35 * to <code>toJvmName(s)</code>.
36 */
37 public static String toJvmName(String classname) {
38 return classname.replace('.', '/');
39 }
40
41 /**
42 * Converts a class name from the internal representation used in
43 * the JVM to the normal one used in Java.
44 * This method does not deal with an array type name such as
45 * "[Ljava/lang/Object;" and "[I;". For such names, use
46 * <code>toClassName()</code>.
47 *
48 * @see #toClassName(String)
49 */
50 public static String toJavaName(String classname) {
51 return classname.replace('/', '.');
52 }
53
54 /**
55 * Returns the internal representation of the class name in the
56 * JVM.
57 */
58 public static String toJvmName(CtClass clazz) {
59 if (clazz.isArray())
60 return of(clazz);
61 else
62 return toJvmName(clazz.getName());
63 }
64
65 /**
66 * Converts to a Java class name from a descriptor.
67 *
68 * @param descriptor type descriptor.
69 */
70 public static String toClassName(String descriptor) {
71 int arrayDim = 0;
72 int i = 0;
73 char c = descriptor.charAt(0);
74 while (c == '[') {
75 ++arrayDim;
76 c = descriptor.charAt(++i);
77 }
78
79 String name;
80 if (c == 'L') {
81 int i2 = descriptor.indexOf(';', i++);
82 name = descriptor.substring(i, i2).replace('/', '.');
83 i = i2;
84 }
85 else if (c == 'V')
86 name = "void";
87 else if (c == 'I')
88 name = "int";
89 else if (c == 'B')
90 name = "byte";
91 else if (c == 'J')
92 name = "long";
93 else if (c == 'D')
94 name = "double";
95 else if (c == 'F')
96 name = "float";
97 else if (c == 'C')
98 name = "char";
99 else if (c == 'S')
100 name = "short";
101 else if (c == 'Z')
102 name = "boolean";
103 else
104 throw new RuntimeException("bad descriptor: " + descriptor);
105
106 if (i + 1 != descriptor.length())
107 throw new RuntimeException("multiple descriptors?: " + descriptor);
108
109 if (arrayDim == 0)
110 return name;
111 else {
112 StringBuffer sbuf = new StringBuffer(name);
113 do {
114 sbuf.append("[]");
115 } while (--arrayDim > 0);
116
117 return sbuf.toString();
118 }
119 }
120
121 /**
122 * Converts to a descriptor from a Java class name
123 */
124 public static String of(String classname) {
125 if (classname.equals("void"))
126 return "V";
127 else if (classname.equals("int"))
128 return "I";
129 else if (classname.equals("byte"))
130 return "B";
131 else if (classname.equals("long"))
132 return "J";
133 else if (classname.equals("double"))
134 return "D";
135 else if (classname.equals("float"))
136 return "F";
137 else if (classname.equals("char"))
138 return "C";
139 else if (classname.equals("short"))
140 return "S";
141 else if (classname.equals("boolean"))
142 return "Z";
143 else
144 return "L" + toJvmName(classname) + ";";
145 }
146
147 /**
148 * Substitutes a class name
149 * in the given descriptor string.
150 *
151 * @param desc descriptor string
152 * @param oldname replaced JVM class name
153 * @param newname substituted JVM class name
154 *
155 * @see Descriptor#toJvmName(String)
156 */
157 public static String rename(String desc, String oldname, String newname) {
158 if (desc.indexOf(oldname) < 0)
159 return desc;
160
161 StringBuffer newdesc = new StringBuffer();
162 int head = 0;
163 int i = 0;
164 for (;;) {
165 int j = desc.indexOf('L', i);
166 if (j < 0)
167 break;
168 else if (desc.startsWith(oldname, j + 1)
169 && desc.charAt(j + oldname.length() + 1) == ';') {
170 newdesc.append(desc.substring(head, j));
171 newdesc.append('L');
172 newdesc.append(newname);
173 newdesc.append(';');
174 head = i = j + oldname.length() + 2;
175 }
176 else {
177 i = desc.indexOf(';', j) + 1;
178 if (i < 1)
179 break; // ';' was not found.
180 }
181 }
182
183 if (head == 0)
184 return desc;
185 else {
186 int len = desc.length();
187 if (head < len)
188 newdesc.append(desc.substring(head, len));
189
190 return newdesc.toString();
191 }
192 }
193
194 /**
195 * Substitutes class names in the given descriptor string
196 * according to the given <code>map</code>.
197 *
198 * @param map a map between replaced and substituted
199 * JVM class names.
200 * @see Descriptor#toJvmName(String)
201 */
202 public static String rename(String desc, Map map) {
203 if (map == null)
204 return desc;
205
206 StringBuffer newdesc = new StringBuffer();
207 int head = 0;
208 int i = 0;
209 for (;;) {
210 int j = desc.indexOf('L', i);
211 if (j < 0)
212 break;
213
214 int k = desc.indexOf(';', j);
215 if (k < 0)
216 break;
217
218 i = k + 1;
219 String name = desc.substring(j + 1, k);
220 String name2 = (String)map.get(name);
221 if (name2 != null) {
222 newdesc.append(desc.substring(head, j));
223 newdesc.append('L');
224 newdesc.append(name2);
225 newdesc.append(';');
226 head = i;
227 }
228 }
229
230 if (head == 0)
231 return desc;
232 else {
233 int len = desc.length();
234 if (head < len)
235 newdesc.append(desc.substring(head, len));
236
237 return newdesc.toString();
238 }
239 }
240
241 /**
242 * Returns the descriptor representing the given type.
243 */
244 public static String of(CtClass type) {
245 StringBuffer sbuf = new StringBuffer();
246 toDescriptor(sbuf, type);
247 return sbuf.toString();
248 }
249
250 private static void toDescriptor(StringBuffer desc, CtClass type) {
251 if (type.isArray()) {
252 desc.append('[');
253 try {
254 toDescriptor(desc, type.getComponentType());
255 }
256 catch (NotFoundException e) {
257 desc.append('L');
258 String name = type.getName();
259 desc.append(toJvmName(name.substring(0, name.length() - 2)));
260 desc.append(';');
261 }
262 }
263 else if (type.isPrimitive()) {
264 CtPrimitiveType pt = (CtPrimitiveType)type;
265 desc.append(pt.getDescriptor());
266 }
267 else { // class type
268 desc.append('L');
269 desc.append(type.getName().replace('.', '/'));
270 desc.append(';');
271 }
272 }
273
274 /**
275 * Returns the descriptor representing a constructor receiving
276 * the given parameter types.
277 *
278 * @param paramTypes parameter types
279 */
280 public static String ofConstructor(CtClass[] paramTypes) {
281 return ofMethod(CtClass.voidType, paramTypes);
282 }
283
284 /**
285 * Returns the descriptor representing a method that receives
286 * the given parameter types and returns the given type.
287 *
288 * @param returnType return type
289 * @param paramTypes parameter types
290 */
291 public static String ofMethod(CtClass returnType, CtClass[] paramTypes) {
292 StringBuffer desc = new StringBuffer();
293 desc.append('(');
294 if (paramTypes != null) {
295 int n = paramTypes.length;
296 for (int i = 0; i < n; ++i)
297 toDescriptor(desc, paramTypes[i]);
298 }
299
300 desc.append(')');
301 if (returnType != null)
302 toDescriptor(desc, returnType);
303
304 return desc.toString();
305 }
306
307 /**
308 * Returns the descriptor representing a list of parameter types.
309 * For example, if the given parameter types are two <code>int</code>,
310 * then this method returns <code>"(II)"</code>.
311 *
312 * @param paramTypes parameter types
313 */
314 public static String ofParameters(CtClass[] paramTypes) {
315 return ofMethod(null, paramTypes);
316 }
317
318 /**
319 * Appends a parameter type to the parameter list represented
320 * by the given descriptor.
321 *
322 * <p><code>classname</code> must not be an array type.
323 *
324 * @param classname parameter type (not primitive type)
325 * @param desc descriptor
326 */
327 public static String appendParameter(String classname, String desc) {
328 int i = desc.indexOf(')');
329 if (i < 0)
330 return desc;
331 else {
332 StringBuffer newdesc = new StringBuffer();
333 newdesc.append(desc.substring(0, i));
334 newdesc.append('L');
335 newdesc.append(classname.replace('.', '/'));
336 newdesc.append(';');
337 newdesc.append(desc.substring(i));
338 return newdesc.toString();
339 }
340 }
341
342 /**
343 * Inserts a parameter type at the beginning of the parameter
344 * list represented
345 * by the given descriptor.
346 *
347 * <p><code>classname</code> must not be an array type.
348 *
349 * @param classname parameter type (not primitive type)
350 * @param desc descriptor
351 */
352 public static String insertParameter(String classname, String desc) {
353 if (desc.charAt(0) != '(')
354 return desc;
355 else
356 return "(L" + classname.replace('.', '/') + ';'
357 + desc.substring(1);
358 }
359
360 /**
361 * Appends a parameter type to the parameter list represented
362 * by the given descriptor. The appended parameter becomes
363 * the last parameter.
364 *
365 * @param type the type of the appended parameter.
366 * @param descriptor the original descriptor.
367 */
368 public static String appendParameter(CtClass type, String descriptor) {
369 int i = descriptor.indexOf(')');
370 if (i < 0)
371 return descriptor;
372 else {
373 StringBuffer newdesc = new StringBuffer();
374 newdesc.append(descriptor.substring(0, i));
375 toDescriptor(newdesc, type);
376 newdesc.append(descriptor.substring(i));
377 return newdesc.toString();
378 }
379 }
380
381 /**
382 * Inserts a parameter type at the beginning of the parameter
383 * list represented
384 * by the given descriptor.
385 *
386 * @param type the type of the inserted parameter.
387 * @param descriptor the descriptor of the method.
388 */
389 public static String insertParameter(CtClass type,
390 String descriptor) {
391 if (descriptor.charAt(0) != '(')
392 return descriptor;
393 else
394 return "(" + of(type) + descriptor.substring(1);
395 }
396
397 /**
398 * Changes the return type included in the given descriptor.
399 *
400 * <p><code>classname</code> must not be an array type.
401 *
402 * @param classname return type
403 * @param desc descriptor
404 */
405 public static String changeReturnType(String classname, String desc) {
406 int i = desc.indexOf(')');
407 if (i < 0)
408 return desc;
409 else {
410 StringBuffer newdesc = new StringBuffer();
411 newdesc.append(desc.substring(0, i + 1));
412 newdesc.append('L');
413 newdesc.append(classname.replace('.', '/'));
414 newdesc.append(';');
415 return newdesc.toString();
416 }
417 }
418
419 /**
420 * Returns the <code>CtClass</code> objects representing the parameter
421 * types specified by the given descriptor.
422 *
423 * @param desc descriptor
424 * @param cp the class pool used for obtaining
425 * a <code>CtClass</code> object.
426 */
427 public static CtClass[] getParameterTypes(String desc, ClassPool cp)
428 throws NotFoundException
429 {
430 if (desc.charAt(0) != '(')
431 return null;
432 else {
433 int num = numOfParameters(desc);
434 CtClass[] args = new CtClass[num];
435 int n = 0;
436 int i = 1;
437 do {
438 i = toCtClass(cp, desc, i, args, n++);
439 } while (i > 0);
440 return args;
441 }
442 }
443
444 /**
445 * Returns true if the list of the parameter types of desc1 is equal to
446 * that of desc2.
447 * For example, "(II)V" and "(II)I" are equal.
448 */
449 public static boolean eqParamTypes(String desc1, String desc2) {
450 if (desc1.charAt(0) != '(')
451 return false;
452
453 for (int i = 0; true; ++i) {
454 char c = desc1.charAt(i);
455 if (c != desc2.charAt(i))
456 return false;
457
458 if (c == ')')
459 return true;
460 }
461 }
462
463 /**
464 * Returns the signature of the given descriptor. The signature does
465 * not include the return type. For example, the signature of "(I)V"
466 * is "(I)".
467 */
468 public static String getParamDescriptor(String decl) {
469 return decl.substring(0, decl.indexOf(')') + 1);
470 }
471
472 /**
473 * Returns the <code>CtClass</code> object representing the return
474 * type specified by the given descriptor.
475 *
476 * @param desc descriptor
477 * @param cp the class pool used for obtaining
478 * a <code>CtClass</code> object.
479 */
480 public static CtClass getReturnType(String desc, ClassPool cp)
481 throws NotFoundException
482 {
483 int i = desc.indexOf(')');
484 if (i < 0)
485 return null;
486 else {
487 CtClass[] type = new CtClass[1];
488 toCtClass(cp, desc, i + 1, type, 0);
489 return type[0];
490 }
491 }
492
493 /**
494 * Returns the number of the prameters included in the given
495 * descriptor.
496 *
497 * @param desc descriptor
498 */
499 public static int numOfParameters(String desc) {
500 int n = 0;
501 int i = 1;
502 for (;;) {
503 char c = desc.charAt(i);
504 if (c == ')')
505 break;
506
507 while (c == '[')
508 c = desc.charAt(++i);
509
510 if (c == 'L') {
511 i = desc.indexOf(';', i) + 1;
512 if (i <= 0)
513 throw new IndexOutOfBoundsException("bad descriptor");
514 }
515 else
516 ++i;
517
518 ++n;
519 }
520
521 return n;
522 }
523
524 /**
525 * Returns a <code>CtClass</code> object representing the type
526 * specified by the given descriptor.
527 *
528 * <p>This method works even if the package-class separator is
529 * not <code>/</code> but <code>.</code> (period). For example,
530 * it accepts <code>Ljava.lang.Object;</code>
531 * as well as <code>Ljava/lang/Object;</code>.
532 *
533 * @param desc descriptor.
534 * @param cp the class pool used for obtaining
535 * a <code>CtClass</code> object.
536 */
537 public static CtClass toCtClass(String desc, ClassPool cp)
538 throws NotFoundException
539 {
540 CtClass[] clazz = new CtClass[1];
541 int res = toCtClass(cp, desc, 0, clazz, 0);
542 if (res >= 0)
543 return clazz[0];
544 else {
545 // maybe, you forgot to surround the class name with
546 // L and ;. It violates the protocol, but I'm tolerant...
547 return cp.get(desc.replace('/', '.'));
548 }
549 }
550
551 private static int toCtClass(ClassPool cp, String desc, int i,
552 CtClass[] args, int n)
553 throws NotFoundException
554 {
555 int i2;
556 String name;
557
558 int arrayDim = 0;
559 char c = desc.charAt(i);
560 while (c == '[') {
561 ++arrayDim;
562 c = desc.charAt(++i);
563 }
564
565 if (c == 'L') {
566 i2 = desc.indexOf(';', ++i);
567 name = desc.substring(i, i2++).replace('/', '.');
568 }
569 else {
570 CtClass type = toPrimitiveClass(c);
571 if (type == null)
572 return -1; // error
573
574 i2 = i + 1;
575 if (arrayDim == 0) {
576 args[n] = type;
577 return i2; // neither an array type or a class type
578 }
579 else
580 name = type.getName();
581 }
582
583 if (arrayDim > 0) {
584 StringBuffer sbuf = new StringBuffer(name);
585 while (arrayDim-- > 0)
586 sbuf.append("[]");
587
588 name = sbuf.toString();
589 }
590
591 args[n] = cp.get(name);
592 return i2;
593 }
594
595 static CtClass toPrimitiveClass(char c) {
596 CtClass type = null;
597 switch (c) {
598 case 'Z' :
599 type = CtClass.booleanType;
600 break;
601 case 'C' :
602 type = CtClass.charType;
603 break;
604 case 'B' :
605 type = CtClass.byteType;
606 break;
607 case 'S' :
608 type = CtClass.shortType;
609 break;
610 case 'I' :
611 type = CtClass.intType;
612 break;
613 case 'J' :
614 type = CtClass.longType;
615 break;
616 case 'F' :
617 type = CtClass.floatType;
618 break;
619 case 'D' :
620 type = CtClass.doubleType;
621 break;
622 case 'V' :
623 type = CtClass.voidType;
624 break;
625 }
626
627 return type;
628 }
629
630 /**
631 * Computes the dimension of the array represented by the given
632 * descriptor. For example, if the descriptor is <code>"[[I"</code>,
633 * then this method returns 2.
634 *
635 * @param desc the descriptor.
636 * @return 0 if the descriptor does not represent an array type.
637 */
638 public static int arrayDimension(String desc) {
639 int dim = 0;
640 while (desc.charAt(dim) == '[')
641 ++dim;
642
643 return dim;
644 }
645
646 /**
647 * Returns the descriptor of the type of the array component.
648 * For example, if the given descriptor is
649 * <code>"[[Ljava/lang/String;"</code> and the given dimension is 2,
650 * then this method returns <code>"Ljava/lang/String;"</code>.
651 *
652 * @param desc the descriptor.
653 * @param dim the array dimension.
654 */
655 public static String toArrayComponent(String desc, int dim) {
656 return desc.substring(dim);
657 }
658
659 /**
660 * Computes the data size specified by the given descriptor.
661 * For example, if the descriptor is "D", this method returns 2.
662 *
663 * <p>If the descriptor represents a method type, this method returns
664 * (the size of the returned value) - (the sum of the data sizes
665 * of all the parameters). For example, if the descriptor is
666 * <code>"(I)D"</code>, then this method returns 1 (= 2 - 1).
667 *
668 * @param desc descriptor
669 */
670 public static int dataSize(String desc) {
671 return dataSize(desc, true);
672 }
673
674 /**
675 * Computes the data size of parameters.
676 * If one of the parameters is double type, the size of that parameter
677 * is 2 words. For example, if the given descriptor is
678 * <code>"(IJ)D"</code>, then this method returns 3. The size of the
679 * return type is not computed.
680 *
681 * @param desc a method descriptor.
682 */
683 public static int paramSize(String desc) {
684 return -dataSize(desc, false);
685 }
686
687 private static int dataSize(String desc, boolean withRet) {
688 int n = 0;
689 char c = desc.charAt(0);
690 if (c == '(') {
691 int i = 1;
692 for (;;) {
693 c = desc.charAt(i);
694 if (c == ')') {
695 c = desc.charAt(i + 1);
696 break;
697 }
698
699 boolean array = false;
700 while (c == '[') {
701 array = true;
702 c = desc.charAt(++i);
703 }
704
705 if (c == 'L') {
706 i = desc.indexOf(';', i) + 1;
707 if (i <= 0)
708 throw new IndexOutOfBoundsException("bad descriptor");
709 }
710 else
711 ++i;
712
713 if (!array && (c == 'J' || c == 'D'))
714 n -= 2;
715 else
716 --n;
717 }
718 }
719
720 if (withRet)
721 if (c == 'J' || c == 'D')
722 n += 2;
723 else if (c != 'V')
724 ++n;
725
726 return n;
727 }
728
729 /**
730 * Returns a human-readable representation of the
731 * given descriptor. For example, <code>Ljava/lang/Object;</code>
732 * is converted into <code>java.lang.Object</code>.
733 * <code>(I[I)V</code> is converted into <code>(int, int[])</code>
734 * (the return type is ignored).
735 */
736 public static String toString(String desc) {
737 return PrettyPrinter.toString(desc);
738 }
739
740 static class PrettyPrinter {
741 static String toString(String desc) {
742 StringBuffer sbuf = new StringBuffer();
743 if (desc.charAt(0) == '(') {
744 int pos = 1;
745 sbuf.append('(');
746 while (desc.charAt(pos) != ')') {
747 if (pos > 1)
748 sbuf.append(',');
749
750 pos = readType(sbuf, pos, desc);
751 }
752
753 sbuf.append(')');
754 }
755 else
756 readType(sbuf, 0, desc);
757
758 return sbuf.toString();
759 }
760
761 static int readType(StringBuffer sbuf, int pos, String desc) {
762 char c = desc.charAt(pos);
763 int arrayDim = 0;
764 while (c == '[') {
765 arrayDim++;
766 c = desc.charAt(++pos);
767 }
768
769 if (c == 'L')
770 while (true) {
771 c = desc.charAt(++pos);
772 if (c == ';')
773 break;
774
775 if (c == '/')
776 c = '.';
777
778 sbuf.append(c);
779 }
780 else {
781 CtClass t = toPrimitiveClass(c);
782 sbuf.append(t.getName());
783 }
784
785 while (arrayDim-- > 0)
786 sbuf.append("[]");
787
788 return pos + 1;
789 }
790 }
791
792 /**
793 * An Iterator over a descriptor.
794 */
795 public static class Iterator {
796 private String desc;
797 private int index, curPos;
798 private boolean param;
799
800 /**
801 * Constructs an iterator.
802 *
803 * @param s descriptor.
804 */
805 public Iterator(String s) {
806 desc = s;
807 index = curPos = 0;
808 param = false;
809 }
810
811 /**
812 * Returns true if the iteration has more elements.
813 */
814 public boolean hasNext() {
815 return index < desc.length();
816 }
817
818 /**
819 * Returns true if the current element is a parameter type.
820 */
821 public boolean isParameter() { return param; }
822
823 /**
824 * Returns the first character of the current element.
825 */
826 public char currentChar() { return desc.charAt(curPos); }
827
828 /**
829 * Returns true if the current element is double or long type.
830 */
831 public boolean is2byte() {
832 char c = currentChar();
833 return c == 'D' || c == 'J';
834 }
835
836 /**
837 * Returns the position of the next type character.
838 * That type character becomes a new current element.
839 */
840 public int next() {
841 int nextPos = index;
842 char c = desc.charAt(nextPos);
843 if (c == '(') {
844 ++index;
845 c = desc.charAt(++nextPos);
846 param = true;
847 }
848
849 if (c == ')') {
850 ++index;
851 c = desc.charAt(++nextPos);
852 param = false;
853 }
854
855 while (c == '[')
856 c = desc.charAt(++nextPos);
857
858 if (c == 'L') {
859 nextPos = desc.indexOf(';', nextPos) + 1;
860 if (nextPos <= 0)
861 throw new IndexOutOfBoundsException("bad descriptor");
862 }
863 else
864 ++nextPos;
865
866 curPos = index;
867 index = nextPos;
868 return curPos;
869 }
870 }
871 }