001: /*
002: * Javassist, a Java-bytecode translator toolkit.
003: * Copyright (C) 1999-2006 Shigeru Chiba. All Rights Reserved.
004: *
005: * The contents of this file are subject to the Mozilla Public License Version
006: * 1.1 (the "License"); you may not use this file except in compliance with
007: * the License. Alternatively, the contents of this file may be used under
008: * the terms of the GNU Lesser General Public License Version 2.1 or later.
009: *
010: * Software distributed under the License is distributed on an "AS IS" basis,
011: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
012: * for the specific language governing rights and limitations under the
013: * License.
014: */
015:
016: package javassist.bytecode;
017:
018: import javassist.ClassPool;
019: import javassist.CtClass;
020: import javassist.CtPrimitiveType;
021: import javassist.NotFoundException;
022: import java.util.Map;
023:
024: /**
025: * A support class for dealing with descriptors.
026: *
027: * <p>See chapter 4.3 in "The Java Virtual Machine Specification (2nd ed.)"
028: */
029: public class Descriptor {
030: /**
031: * Converts a class name into the internal representation used in
032: * the JVM.
033: *
034: * <p>Note that <code>toJvmName(toJvmName(s))</code> is equivalent
035: * to <code>toJvmName(s)</code>.
036: */
037: public static String toJvmName(String classname) {
038: return classname.replace('.', '/');
039: }
040:
041: /**
042: * Converts a class name from the internal representation used in
043: * the JVM to the normal one used in Java.
044: */
045: public static String toJavaName(String classname) {
046: return classname.replace('/', '.');
047: }
048:
049: /**
050: * Returns the internal representation of the class name in the
051: * JVM.
052: */
053: public static String toJvmName(CtClass clazz) {
054: if (clazz.isArray())
055: return of(clazz);
056: else
057: return toJvmName(clazz.getName());
058: }
059:
060: /**
061: * Converts to a Java class name from a descriptor.
062: *
063: * @param descriptor type descriptor.
064: */
065: public static String toClassName(String descriptor) {
066: int arrayDim = 0;
067: int i = 0;
068: char c = descriptor.charAt(0);
069: while (c == '[') {
070: ++arrayDim;
071: c = descriptor.charAt(++i);
072: }
073:
074: String name;
075: if (c == 'L') {
076: int i2 = descriptor.indexOf(';', i++);
077: name = descriptor.substring(i, i2).replace('/', '.');
078: i = i2;
079: } else if (c == 'V')
080: name = "void";
081: else if (c == 'I')
082: name = "int";
083: else if (c == 'B')
084: name = "byte";
085: else if (c == 'J')
086: name = "long";
087: else if (c == 'D')
088: name = "double";
089: else if (c == 'F')
090: name = "float";
091: else if (c == 'C')
092: name = "char";
093: else if (c == 'S')
094: name = "short";
095: else if (c == 'Z')
096: name = "boolean";
097: else
098: throw new RuntimeException("bad descriptor: " + descriptor);
099:
100: if (i + 1 != descriptor.length())
101: throw new RuntimeException("multiple descriptors?: "
102: + descriptor);
103:
104: if (arrayDim == 0)
105: return name;
106: else {
107: StringBuffer sbuf = new StringBuffer(name);
108: do {
109: sbuf.append("[]");
110: } while (--arrayDim > 0);
111:
112: return sbuf.toString();
113: }
114: }
115:
116: /**
117: * Converts to a descriptor from a Java class name
118: */
119: public static String of(String classname) {
120: if (classname.equals("void"))
121: return "V";
122: else if (classname.equals("int"))
123: return "I";
124: else if (classname.equals("byte"))
125: return "B";
126: else if (classname.equals("long"))
127: return "J";
128: else if (classname.equals("double"))
129: return "D";
130: else if (classname.equals("float"))
131: return "F";
132: else if (classname.equals("char"))
133: return "C";
134: else if (classname.equals("short"))
135: return "S";
136: else if (classname.equals("boolean"))
137: return "Z";
138: else
139: return "L" + toJvmName(classname) + ";";
140: }
141:
142: /**
143: * Substitutes a class name
144: * in the given descriptor string.
145: *
146: * @param desc descriptor string
147: * @param oldname replaced JVM class name
148: * @param newname substituted JVM class name
149: *
150: * @see Descriptor#toJvmName(String)
151: */
152: public static String rename(String desc, String oldname,
153: String newname) {
154: if (desc.indexOf(oldname) < 0)
155: return desc;
156:
157: StringBuffer newdesc = new StringBuffer();
158: int head = 0;
159: int i = 0;
160: for (;;) {
161: int j = desc.indexOf('L', i);
162: if (j < 0)
163: break;
164: else if (desc.startsWith(oldname, j + 1)
165: && desc.charAt(j + oldname.length() + 1) == ';') {
166: newdesc.append(desc.substring(head, j));
167: newdesc.append('L');
168: newdesc.append(newname);
169: newdesc.append(';');
170: head = i = j + oldname.length() + 2;
171: } else {
172: i = desc.indexOf(';', j) + 1;
173: if (i < 1)
174: break; // ';' was not found.
175: }
176: }
177:
178: if (head == 0)
179: return desc;
180: else {
181: int len = desc.length();
182: if (head < len)
183: newdesc.append(desc.substring(head, len));
184:
185: return newdesc.toString();
186: }
187: }
188:
189: /**
190: * Substitutes class names in the given descriptor string
191: * according to the given <code>map</code>.
192: *
193: * @param map a map between replaced and substituted
194: * JVM class names.
195: * @see Descriptor#toJvmName(String)
196: */
197: public static String rename(String desc, Map map) {
198: if (map == null)
199: return desc;
200:
201: StringBuffer newdesc = new StringBuffer();
202: int head = 0;
203: int i = 0;
204: for (;;) {
205: int j = desc.indexOf('L', i);
206: if (j < 0)
207: break;
208:
209: int k = desc.indexOf(';', j);
210: if (k < 0)
211: break;
212:
213: i = k + 1;
214: String name = desc.substring(j + 1, k);
215: String name2 = (String) map.get(name);
216: if (name2 != null) {
217: newdesc.append(desc.substring(head, j));
218: newdesc.append('L');
219: newdesc.append(name2);
220: newdesc.append(';');
221: head = i;
222: }
223: }
224:
225: if (head == 0)
226: return desc;
227: else {
228: int len = desc.length();
229: if (head < len)
230: newdesc.append(desc.substring(head, len));
231:
232: return newdesc.toString();
233: }
234: }
235:
236: /**
237: * Returns the descriptor representing the given type.
238: */
239: public static String of(CtClass type) {
240: StringBuffer sbuf = new StringBuffer();
241: toDescriptor(sbuf, type);
242: return sbuf.toString();
243: }
244:
245: private static void toDescriptor(StringBuffer desc, CtClass type) {
246: if (type.isArray()) {
247: desc.append('[');
248: try {
249: toDescriptor(desc, type.getComponentType());
250: } catch (NotFoundException e) {
251: desc.append('L');
252: String name = type.getName();
253: desc.append(toJvmName(name.substring(0,
254: name.length() - 2)));
255: desc.append(';');
256: }
257: } else if (type.isPrimitive()) {
258: CtPrimitiveType pt = (CtPrimitiveType) type;
259: desc.append(pt.getDescriptor());
260: } else { // class type
261: desc.append('L');
262: desc.append(type.getName().replace('.', '/'));
263: desc.append(';');
264: }
265: }
266:
267: /**
268: * Returns the descriptor representing a constructor receiving
269: * the given parameter types.
270: *
271: * @param paramTypes parameter types
272: */
273: public static String ofConstructor(CtClass[] paramTypes) {
274: return ofMethod(CtClass.voidType, paramTypes);
275: }
276:
277: /**
278: * Returns the descriptor representing a method that receives
279: * the given parameter types and returns the given type.
280: *
281: * @param returnType return type
282: * @param paramTypes parameter types
283: */
284: public static String ofMethod(CtClass returnType,
285: CtClass[] paramTypes) {
286: StringBuffer desc = new StringBuffer();
287: desc.append('(');
288: if (paramTypes != null) {
289: int n = paramTypes.length;
290: for (int i = 0; i < n; ++i)
291: toDescriptor(desc, paramTypes[i]);
292: }
293:
294: desc.append(')');
295: if (returnType != null)
296: toDescriptor(desc, returnType);
297:
298: return desc.toString();
299: }
300:
301: /**
302: * Returns the descriptor representing a list of parameter types.
303: * For example, if the given parameter types are two <code>int</code>,
304: * then this method returns <code>"(II)"</code>.
305: *
306: * @param paramTypes parameter types
307: */
308: public static String ofParameters(CtClass[] paramTypes) {
309: return ofMethod(null, paramTypes);
310: }
311:
312: /**
313: * Appends a parameter type to the parameter list represented
314: * by the given descriptor.
315: *
316: * <p><code>classname</code> must not be an array type.
317: *
318: * @param classname parameter type (not primitive type)
319: * @param desc descriptor
320: */
321: public static String appendParameter(String classname, String desc) {
322: int i = desc.indexOf(')');
323: if (i < 0)
324: return desc;
325: else {
326: StringBuffer newdesc = new StringBuffer();
327: newdesc.append(desc.substring(0, i));
328: newdesc.append('L');
329: newdesc.append(classname.replace('.', '/'));
330: newdesc.append(';');
331: newdesc.append(desc.substring(i));
332: return newdesc.toString();
333: }
334: }
335:
336: /**
337: * Inserts a parameter type at the beginning of the parameter
338: * list represented
339: * by the given descriptor.
340: *
341: * <p><code>classname</code> must not be an array type.
342: *
343: * @param classname parameter type (not primitive type)
344: * @param desc descriptor
345: */
346: public static String insertParameter(String classname, String desc) {
347: if (desc.charAt(0) != '(')
348: return desc;
349: else
350: return "(L" + classname.replace('.', '/') + ';'
351: + desc.substring(1);
352: }
353:
354: /**
355: * Changes the return type included in the given descriptor.
356: *
357: * <p><code>classname</code> must not be an array type.
358: *
359: * @param classname return type
360: * @param desc descriptor
361: */
362: public static String changeReturnType(String classname, String desc) {
363: int i = desc.indexOf(')');
364: if (i < 0)
365: return desc;
366: else {
367: StringBuffer newdesc = new StringBuffer();
368: newdesc.append(desc.substring(0, i + 1));
369: newdesc.append('L');
370: newdesc.append(classname.replace('.', '/'));
371: newdesc.append(';');
372: return newdesc.toString();
373: }
374: }
375:
376: /**
377: * Returns the <code>CtClass</code> objects representing the parameter
378: * types specified by the given descriptor.
379: *
380: * @param desc descriptor
381: * @param cp the class pool used for obtaining
382: * a <code>CtClass</code> object.
383: */
384: public static CtClass[] getParameterTypes(String desc, ClassPool cp)
385: throws NotFoundException {
386: if (desc.charAt(0) != '(')
387: return null;
388: else {
389: int num = numOfParameters(desc);
390: CtClass[] args = new CtClass[num];
391: int n = 0;
392: int i = 1;
393: do {
394: i = toCtClass(cp, desc, i, args, n++);
395: } while (i > 0);
396: return args;
397: }
398: }
399:
400: /**
401: * Returns true if the list of the parameter types of desc1 is equal to
402: * that of desc2.
403: * For example, "(II)V" and "(II)I" are equal.
404: */
405: public static boolean eqParamTypes(String desc1, String desc2) {
406: if (desc1.charAt(0) != '(')
407: return false;
408:
409: for (int i = 0; true; ++i) {
410: char c = desc1.charAt(i);
411: if (c != desc2.charAt(i))
412: return false;
413:
414: if (c == ')')
415: return true;
416: }
417: }
418:
419: /**
420: * Returns the signature of the given descriptor. The signature does
421: * not include the return type. For example, the signature of "(I)V"
422: * is "(I)".
423: */
424: public static String getParamDescriptor(String decl) {
425: return decl.substring(0, decl.indexOf(')') + 1);
426: }
427:
428: /**
429: * Returns the <code>CtClass</code> object representing the return
430: * type specified by the given descriptor.
431: *
432: * @param desc descriptor
433: * @param cp the class pool used for obtaining
434: * a <code>CtClass</code> object.
435: */
436: public static CtClass getReturnType(String desc, ClassPool cp)
437: throws NotFoundException {
438: int i = desc.indexOf(')');
439: if (i < 0)
440: return null;
441: else {
442: CtClass[] type = new CtClass[1];
443: toCtClass(cp, desc, i + 1, type, 0);
444: return type[0];
445: }
446: }
447:
448: /**
449: * Returns the number of the prameters included in the given
450: * descriptor.
451: *
452: * @param desc descriptor
453: */
454: public static int numOfParameters(String desc) {
455: int n = 0;
456: int i = 1;
457: for (;;) {
458: char c = desc.charAt(i);
459: if (c == ')')
460: break;
461:
462: while (c == '[')
463: c = desc.charAt(++i);
464:
465: if (c == 'L') {
466: i = desc.indexOf(';', i) + 1;
467: if (i <= 0)
468: throw new IndexOutOfBoundsException(
469: "bad descriptor");
470: } else
471: ++i;
472:
473: ++n;
474: }
475:
476: return n;
477: }
478:
479: /**
480: * Returns a <code>CtClass</code> object representing the type
481: * specified by the given descriptor.
482: *
483: * <p>This method works even if the package-class separator is
484: * not <code>/</code> but <code>.</code> (period). For example,
485: * it accepts <code>Ljava.lang.Object;</code>
486: * as well as <code>Ljava/lang/Object;</code>.
487: *
488: * @param desc descriptor
489: * @param cp the class pool used for obtaining
490: * a <code>CtClass</code> object.
491: */
492: public static CtClass toCtClass(String desc, ClassPool cp)
493: throws NotFoundException {
494: CtClass[] clazz = new CtClass[1];
495: int res = toCtClass(cp, desc, 0, clazz, 0);
496: if (res >= 0)
497: return clazz[0];
498: else {
499: // maybe, you forgot to surround the class name with
500: // L and ;. It violates the protocol, but I'm tolerant...
501: return cp.get(desc.replace('/', '.'));
502: }
503: }
504:
505: private static int toCtClass(ClassPool cp, String desc, int i,
506: CtClass[] args, int n) throws NotFoundException {
507: int i2;
508: String name;
509:
510: int arrayDim = 0;
511: char c = desc.charAt(i);
512: while (c == '[') {
513: ++arrayDim;
514: c = desc.charAt(++i);
515: }
516:
517: if (c == 'L') {
518: i2 = desc.indexOf(';', ++i);
519: name = desc.substring(i, i2++).replace('/', '.');
520: } else {
521: CtClass type = toPrimitiveClass(c);
522: if (type == null)
523: return -1; // error
524:
525: i2 = i + 1;
526: if (arrayDim == 0) {
527: args[n] = type;
528: return i2; // neither an array type or a class type
529: } else
530: name = type.getName();
531: }
532:
533: if (arrayDim > 0) {
534: StringBuffer sbuf = new StringBuffer(name);
535: while (arrayDim-- > 0)
536: sbuf.append("[]");
537:
538: name = sbuf.toString();
539: }
540:
541: args[n] = cp.get(name);
542: return i2;
543: }
544:
545: private static CtClass toPrimitiveClass(char c) {
546: CtClass type = null;
547: switch (c) {
548: case 'Z':
549: type = CtClass.booleanType;
550: break;
551: case 'C':
552: type = CtClass.charType;
553: break;
554: case 'B':
555: type = CtClass.byteType;
556: break;
557: case 'S':
558: type = CtClass.shortType;
559: break;
560: case 'I':
561: type = CtClass.intType;
562: break;
563: case 'J':
564: type = CtClass.longType;
565: break;
566: case 'F':
567: type = CtClass.floatType;
568: break;
569: case 'D':
570: type = CtClass.doubleType;
571: break;
572: case 'V':
573: type = CtClass.voidType;
574: break;
575: }
576:
577: return type;
578: }
579:
580: /**
581: * Computes the dimension of the array represented by the given
582: * descriptor. For example, if the descriptor is <code>"[[I"</code>,
583: * then this method returns 2.
584: *
585: * @param desc the descriptor.
586: * @return 0 if the descriptor does not represent an array type.
587: */
588: public static int arrayDimension(String desc) {
589: int dim = 0;
590: while (desc.charAt(dim) == '[')
591: ++dim;
592:
593: return dim;
594: }
595:
596: /**
597: * Returns the descriptor of the type of the array component.
598: * For example, if the given descriptor is
599: * <code>"[[Ljava/lang/String;"</code> and the given dimension is 2,
600: * then this method returns <code>"Ljava/lang/String;"</code>.
601: *
602: * @param desc the descriptor.
603: * @param dim the array dimension.
604: */
605: public static String toArrayComponent(String desc, int dim) {
606: return desc.substring(dim);
607: }
608:
609: /**
610: * Computes the data size specified by the given descriptor.
611: * For example, if the descriptor is "D", this method returns 2.
612: *
613: * <p>If the descriptor represents a method type, this method returns
614: * (the size of the returned value) - (the sum of the data sizes
615: * of all the parameters). For example, if the descriptor is
616: * <code>"(I)D"</code>, then this method returns 1 (= 2 - 1).
617: *
618: * @param desc descriptor
619: */
620: public static int dataSize(String desc) {
621: return dataSize(desc, true);
622: }
623:
624: /**
625: * Computes the data size of parameters.
626: * If one of the parameters is double type, the size of that parameter
627: * is 2 words. For example, if the given descriptor is
628: * <code>"(IJ)D"</code>, then this method returns 3. The size of the
629: * return type is not computed.
630: *
631: * @param desc a method descriptor.
632: */
633: public static int paramSize(String desc) {
634: return -dataSize(desc, false);
635: }
636:
637: private static int dataSize(String desc, boolean withRet) {
638: int n = 0;
639: char c = desc.charAt(0);
640: if (c == '(') {
641: int i = 1;
642: for (;;) {
643: c = desc.charAt(i);
644: if (c == ')') {
645: c = desc.charAt(i + 1);
646: break;
647: }
648:
649: boolean array = false;
650: while (c == '[') {
651: array = true;
652: c = desc.charAt(++i);
653: }
654:
655: if (c == 'L') {
656: i = desc.indexOf(';', i) + 1;
657: if (i <= 0)
658: throw new IndexOutOfBoundsException(
659: "bad descriptor");
660: } else
661: ++i;
662:
663: if (!array && (c == 'J' || c == 'D'))
664: n -= 2;
665: else
666: --n;
667: }
668: }
669:
670: if (withRet)
671: if (c == 'J' || c == 'D')
672: n += 2;
673: else if (c != 'V')
674: ++n;
675:
676: return n;
677: }
678:
679: /**
680: * An Iterator over a descriptor.
681: */
682: public static class Iterator {
683: private String desc;
684: private int index, curPos;
685: private boolean param;
686:
687: /**
688: * Constructs an iterator.
689: *
690: * @param s descriptor.
691: */
692: public Iterator(String s) {
693: desc = s;
694: index = curPos = 0;
695: param = false;
696: }
697:
698: /**
699: * Returns true if the iteration has more elements.
700: */
701: public boolean hasNext() {
702: return index < desc.length();
703: }
704:
705: /**
706: * Returns true if the current element is a parameter type.
707: */
708: public boolean isParameter() {
709: return param;
710: }
711:
712: /**
713: * Returns the first character of the current element.
714: */
715: public char currentChar() {
716: return desc.charAt(curPos);
717: }
718:
719: /**
720: * Returns true if the current element is double or long type.
721: */
722: public boolean is2byte() {
723: char c = currentChar();
724: return c == 'D' || c == 'J';
725: }
726:
727: /**
728: * Returns the position of the next type character.
729: * That type character becomes a new current element.
730: */
731: public int next() {
732: int nextPos = index;
733: char c = desc.charAt(nextPos);
734: if (c == '(') {
735: ++index;
736: c = desc.charAt(++nextPos);
737: param = true;
738: }
739:
740: if (c == ')') {
741: ++index;
742: c = desc.charAt(++nextPos);
743: param = false;
744: }
745:
746: while (c == '[')
747: c = desc.charAt(++nextPos);
748:
749: if (c == 'L') {
750: nextPos = desc.indexOf(';', nextPos) + 1;
751: if (nextPos <= 0)
752: throw new IndexOutOfBoundsException(
753: "bad descriptor");
754: } else
755: ++nextPos;
756:
757: curPos = index;
758: index = nextPos;
759: return curPos;
760: }
761: }
762: }
|