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