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 */ toJvmName(String classname)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 */ toJavaName(String classname)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 */ toJvmName(CtClass clazz)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 */ toClassName(String descriptor)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 */ of(String classname)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 */ rename(String desc, String oldname, String newname)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 */ rename(String desc, Map map)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 */ of(CtClass type)244 public static String of(CtClass type) { 245 StringBuffer sbuf = new StringBuffer(); 246 toDescriptor(sbuf, type); 247 return sbuf.toString(); 248 } 249 toDescriptor(StringBuffer desc, CtClass type)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 */ ofConstructor(CtClass[] paramTypes)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 */ ofMethod(CtClass returnType, CtClass[] paramTypes)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 */ ofParameters(CtClass[] paramTypes)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 */ appendParameter(String classname, String desc)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 */ insertParameter(String classname, String desc)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 */ appendParameter(CtClass type, String descriptor)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 */ insertParameter(CtClass type, String descriptor)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 */ changeReturnType(String classname, String desc)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 */ getParameterTypes(String desc, ClassPool cp)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 */ eqParamTypes(String desc1, String desc2)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 */ getParamDescriptor(String decl)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 */ getReturnType(String desc, ClassPool cp)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 */ numOfParameters(String desc)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 */ toCtClass(String desc, ClassPool cp)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 toCtClass(ClassPool cp, String desc, int i, CtClass[] args, int n)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 toPrimitiveClass(char c)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 */ arrayDimension(String desc)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 */ toArrayComponent(String desc, int dim)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 */ dataSize(String desc)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 */ paramSize(String desc)683 public static int paramSize(String desc) { 684 return -dataSize(desc, false); 685 } 686 dataSize(String desc, boolean withRet)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 */ toString(String desc)736 public static String toString(String desc) { 737 return PrettyPrinter.toString(desc); 738 } 739 740 static class PrettyPrinter { toString(String desc)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 readType(StringBuffer sbuf, int pos, String desc)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 */ Iterator(String s)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 */ hasNext()814 public boolean hasNext() { 815 return index < desc.length(); 816 } 817 818 /** 819 * Returns true if the current element is a parameter type. 820 */ isParameter()821 public boolean isParameter() { return param; } 822 823 /** 824 * Returns the first character of the current element. 825 */ currentChar()826 public char currentChar() { return desc.charAt(curPos); } 827 828 /** 829 * Returns true if the current element is double or long type. 830 */ is2byte()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 */ next()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 } 872