001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.bcel.classfile; 019 020import java.io.ByteArrayInputStream; 021import java.io.ByteArrayOutputStream; 022import java.io.CharArrayReader; 023import java.io.CharArrayWriter; 024import java.io.FilterReader; 025import java.io.FilterWriter; 026import java.io.IOException; 027import java.io.PrintStream; 028import java.io.PrintWriter; 029import java.io.Reader; 030import java.io.Writer; 031import java.util.ArrayList; 032import java.util.Arrays; 033import java.util.List; 034import java.util.Locale; 035import java.util.zip.GZIPInputStream; 036import java.util.zip.GZIPOutputStream; 037 038import org.apache.bcel.Const; 039import org.apache.bcel.util.ByteSequence; 040import org.apache.commons.lang3.ArrayUtils; 041 042/** 043 * Utility functions that do not really belong to any class in particular. 044 */ 045// @since 6.0 methods are no longer final 046public abstract class Utility { 047 048 /** 049 * Decode characters into bytes. Used by <a href="Utility.html#decode(java.lang.String, boolean)">decode()</a> 050 */ 051 private static class JavaReader extends FilterReader { 052 053 public JavaReader(final Reader in) { 054 super(in); 055 } 056 057 @Override 058 public int read() throws IOException { 059 final int b = in.read(); 060 if (b != ESCAPE_CHAR) { 061 return b; 062 } 063 final int i = in.read(); 064 if (i < 0) { 065 return -1; 066 } 067 if (i >= '0' && i <= '9' || i >= 'a' && i <= 'f') { // Normal escape 068 final int j = in.read(); 069 if (j < 0) { 070 return -1; 071 } 072 final char[] tmp = {(char) i, (char) j}; 073 return Integer.parseInt(new String(tmp), 16); 074 } 075 return MAP_CHAR[i]; 076 } 077 078 @Override 079 public int read(final char[] cbuf, final int off, final int len) throws IOException { 080 for (int i = 0; i < len; i++) { 081 cbuf[off + i] = (char) read(); 082 } 083 return len; 084 } 085 } 086 087 /** 088 * Encode bytes into valid java identifier characters. Used by 089 * <a href="Utility.html#encode(byte[], boolean)">encode()</a> 090 */ 091 private static class JavaWriter extends FilterWriter { 092 093 public JavaWriter(final Writer out) { 094 super(out); 095 } 096 097 @Override 098 public void write(final char[] cbuf, final int off, final int len) throws IOException { 099 for (int i = 0; i < len; i++) { 100 write(cbuf[off + i]); 101 } 102 } 103 104 @Override 105 public void write(final int b) throws IOException { 106 if (isJavaIdentifierPart((char) b) && b != ESCAPE_CHAR) { 107 out.write(b); 108 } else { 109 out.write(ESCAPE_CHAR); // Escape character 110 // Special escape 111 if (b >= 0 && b < FREE_CHARS) { 112 out.write(CHAR_MAP[b]); 113 } else { // Normal escape 114 final char[] tmp = Integer.toHexString(b).toCharArray(); 115 if (tmp.length == 1) { 116 out.write('0'); 117 out.write(tmp[0]); 118 } else { 119 out.write(tmp[0]); 120 out.write(tmp[1]); 121 } 122 } 123 } 124 } 125 126 @Override 127 public void write(final String str, final int off, final int len) throws IOException { 128 write(str.toCharArray(), off, len); 129 } 130 } 131 132 /* 133 * How many chars have been consumed during parsing in typeSignatureToString(). Read by methodSignatureToString(). Set 134 * by side effect, but only internally. 135 */ 136 private static final ThreadLocal<Integer> CONSUMER_CHARS = ThreadLocal.withInitial(() -> Integer.valueOf(0)); 137 138 /* 139 * The 'WIDE' instruction is used in the byte code to allow 16-bit wide indices for local variables. This opcode 140 * precedes an 'ILOAD', e.g.. The opcode immediately following takes an extra byte which is combined with the following 141 * byte to form a 16-bit value. 142 */ 143 private static boolean wide; 144 145 // A-Z, g-z, _, $ 146 private static final int FREE_CHARS = 48; 147 148 private static final int[] CHAR_MAP = new int[FREE_CHARS]; 149 150 private static final int[] MAP_CHAR = new int[256]; // Reverse map 151 152 private static final char ESCAPE_CHAR = '$'; 153 154 static { 155 int j = 0; 156 for (int i = 'A'; i <= 'Z'; i++) { 157 CHAR_MAP[j] = i; 158 MAP_CHAR[i] = j; 159 j++; 160 } 161 for (int i = 'g'; i <= 'z'; i++) { 162 CHAR_MAP[j] = i; 163 MAP_CHAR[i] = j; 164 j++; 165 } 166 CHAR_MAP[j] = '$'; 167 MAP_CHAR['$'] = j; 168 j++; 169 CHAR_MAP[j] = '_'; 170 MAP_CHAR['_'] = j; 171 } 172 173 /** 174 * Convert bit field of flags into string such as 'static final'. 175 * 176 * @param accessFlags Access flags 177 * @return String representation of flags 178 */ 179 public static String accessToString(final int accessFlags) { 180 return accessToString(accessFlags, false); 181 } 182 183 /** 184 * Convert bit field of flags into string such as 'static final'. 185 * 186 * Special case: Classes compiled with new compilers and with the 'ACC_SUPER' flag would be said to be "synchronized". 187 * This is because SUN used the same value for the flags 'ACC_SUPER' and 'ACC_SYNCHRONIZED'. 188 * 189 * @param accessFlags Access flags 190 * @param forClass access flags are for class qualifiers ? 191 * @return String representation of flags 192 */ 193 public static String accessToString(final int accessFlags, final boolean forClass) { 194 final StringBuilder buf = new StringBuilder(); 195 int p = 0; 196 for (int i = 0; p < Const.MAX_ACC_FLAG_I; i++) { // Loop through known flags 197 p = pow2(i); 198 if ((accessFlags & p) != 0) { 199 /* 200 * Special case: Classes compiled with new compilers and with the 'ACC_SUPER' flag would be said to be "synchronized". 201 * This is because SUN used the same value for the flags 'ACC_SUPER' and 'ACC_SYNCHRONIZED'. 202 */ 203 if (forClass && (p == Const.ACC_SUPER || p == Const.ACC_INTERFACE)) { 204 continue; 205 } 206 buf.append(Const.getAccessName(i)).append(" "); 207 } 208 } 209 return buf.toString().trim(); 210 } 211 212 /** 213 * Convert (signed) byte to (unsigned) short value, i.e., all negative values become positive. 214 */ 215 private static short byteToShort(final byte b) { 216 return b < 0 ? (short) (256 + b) : (short) b; 217 } 218 219 /** 220 * @param accessFlags the class flags 221 * 222 * @return "class" or "interface", depending on the ACC_INTERFACE flag 223 */ 224 public static String classOrInterface(final int accessFlags) { 225 return (accessFlags & Const.ACC_INTERFACE) != 0 ? "interface" : "class"; 226 } 227 228 /** 229 * @return 'flag' with bit 'i' set to 0 230 */ 231 public static int clearBit(final int flag, final int i) { 232 final int bit = pow2(i); 233 return (flag & bit) == 0 ? flag : flag ^ bit; 234 } 235 236 public static String codeToString(final byte[] code, final ConstantPool constantPool, final int index, final int length) { 237 return codeToString(code, constantPool, index, length, true); 238 } 239 240 /** 241 * Disassemble a byte array of JVM byte codes starting from code line 'index' and return the disassembled string 242 * representation. Decode only 'num' opcodes (including their operands), use -1 if you want to decompile everything. 243 * 244 * @param code byte code array 245 * @param constantPool Array of constants 246 * @param index offset in 'code' array <EM>(number of opcodes, not bytes!)</EM> 247 * @param length number of opcodes to decompile, -1 for all 248 * @param verbose be verbose, e.g. print constant pool index 249 * @return String representation of byte codes 250 */ 251 public static String codeToString(final byte[] code, final ConstantPool constantPool, final int index, final int length, final boolean verbose) { 252 final StringBuilder buf = new StringBuilder(code.length * 20); // Should be sufficient // CHECKSTYLE IGNORE MagicNumber 253 try (ByteSequence stream = new ByteSequence(code)) { 254 for (int i = 0; i < index; i++) { 255 codeToString(stream, constantPool, verbose); 256 } 257 for (int i = 0; stream.available() > 0; i++) { 258 if (length < 0 || i < length) { 259 final String indices = fillup(stream.getIndex() + ":", 6, true, ' '); 260 buf.append(indices).append(codeToString(stream, constantPool, verbose)).append('\n'); 261 } 262 } 263 } catch (final IOException e) { 264 throw new ClassFormatException("Byte code error: " + buf.toString(), e); 265 } 266 return buf.toString(); 267 } 268 269 public static String codeToString(final ByteSequence bytes, final ConstantPool constantPool) throws IOException { 270 return codeToString(bytes, constantPool, true); 271 } 272 273 /** 274 * Disassemble a stream of byte codes and return the string representation. 275 * 276 * @param bytes stream of bytes 277 * @param constantPool Array of constants 278 * @param verbose be verbose, e.g. print constant pool index 279 * @return String representation of byte code 280 * 281 * @throws IOException if a failure from reading from the bytes argument occurs 282 */ 283 public static String codeToString(final ByteSequence bytes, final ConstantPool constantPool, final boolean verbose) throws IOException { 284 final short opcode = (short) bytes.readUnsignedByte(); 285 int defaultOffset = 0; 286 int low; 287 int high; 288 int npairs; 289 int index; 290 int vindex; 291 int constant; 292 int[] match; 293 int[] jumpTable; 294 int noPadBytes = 0; 295 int offset; 296 final StringBuilder buf = new StringBuilder(Const.getOpcodeName(opcode)); 297 /* 298 * Special case: Skip (0-3) padding bytes, i.e., the following bytes are 4-byte-aligned 299 */ 300 if (opcode == Const.TABLESWITCH || opcode == Const.LOOKUPSWITCH) { 301 final int remainder = bytes.getIndex() % 4; 302 noPadBytes = remainder == 0 ? 0 : 4 - remainder; 303 for (int i = 0; i < noPadBytes; i++) { 304 byte b; 305 if ((b = bytes.readByte()) != 0) { 306 System.err.println("Warning: Padding byte != 0 in " + Const.getOpcodeName(opcode) + ":" + b); 307 } 308 } 309 // Both cases have a field default_offset in common 310 defaultOffset = bytes.readInt(); 311 } 312 switch (opcode) { 313 /* 314 * Table switch has variable length arguments. 315 */ 316 case Const.TABLESWITCH: 317 low = bytes.readInt(); 318 high = bytes.readInt(); 319 offset = bytes.getIndex() - 12 - noPadBytes - 1; 320 defaultOffset += offset; 321 buf.append("\tdefault = ").append(defaultOffset).append(", low = ").append(low).append(", high = ").append(high).append("("); 322 jumpTable = new int[high - low + 1]; 323 for (int i = 0; i < jumpTable.length; i++) { 324 jumpTable[i] = offset + bytes.readInt(); 325 buf.append(jumpTable[i]); 326 if (i < jumpTable.length - 1) { 327 buf.append(", "); 328 } 329 } 330 buf.append(")"); 331 break; 332 /* 333 * Lookup switch has variable length arguments. 334 */ 335 case Const.LOOKUPSWITCH: { 336 npairs = bytes.readInt(); 337 offset = bytes.getIndex() - 8 - noPadBytes - 1; 338 match = new int[npairs]; 339 jumpTable = new int[npairs]; 340 defaultOffset += offset; 341 buf.append("\tdefault = ").append(defaultOffset).append(", npairs = ").append(npairs).append(" ("); 342 for (int i = 0; i < npairs; i++) { 343 match[i] = bytes.readInt(); 344 jumpTable[i] = offset + bytes.readInt(); 345 buf.append("(").append(match[i]).append(", ").append(jumpTable[i]).append(")"); 346 if (i < npairs - 1) { 347 buf.append(", "); 348 } 349 } 350 buf.append(")"); 351 } 352 break; 353 /* 354 * Two address bytes + offset from start of byte stream form the jump target 355 */ 356 case Const.GOTO: 357 case Const.IFEQ: 358 case Const.IFGE: 359 case Const.IFGT: 360 case Const.IFLE: 361 case Const.IFLT: 362 case Const.JSR: 363 case Const.IFNE: 364 case Const.IFNONNULL: 365 case Const.IFNULL: 366 case Const.IF_ACMPEQ: 367 case Const.IF_ACMPNE: 368 case Const.IF_ICMPEQ: 369 case Const.IF_ICMPGE: 370 case Const.IF_ICMPGT: 371 case Const.IF_ICMPLE: 372 case Const.IF_ICMPLT: 373 case Const.IF_ICMPNE: 374 buf.append("\t\t#").append(bytes.getIndex() - 1 + bytes.readShort()); 375 break; 376 /* 377 * 32-bit wide jumps 378 */ 379 case Const.GOTO_W: 380 case Const.JSR_W: 381 buf.append("\t\t#").append(bytes.getIndex() - 1 + bytes.readInt()); 382 break; 383 /* 384 * Index byte references local variable (register) 385 */ 386 case Const.ALOAD: 387 case Const.ASTORE: 388 case Const.DLOAD: 389 case Const.DSTORE: 390 case Const.FLOAD: 391 case Const.FSTORE: 392 case Const.ILOAD: 393 case Const.ISTORE: 394 case Const.LLOAD: 395 case Const.LSTORE: 396 case Const.RET: 397 if (wide) { 398 vindex = bytes.readUnsignedShort(); 399 wide = false; // Clear flag 400 } else { 401 vindex = bytes.readUnsignedByte(); 402 } 403 buf.append("\t\t%").append(vindex); 404 break; 405 /* 406 * Remember wide byte which is used to form a 16-bit address in the following instruction. Relies on that the method is 407 * called again with the following opcode. 408 */ 409 case Const.WIDE: 410 wide = true; 411 buf.append("\t(wide)"); 412 break; 413 /* 414 * Array of basic type. 415 */ 416 case Const.NEWARRAY: 417 buf.append("\t\t<").append(Const.getTypeName(bytes.readByte())).append(">"); 418 break; 419 /* 420 * Access object/class fields. 421 */ 422 case Const.GETFIELD: 423 case Const.GETSTATIC: 424 case Const.PUTFIELD: 425 case Const.PUTSTATIC: 426 index = bytes.readUnsignedShort(); 427 buf.append("\t\t").append(constantPool.constantToString(index, Const.CONSTANT_Fieldref)).append(verbose ? " (" + index + ")" : ""); 428 break; 429 /* 430 * Operands are references to classes in constant pool 431 */ 432 case Const.NEW: 433 case Const.CHECKCAST: 434 buf.append("\t"); 435 //$FALL-THROUGH$ 436 case Const.INSTANCEOF: 437 index = bytes.readUnsignedShort(); 438 buf.append("\t<").append(constantPool.constantToString(index, Const.CONSTANT_Class)).append(">").append(verbose ? " (" + index + ")" : ""); 439 break; 440 /* 441 * Operands are references to methods in constant pool 442 */ 443 case Const.INVOKESPECIAL: 444 case Const.INVOKESTATIC: 445 index = bytes.readUnsignedShort(); 446 final Constant c = constantPool.getConstant(index); 447 // With Java8 operand may be either a CONSTANT_Methodref 448 // or a CONSTANT_InterfaceMethodref. (markro) 449 buf.append("\t").append(constantPool.constantToString(index, c.getTag())).append(verbose ? " (" + index + ")" : ""); 450 break; 451 case Const.INVOKEVIRTUAL: 452 index = bytes.readUnsignedShort(); 453 buf.append("\t").append(constantPool.constantToString(index, Const.CONSTANT_Methodref)).append(verbose ? " (" + index + ")" : ""); 454 break; 455 case Const.INVOKEINTERFACE: 456 index = bytes.readUnsignedShort(); 457 final int nargs = bytes.readUnsignedByte(); // historical, redundant 458 buf.append("\t").append(constantPool.constantToString(index, Const.CONSTANT_InterfaceMethodref)).append(verbose ? " (" + index + ")\t" : "") 459 .append(nargs).append("\t").append(bytes.readUnsignedByte()); // Last byte is a reserved space 460 break; 461 case Const.INVOKEDYNAMIC: 462 index = bytes.readUnsignedShort(); 463 buf.append("\t").append(constantPool.constantToString(index, Const.CONSTANT_InvokeDynamic)).append(verbose ? " (" + index + ")\t" : "") 464 .append(bytes.readUnsignedByte()) // Thrid byte is a reserved space 465 .append(bytes.readUnsignedByte()); // Last byte is a reserved space 466 break; 467 /* 468 * Operands are references to items in constant pool 469 */ 470 case Const.LDC_W: 471 case Const.LDC2_W: 472 index = bytes.readUnsignedShort(); 473 buf.append("\t\t").append(constantPool.constantToString(index, constantPool.getConstant(index).getTag())) 474 .append(verbose ? " (" + index + ")" : ""); 475 break; 476 case Const.LDC: 477 index = bytes.readUnsignedByte(); 478 buf.append("\t\t").append(constantPool.constantToString(index, constantPool.getConstant(index).getTag())) 479 .append(verbose ? " (" + index + ")" : ""); 480 break; 481 /* 482 * Array of references. 483 */ 484 case Const.ANEWARRAY: 485 index = bytes.readUnsignedShort(); 486 buf.append("\t\t<").append(compactClassName(constantPool.getConstantString(index, Const.CONSTANT_Class), false)).append(">") 487 .append(verbose ? " (" + index + ")" : ""); 488 break; 489 /* 490 * Multidimensional array of references. 491 */ 492 case Const.MULTIANEWARRAY: { 493 index = bytes.readUnsignedShort(); 494 final int dimensions = bytes.readUnsignedByte(); 495 buf.append("\t<").append(compactClassName(constantPool.getConstantString(index, Const.CONSTANT_Class), false)).append(">\t").append(dimensions) 496 .append(verbose ? " (" + index + ")" : ""); 497 } 498 break; 499 /* 500 * Increment local variable. 501 */ 502 case Const.IINC: 503 if (wide) { 504 vindex = bytes.readUnsignedShort(); 505 constant = bytes.readShort(); 506 wide = false; 507 } else { 508 vindex = bytes.readUnsignedByte(); 509 constant = bytes.readByte(); 510 } 511 buf.append("\t\t%").append(vindex).append("\t").append(constant); 512 break; 513 default: 514 if (Const.getNoOfOperands(opcode) > 0) { 515 for (int i = 0; i < Const.getOperandTypeCount(opcode); i++) { 516 buf.append("\t\t"); 517 switch (Const.getOperandType(opcode, i)) { 518 case Const.T_BYTE: 519 buf.append(bytes.readByte()); 520 break; 521 case Const.T_SHORT: 522 buf.append(bytes.readShort()); 523 break; 524 case Const.T_INT: 525 buf.append(bytes.readInt()); 526 break; 527 default: // Never reached 528 throw new IllegalStateException("Unreachable default case reached!"); 529 } 530 } 531 } 532 } 533 return buf.toString(); 534 } 535 536 /** 537 * Shorten long class names, <em>java/lang/String</em> becomes <em>String</em>. 538 * 539 * @param str The long class name 540 * @return Compacted class name 541 */ 542 public static String compactClassName(final String str) { 543 return compactClassName(str, true); 544 } 545 546 /** 547 * Shorten long class names, <em>java/lang/String</em> becomes <em>java.lang.String</em>, e.g.. If <em>chopit</em> is 548 * <em>true</em> the prefix <em>java.lang</em> is also removed. 549 * 550 * @param str The long class name 551 * @param chopit flag that determines whether chopping is executed or not 552 * @return Compacted class name 553 */ 554 public static String compactClassName(final String str, final boolean chopit) { 555 return compactClassName(str, "java.lang.", chopit); 556 } 557 558 /** 559 * Shorten long class name <em>str</em>, i.e., chop off the <em>prefix</em>, if the class name starts with this string 560 * and the flag <em>chopit</em> is true. Slashes <em>/</em> are converted to dots <em>.</em>. 561 * 562 * @param str The long class name 563 * @param prefix The prefix the get rid off 564 * @param chopit flag that determines whether chopping is executed or not 565 * @return Compacted class name 566 */ 567 public static String compactClassName(String str, final String prefix, final boolean chopit) { 568 final int len = prefix.length(); 569 str = pathToPackage(str); // Is '/' on all systems, even DOS 570 // If string starts with 'prefix' and contains no further dots 571 if (chopit && str.startsWith(prefix) && str.substring(len).indexOf('.') == -1) { 572 str = str.substring(len); 573 } 574 return str; 575 } 576 577 /** 578 * Escape all occurrences of newline chars '\n', quotes \", etc. 579 */ 580 public static String convertString(final String label) { 581 final char[] ch = label.toCharArray(); 582 final StringBuilder buf = new StringBuilder(); 583 for (final char element : ch) { 584 switch (element) { 585 case '\n': 586 buf.append("\\n"); 587 break; 588 case '\r': 589 buf.append("\\r"); 590 break; 591 case '\"': 592 buf.append("\\\""); 593 break; 594 case '\'': 595 buf.append("\\'"); 596 break; 597 case '\\': 598 buf.append("\\\\"); 599 break; 600 default: 601 buf.append(element); 602 break; 603 } 604 } 605 return buf.toString(); 606 } 607 608 private static int countBrackets(final String brackets) { 609 final char[] chars = brackets.toCharArray(); 610 int count = 0; 611 boolean open = false; 612 for (final char c : chars) { 613 switch (c) { 614 case '[': 615 if (open) { 616 throw new IllegalArgumentException("Illegally nested brackets:" + brackets); 617 } 618 open = true; 619 break; 620 case ']': 621 if (!open) { 622 throw new IllegalArgumentException("Illegally nested brackets:" + brackets); 623 } 624 open = false; 625 count++; 626 break; 627 default: 628 // Don't care 629 break; 630 } 631 } 632 if (open) { 633 throw new IllegalArgumentException("Illegally nested brackets:" + brackets); 634 } 635 return count; 636 } 637 638 /** 639 * Decode a string back to a byte array. 640 * 641 * @param s the string to convert 642 * @param uncompress use gzip to uncompress the stream of bytes 643 * 644 * @throws IOException if there's a gzip exception 645 */ 646 public static byte[] decode(final String s, final boolean uncompress) throws IOException { 647 byte[] bytes; 648 try (JavaReader jr = new JavaReader(new CharArrayReader(s.toCharArray())); ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 649 int ch; 650 while ((ch = jr.read()) >= 0) { 651 bos.write(ch); 652 } 653 bytes = bos.toByteArray(); 654 } 655 if (uncompress) { 656 final GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(bytes)); 657 final byte[] tmp = new byte[bytes.length * 3]; // Rough estimate 658 int count = 0; 659 int b; 660 while ((b = gis.read()) >= 0) { 661 tmp[count++] = (byte) b; 662 } 663 bytes = Arrays.copyOf(tmp, count); 664 } 665 return bytes; 666 } 667 668 /** 669 * Encode byte array it into Java identifier string, i.e., a string that only contains the following characters: (a, ... 670 * z, A, ... Z, 0, ... 9, _, $). The encoding algorithm itself is not too clever: if the current byte's ASCII value 671 * already is a valid Java identifier part, leave it as it is. Otherwise it writes the escape character($) followed by: 672 * 673 * <ul> 674 * <li>the ASCII value as a hexadecimal string, if the value is not in the range 200..247</li> 675 * <li>a Java identifier char not used in a lowercase hexadecimal string, if the value is in the range 200..247</li> 676 * </ul> 677 * 678 * <p> 679 * This operation inflates the original byte array by roughly 40-50% 680 * </p> 681 * 682 * @param bytes the byte array to convert 683 * @param compress use gzip to minimize string 684 * 685 * @throws IOException if there's a gzip exception 686 */ 687 public static String encode(byte[] bytes, final boolean compress) throws IOException { 688 if (compress) { 689 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); GZIPOutputStream gos = new GZIPOutputStream(baos)) { 690 gos.write(bytes, 0, bytes.length); 691 gos.close(); 692 bytes = baos.toByteArray(); 693 } 694 } 695 final CharArrayWriter caw = new CharArrayWriter(); 696 try (JavaWriter jw = new JavaWriter(caw)) { 697 for (final byte b : bytes) { 698 final int in = b & 0x000000ff; // Normalize to unsigned 699 jw.write(in); 700 } 701 } 702 return caw.toString(); 703 } 704 705 /** 706 * Fillup char with up to length characters with char 'fill' and justify it left or right. 707 * 708 * @param str string to format 709 * @param length length of desired string 710 * @param leftJustify format left or right 711 * @param fill fill character 712 * @return formatted string 713 */ 714 public static String fillup(final String str, final int length, final boolean leftJustify, final char fill) { 715 final int len = length - str.length(); 716 final char[] buf = new char[Math.max(len, 0)]; 717 Arrays.fill(buf, fill); 718 if (leftJustify) { 719 return str + new String(buf); 720 } 721 return new String(buf) + str; 722 } 723 724 /** 725 * Return a string for an integer justified left or right and filled up with 'fill' characters if necessary. 726 * 727 * @param i integer to format 728 * @param length length of desired string 729 * @param leftJustify format left or right 730 * @param fill fill character 731 * @return formatted int 732 */ 733 public static String format(final int i, final int length, final boolean leftJustify, final char fill) { 734 return fillup(Integer.toString(i), length, leftJustify, fill); 735 } 736 737 /** 738 * WARNING: 739 * 740 * There is some nomenclature confusion through much of the BCEL code base with respect to the terms Descriptor and 741 * Signature. For the offical definitions see: 742 * 743 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3"> Descriptors in The Java 744 * Virtual Machine Specification</a> 745 * 746 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.9.1"> Signatures in The Java 747 * Virtual Machine Specification</a> 748 * 749 * In brief, a descriptor is a string representing the type of a field or method. Signatures are similar, but more 750 * complex. Signatures are used to encode declarations written in the Java programming language that use types 751 * outside the type system of the Java Virtual Machine. They are used to describe the type of any class, interface, 752 * constructor, method or field whose declaration uses type variables or parameterized types. 753 * 754 * To parse a descriptor, call typeSignatureToString. To parse a signature, call signatureToString. 755 * 756 * Note that if the signature string is a single, non-generic item, the call to signatureToString reduces to a call 757 * to typeSignatureToString. Also note, that if you only wish to parse the first item in a longer signature string, 758 * you should call typeSignatureToString directly. 759 */ 760 761 /** 762 * Parse Java type such as "char", or "java.lang.String[]" and return the signature in byte code format, e.g. "C" or 763 * "[Ljava/lang/String;" respectively. 764 * 765 * @param type Java type 766 * @return byte code signature 767 */ 768 public static String getSignature(String type) { 769 final StringBuilder buf = new StringBuilder(); 770 final char[] chars = type.toCharArray(); 771 boolean charFound = false; 772 boolean delim = false; 773 int index = -1; 774 loop: for (int i = 0; i < chars.length; i++) { 775 switch (chars[i]) { 776 case ' ': 777 case '\t': 778 case '\n': 779 case '\r': 780 case '\f': 781 if (charFound) { 782 delim = true; 783 } 784 break; 785 case '[': 786 if (!charFound) { 787 throw new IllegalArgumentException("Illegal type: " + type); 788 } 789 index = i; 790 break loop; 791 default: 792 charFound = true; 793 if (!delim) { 794 buf.append(chars[i]); 795 } 796 } 797 } 798 int brackets = 0; 799 if (index > 0) { 800 brackets = countBrackets(type.substring(index)); 801 } 802 type = buf.toString(); 803 buf.setLength(0); 804 for (int i = 0; i < brackets; i++) { 805 buf.append('['); 806 } 807 boolean found = false; 808 for (int i = Const.T_BOOLEAN; i <= Const.T_VOID && !found; i++) { 809 if (Const.getTypeName(i).equals(type)) { 810 found = true; 811 buf.append(Const.getShortTypeName(i)); 812 } 813 } 814 if (!found) { 815 buf.append('L').append(packageToPath(type)).append(';'); 816 } 817 return buf.toString(); 818 } 819 820 /** 821 * @param ch the character to test if it's part of an identifier 822 * 823 * @return true, if character is one of (a, ... z, A, ... Z, 0, ... 9, _) 824 */ 825 public static boolean isJavaIdentifierPart(final char ch) { 826 return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch == '_'; 827 } 828 829 /** 830 * @return true, if bit 'i' in 'flag' is set 831 */ 832 public static boolean isSet(final int flag, final int i) { 833 return (flag & pow2(i)) != 0; 834 } 835 836 /** 837 * Converts argument list portion of method signature to string with all class names compacted. 838 * 839 * @param signature Method signature 840 * @return String Array of argument types 841 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file 842 */ 843 public static String[] methodSignatureArgumentTypes(final String signature) throws ClassFormatException { 844 return methodSignatureArgumentTypes(signature, true); 845 } 846 847 /** 848 * Converts argument list portion of method signature to string. 849 * 850 * @param signature Method signature 851 * @param chopit flag that determines whether chopping is executed or not 852 * @return String Array of argument types 853 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file 854 */ 855 public static String[] methodSignatureArgumentTypes(final String signature, final boolean chopit) throws ClassFormatException { 856 final List<String> vec = new ArrayList<>(); 857 int index; 858 try { 859 // Skip any type arguments to read argument declarations between '(' and ')' 860 index = signature.indexOf('(') + 1; 861 if (index <= 0) { 862 throw new ClassFormatException("Invalid method signature: " + signature); 863 } 864 while (signature.charAt(index) != ')') { 865 vec.add(typeSignatureToString(signature.substring(index), chopit)); 866 // corrected concurrent private static field acess 867 index += unwrap(CONSUMER_CHARS); // update position 868 } 869 } catch (final StringIndexOutOfBoundsException e) { // Should never occur 870 throw new ClassFormatException("Invalid method signature: " + signature, e); 871 } 872 return vec.toArray(ArrayUtils.EMPTY_STRING_ARRAY); 873 } 874 875 /** 876 * Converts return type portion of method signature to string with all class names compacted. 877 * 878 * @param signature Method signature 879 * @return String representation of method return type 880 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file 881 */ 882 public static String methodSignatureReturnType(final String signature) throws ClassFormatException { 883 return methodSignatureReturnType(signature, true); 884 } 885 886 /** 887 * Converts return type portion of method signature to string. 888 * 889 * @param signature Method signature 890 * @param chopit flag that determines whether chopping is executed or not 891 * @return String representation of method return type 892 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file 893 */ 894 public static String methodSignatureReturnType(final String signature, final boolean chopit) throws ClassFormatException { 895 int index; 896 String type; 897 try { 898 // Read return type after ')' 899 index = signature.lastIndexOf(')') + 1; 900 if (index <= 0) { 901 throw new ClassFormatException("Invalid method signature: " + signature); 902 } 903 type = typeSignatureToString(signature.substring(index), chopit); 904 } catch (final StringIndexOutOfBoundsException e) { // Should never occur 905 throw new ClassFormatException("Invalid method signature: " + signature, e); 906 } 907 return type; 908 } 909 910 /** 911 * Converts method signature to string with all class names compacted. 912 * 913 * @param signature to convert 914 * @param name of method 915 * @param access flags of method 916 * @return Human readable signature 917 */ 918 public static String methodSignatureToString(final String signature, final String name, final String access) { 919 return methodSignatureToString(signature, name, access, true); 920 } 921 922 /** 923 * Converts method signature to string. 924 * 925 * @param signature to convert 926 * @param name of method 927 * @param access flags of method 928 * @param chopit flag that determines whether chopping is executed or not 929 * @return Human readable signature 930 */ 931 public static String methodSignatureToString(final String signature, final String name, final String access, final boolean chopit) { 932 return methodSignatureToString(signature, name, access, chopit, null); 933 } 934 935 /** 936 * This method converts a method signature string into a Java type declaration like 'void main(String[])' and throws a 937 * 'ClassFormatException' when the parsed type is invalid. 938 * 939 * @param signature Method signature 940 * @param name Method name 941 * @param access Method access rights 942 * @param chopit flag that determines whether chopping is executed or not 943 * @param vars the LocalVariableTable for the method 944 * @return Java type declaration 945 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file 946 */ 947 public static String methodSignatureToString(final String signature, final String name, final String access, final boolean chopit, 948 final LocalVariableTable vars) throws ClassFormatException { 949 final StringBuilder buf = new StringBuilder("("); 950 String type; 951 int index; 952 int varIndex = access.contains("static") ? 0 : 1; 953 try { 954 // Skip any type arguments to read argument declarations between '(' and ')' 955 index = signature.indexOf('(') + 1; 956 if (index <= 0) { 957 throw new ClassFormatException("Invalid method signature: " + signature); 958 } 959 while (signature.charAt(index) != ')') { 960 final String paramType = typeSignatureToString(signature.substring(index), chopit); 961 buf.append(paramType); 962 if (vars != null) { 963 final LocalVariable l = vars.getLocalVariable(varIndex, 0); 964 if (l != null) { 965 buf.append(" ").append(l.getName()); 966 } 967 } else { 968 buf.append(" arg").append(varIndex); 969 } 970 if ("double".equals(paramType) || "long".equals(paramType)) { 971 varIndex += 2; 972 } else { 973 varIndex++; 974 } 975 buf.append(", "); 976 // corrected concurrent private static field acess 977 index += unwrap(CONSUMER_CHARS); // update position 978 } 979 index++; // update position 980 // Read return type after ')' 981 type = typeSignatureToString(signature.substring(index), chopit); 982 } catch (final StringIndexOutOfBoundsException e) { // Should never occur 983 throw new ClassFormatException("Invalid method signature: " + signature, e); 984 } 985 // ignore any throws information in the signature 986 if (buf.length() > 1) { 987 buf.setLength(buf.length() - 2); 988 } 989 buf.append(")"); 990 return access + (!access.isEmpty() ? " " : "") + // May be an empty string 991 type + " " + name + buf.toString(); 992 } 993 994 /** 995 * Converts string containing the method return and argument types to a byte code method signature. 996 * 997 * @param ret Return type of method 998 * @param argv Types of method arguments 999 * @return Byte code representation of method signature 1000 * 1001 * @throws ClassFormatException if the signature is for Void 1002 */ 1003 public static String methodTypeToSignature(final String ret, final String[] argv) throws ClassFormatException { 1004 final StringBuilder buf = new StringBuilder("("); 1005 String str; 1006 if (argv != null) { 1007 for (final String element : argv) { 1008 str = getSignature(element); 1009 if (str.endsWith("V")) { 1010 throw new ClassFormatException("Invalid type: " + element); 1011 } 1012 buf.append(str); 1013 } 1014 } 1015 str = getSignature(ret); 1016 buf.append(")").append(str); 1017 return buf.toString(); 1018 } 1019 1020 /** 1021 * Converts '.'s to '/'s. 1022 * 1023 * @param name Source 1024 * @return converted value 1025 * @since 6.7.0 1026 */ 1027 public static String packageToPath(final String name) { 1028 return name.replace('.', '/'); 1029 } 1030 1031 /** 1032 * Converts a path to a package name. 1033 * 1034 * @param str the source path. 1035 * @return a package name. 1036 * @since 6.6.0 1037 */ 1038 public static String pathToPackage(final String str) { 1039 return str.replace('/', '.'); 1040 } 1041 1042 private static int pow2(final int n) { 1043 return 1 << n; 1044 } 1045 1046 public static String printArray(final Object[] obj) { 1047 return printArray(obj, true); 1048 } 1049 1050 public static String printArray(final Object[] obj, final boolean braces) { 1051 return printArray(obj, braces, false); 1052 } 1053 1054 public static String printArray(final Object[] obj, final boolean braces, final boolean quote) { 1055 if (obj == null) { 1056 return null; 1057 } 1058 final StringBuilder buf = new StringBuilder(); 1059 if (braces) { 1060 buf.append('{'); 1061 } 1062 for (int i = 0; i < obj.length; i++) { 1063 if (obj[i] != null) { 1064 buf.append(quote ? "\"" : "").append(obj[i]).append(quote ? "\"" : ""); 1065 } else { 1066 buf.append("null"); 1067 } 1068 if (i < obj.length - 1) { 1069 buf.append(", "); 1070 } 1071 } 1072 if (braces) { 1073 buf.append('}'); 1074 } 1075 return buf.toString(); 1076 } 1077 1078 public static void printArray(final PrintStream out, final Object[] obj) { 1079 out.println(printArray(obj, true)); 1080 } 1081 1082 public static void printArray(final PrintWriter out, final Object[] obj) { 1083 out.println(printArray(obj, true)); 1084 } 1085 1086 /** 1087 * Replace all occurrences of <em>old</em> in <em>str</em> with <em>new</em>. 1088 * 1089 * @param str String to permute 1090 * @param old String to be replaced 1091 * @param new_ Replacement string 1092 * @return new String object 1093 */ 1094 public static String replace(String str, final String old, final String new_) { 1095 int index; 1096 int oldIndex; 1097 try { 1098 if (str.contains(old)) { // 'old' found in str 1099 final StringBuilder buf = new StringBuilder(); 1100 oldIndex = 0; // String start offset 1101 // While we have something to replace 1102 while ((index = str.indexOf(old, oldIndex)) != -1) { 1103 buf.append(str, oldIndex, index); // append prefix 1104 buf.append(new_); // append replacement 1105 oldIndex = index + old.length(); // Skip 'old'.length chars 1106 } 1107 buf.append(str.substring(oldIndex)); // append rest of string 1108 str = buf.toString(); 1109 } 1110 } catch (final StringIndexOutOfBoundsException e) { // Should not occur 1111 System.err.println(e); 1112 } 1113 return str; 1114 } 1115 1116 /** 1117 * Map opcode names to opcode numbers. E.g., return Constants.ALOAD for "aload" 1118 */ 1119 public static short searchOpcode(String name) { 1120 name = name.toLowerCase(Locale.ENGLISH); 1121 for (short i = 0; i < Const.OPCODE_NAMES_LENGTH; i++) { 1122 if (Const.getOpcodeName(i).equals(name)) { 1123 return i; 1124 } 1125 } 1126 return -1; 1127 } 1128 1129 /** 1130 * @return 'flag' with bit 'i' set to 1 1131 */ 1132 public static int setBit(final int flag, final int i) { 1133 return flag | pow2(i); 1134 } 1135 1136 /** 1137 * Converts a signature to a string with all class names compacted. Class, Method and Type signatures are supported. 1138 * Enum and Interface signatures are not supported. 1139 * 1140 * @param signature signature to convert 1141 * @return String containg human readable signature 1142 */ 1143 public static String signatureToString(final String signature) { 1144 return signatureToString(signature, true); 1145 } 1146 1147 /** 1148 * Converts a signature to a string. Class, Method and Type signatures are supported. Enum and Interface signatures are 1149 * not supported. 1150 * 1151 * @param signature signature to convert 1152 * @param chopit flag that determines whether chopping is executed or not 1153 * @return String containg human readable signature 1154 */ 1155 public static String signatureToString(final String signature, final boolean chopit) { 1156 String type = ""; 1157 String typeParams = ""; 1158 int index = 0; 1159 if (signature.charAt(0) == '<') { 1160 // we have type paramters 1161 typeParams = typeParamTypesToString(signature, chopit); 1162 index += unwrap(CONSUMER_CHARS); // update position 1163 } 1164 if (signature.charAt(index) == '(') { 1165 // We have a Method signature. 1166 // add types of arguments 1167 type = typeParams + typeSignaturesToString(signature.substring(index), chopit, ')'); 1168 index += unwrap(CONSUMER_CHARS); // update position 1169 // add return type 1170 type = type + typeSignatureToString(signature.substring(index), chopit); 1171 index += unwrap(CONSUMER_CHARS); // update position 1172 // ignore any throws information in the signature 1173 return type; 1174 } 1175 // Could be Class or Type... 1176 type = typeSignatureToString(signature.substring(index), chopit); 1177 index += unwrap(CONSUMER_CHARS); // update position 1178 if (typeParams.isEmpty() && index == signature.length()) { 1179 // We have a Type signature. 1180 return type; 1181 } 1182 // We have a Class signature. 1183 final StringBuilder typeClass = new StringBuilder(typeParams); 1184 typeClass.append(" extends "); 1185 typeClass.append(type); 1186 if (index < signature.length()) { 1187 typeClass.append(" implements "); 1188 typeClass.append(typeSignatureToString(signature.substring(index), chopit)); 1189 index += unwrap(CONSUMER_CHARS); // update position 1190 } 1191 while (index < signature.length()) { 1192 typeClass.append(", "); 1193 typeClass.append(typeSignatureToString(signature.substring(index), chopit)); 1194 index += unwrap(CONSUMER_CHARS); // update position 1195 } 1196 return typeClass.toString(); 1197 } 1198 1199 /** 1200 * Convert bytes into hexadecimal string 1201 * 1202 * @param bytes an array of bytes to convert to hexadecimal 1203 * 1204 * @return bytes as hexadecimal string, e.g. 00 fa 12 ... 1205 */ 1206 public static String toHexString(final byte[] bytes) { 1207 final StringBuilder buf = new StringBuilder(); 1208 for (int i = 0; i < bytes.length; i++) { 1209 final short b = byteToShort(bytes[i]); 1210 final String hex = Integer.toHexString(b); 1211 if (b < 0x10) { 1212 buf.append('0'); 1213 } 1214 buf.append(hex); 1215 if (i < bytes.length - 1) { 1216 buf.append(' '); 1217 } 1218 } 1219 return buf.toString(); 1220 } 1221 1222 /** 1223 * Return type of method signature as a byte value as defined in <em>Constants</em> 1224 * 1225 * @param signature in format described above 1226 * @return type of method signature 1227 * @see Const 1228 * 1229 * @throws ClassFormatException if signature is not a method signature 1230 */ 1231 public static byte typeOfMethodSignature(final String signature) throws ClassFormatException { 1232 int index; 1233 try { 1234 if (signature.charAt(0) != '(') { 1235 throw new ClassFormatException("Invalid method signature: " + signature); 1236 } 1237 index = signature.lastIndexOf(')') + 1; 1238 return typeOfSignature(signature.substring(index)); 1239 } catch (final StringIndexOutOfBoundsException e) { 1240 throw new ClassFormatException("Invalid method signature: " + signature, e); 1241 } 1242 } 1243 1244 /** 1245 * Return type of signature as a byte value as defined in <em>Constants</em> 1246 * 1247 * @param signature in format described above 1248 * @return type of signature 1249 * @see Const 1250 * 1251 * @throws ClassFormatException if signature isn't a known type 1252 */ 1253 public static byte typeOfSignature(final String signature) throws ClassFormatException { 1254 try { 1255 switch (signature.charAt(0)) { 1256 case 'B': 1257 return Const.T_BYTE; 1258 case 'C': 1259 return Const.T_CHAR; 1260 case 'D': 1261 return Const.T_DOUBLE; 1262 case 'F': 1263 return Const.T_FLOAT; 1264 case 'I': 1265 return Const.T_INT; 1266 case 'J': 1267 return Const.T_LONG; 1268 case 'L': 1269 case 'T': 1270 return Const.T_REFERENCE; 1271 case '[': 1272 return Const.T_ARRAY; 1273 case 'V': 1274 return Const.T_VOID; 1275 case 'Z': 1276 return Const.T_BOOLEAN; 1277 case 'S': 1278 return Const.T_SHORT; 1279 case '!': 1280 case '+': 1281 case '*': 1282 return typeOfSignature(signature.substring(1)); 1283 default: 1284 throw new ClassFormatException("Invalid method signature: " + signature); 1285 } 1286 } catch (final StringIndexOutOfBoundsException e) { 1287 throw new ClassFormatException("Invalid method signature: " + signature, e); 1288 } 1289 } 1290 1291 /** 1292 * Converts a type parameter list signature to a string. 1293 * 1294 * @param signature signature to convert 1295 * @param chopit flag that determines whether chopping is executed or not 1296 * @return String containg human readable signature 1297 */ 1298 private static String typeParamTypesToString(final String signature, final boolean chopit) { 1299 // The first character is guranteed to be '<' 1300 final StringBuilder typeParams = new StringBuilder("<"); 1301 int index = 1; // skip the '<' 1302 // get the first TypeParameter 1303 typeParams.append(typeParamTypeToString(signature.substring(index), chopit)); 1304 index += unwrap(CONSUMER_CHARS); // update position 1305 // are there more TypeParameters? 1306 while (signature.charAt(index) != '>') { 1307 typeParams.append(", "); 1308 typeParams.append(typeParamTypeToString(signature.substring(index), chopit)); 1309 index += unwrap(CONSUMER_CHARS); // update position 1310 } 1311 wrap(CONSUMER_CHARS, index + 1); // account for the '>' char 1312 return typeParams.append(">").toString(); 1313 } 1314 1315 /** 1316 * Converts a type parameter signature to a string. 1317 * 1318 * @param signature signature to convert 1319 * @param chopit flag that determines whether chopping is executed or not 1320 * @return String containg human readable signature 1321 */ 1322 private static String typeParamTypeToString(final String signature, final boolean chopit) { 1323 int index = signature.indexOf(':'); 1324 if (index <= 0) { 1325 throw new ClassFormatException("Invalid type parameter signature: " + signature); 1326 } 1327 // get the TypeParameter identifier 1328 final StringBuilder typeParam = new StringBuilder(signature.substring(0, index)); 1329 index++; // account for the ':' 1330 if (signature.charAt(index) != ':') { 1331 // we have a class bound 1332 typeParam.append(" extends "); 1333 typeParam.append(typeSignatureToString(signature.substring(index), chopit)); 1334 index += unwrap(CONSUMER_CHARS); // update position 1335 } 1336 // look for interface bounds 1337 while (signature.charAt(index) == ':') { 1338 index++; // skip over the ':' 1339 typeParam.append(" & "); 1340 typeParam.append(typeSignatureToString(signature.substring(index), chopit)); 1341 index += unwrap(CONSUMER_CHARS); // update position 1342 } 1343 wrap(CONSUMER_CHARS, index); 1344 return typeParam.toString(); 1345 } 1346 1347 /** 1348 * Converts a list of type signatures to a string. 1349 * 1350 * @param signature signature to convert 1351 * @param chopit flag that determines whether chopping is executed or not 1352 * @param term character indicating the end of the list 1353 * @return String containg human readable signature 1354 */ 1355 private static String typeSignaturesToString(final String signature, final boolean chopit, final char term) { 1356 // The first character will be an 'open' that matches the 'close' contained in term. 1357 final StringBuilder typeList = new StringBuilder(signature.substring(0, 1)); 1358 int index = 1; // skip the 'open' character 1359 // get the first Type in the list 1360 if (signature.charAt(index) != term) { 1361 typeList.append(typeSignatureToString(signature.substring(index), chopit)); 1362 index += unwrap(CONSUMER_CHARS); // update position 1363 } 1364 // are there more types in the list? 1365 while (signature.charAt(index) != term) { 1366 typeList.append(", "); 1367 typeList.append(typeSignatureToString(signature.substring(index), chopit)); 1368 index += unwrap(CONSUMER_CHARS); // update position 1369 } 1370 wrap(CONSUMER_CHARS, index + 1); // account for the term char 1371 return typeList.append(term).toString(); 1372 } 1373 1374 /** 1375 * 1376 * This method converts a type signature string into a Java type declaration such as 'String[]' and throws a 1377 * 'ClassFormatException' when the parsed type is invalid. 1378 * 1379 * @param signature type signature 1380 * @param chopit flag that determines whether chopping is executed or not 1381 * @return string containing human readable type signature 1382 * @throws ClassFormatException if a class is malformed or cannot be interpreted as a class file 1383 * @since 6.4.0 1384 */ 1385 public static String typeSignatureToString(final String signature, final boolean chopit) throws ClassFormatException { 1386 // corrected concurrent private static field acess 1387 wrap(CONSUMER_CHARS, 1); // This is the default, read just one char like 'B' 1388 try { 1389 switch (signature.charAt(0)) { 1390 case 'B': 1391 return "byte"; 1392 case 'C': 1393 return "char"; 1394 case 'D': 1395 return "double"; 1396 case 'F': 1397 return "float"; 1398 case 'I': 1399 return "int"; 1400 case 'J': 1401 return "long"; 1402 case 'T': { // TypeVariableSignature 1403 final int index = signature.indexOf(';'); // Look for closing ';' 1404 if (index < 0) { 1405 throw new ClassFormatException("Invalid type variable signature: " + signature); 1406 } 1407 // corrected concurrent private static field acess 1408 wrap(CONSUMER_CHARS, index + 1); // "Tblabla;" 'T' and ';' are removed 1409 return compactClassName(signature.substring(1, index), chopit); 1410 } 1411 case 'L': { // Full class name 1412 // should this be a while loop? can there be more than 1413 // one generic clause? (markro) 1414 int fromIndex = signature.indexOf('<'); // generic type? 1415 if (fromIndex < 0) { 1416 fromIndex = 0; 1417 } else { 1418 fromIndex = signature.indexOf('>', fromIndex); 1419 if (fromIndex < 0) { 1420 throw new ClassFormatException("Invalid signature: " + signature); 1421 } 1422 } 1423 final int index = signature.indexOf(';', fromIndex); // Look for closing ';' 1424 if (index < 0) { 1425 throw new ClassFormatException("Invalid signature: " + signature); 1426 } 1427 1428 // check to see if there are any TypeArguments 1429 final int bracketIndex = signature.substring(0, index).indexOf('<'); 1430 if (bracketIndex < 0) { 1431 // just a class identifier 1432 wrap(CONSUMER_CHARS, index + 1); // "Lblabla;" 'L' and ';' are removed 1433 return compactClassName(signature.substring(1, index), chopit); 1434 } 1435 // but make sure we are not looking past the end of the current item 1436 fromIndex = signature.indexOf(';'); 1437 if (fromIndex < 0) { 1438 throw new ClassFormatException("Invalid signature: " + signature); 1439 } 1440 if (fromIndex < bracketIndex) { 1441 // just a class identifier 1442 wrap(CONSUMER_CHARS, fromIndex + 1); // "Lblabla;" 'L' and ';' are removed 1443 return compactClassName(signature.substring(1, fromIndex), chopit); 1444 } 1445 1446 // we have TypeArguments; build up partial result 1447 // as we recurse for each TypeArgument 1448 final StringBuilder type = new StringBuilder(compactClassName(signature.substring(1, bracketIndex), chopit)).append("<"); 1449 int consumedChars = bracketIndex + 1; // Shadows global var 1450 1451 // check for wildcards 1452 if (signature.charAt(consumedChars) == '+') { 1453 type.append("? extends "); 1454 consumedChars++; 1455 } else if (signature.charAt(consumedChars) == '-') { 1456 type.append("? super "); 1457 consumedChars++; 1458 } 1459 1460 // get the first TypeArgument 1461 if (signature.charAt(consumedChars) == '*') { 1462 type.append("?"); 1463 consumedChars++; 1464 } else { 1465 type.append(typeSignatureToString(signature.substring(consumedChars), chopit)); 1466 // update our consumed count by the number of characters the for type argument 1467 consumedChars = unwrap(Utility.CONSUMER_CHARS) + consumedChars; 1468 wrap(Utility.CONSUMER_CHARS, consumedChars); 1469 } 1470 1471 // are there more TypeArguments? 1472 while (signature.charAt(consumedChars) != '>') { 1473 type.append(", "); 1474 // check for wildcards 1475 if (signature.charAt(consumedChars) == '+') { 1476 type.append("? extends "); 1477 consumedChars++; 1478 } else if (signature.charAt(consumedChars) == '-') { 1479 type.append("? super "); 1480 consumedChars++; 1481 } 1482 if (signature.charAt(consumedChars) == '*') { 1483 type.append("?"); 1484 consumedChars++; 1485 } else { 1486 type.append(typeSignatureToString(signature.substring(consumedChars), chopit)); 1487 // update our consumed count by the number of characters the for type argument 1488 consumedChars = unwrap(Utility.CONSUMER_CHARS) + consumedChars; 1489 wrap(Utility.CONSUMER_CHARS, consumedChars); 1490 } 1491 } 1492 1493 // process the closing ">" 1494 consumedChars++; 1495 type.append(">"); 1496 1497 if (signature.charAt(consumedChars) == '.') { 1498 // we have a ClassTypeSignatureSuffix 1499 type.append("."); 1500 // convert SimpleClassTypeSignature to fake ClassTypeSignature 1501 // and then recurse to parse it 1502 type.append(typeSignatureToString("L" + signature.substring(consumedChars + 1), chopit)); 1503 // update our consumed count by the number of characters the for type argument 1504 // note that this count includes the "L" we added, but that is ok 1505 // as it accounts for the "." we didn't consume 1506 consumedChars = unwrap(Utility.CONSUMER_CHARS) + consumedChars; 1507 wrap(Utility.CONSUMER_CHARS, consumedChars); 1508 return type.toString(); 1509 } 1510 if (signature.charAt(consumedChars) != ';') { 1511 throw new ClassFormatException("Invalid signature: " + signature); 1512 } 1513 wrap(Utility.CONSUMER_CHARS, consumedChars + 1); // remove final ";" 1514 return type.toString(); 1515 } 1516 case 'S': 1517 return "short"; 1518 case 'Z': 1519 return "boolean"; 1520 case '[': { // Array declaration 1521 int n; 1522 StringBuilder brackets; 1523 String type; 1524 int consumedChars; // Shadows global var 1525 brackets = new StringBuilder(); // Accumulate []'s 1526 // Count opening brackets and look for optional size argument 1527 for (n = 0; signature.charAt(n) == '['; n++) { 1528 brackets.append("[]"); 1529 } 1530 consumedChars = n; // Remember value 1531 // The rest of the string denotes a '<field_type>' 1532 type = typeSignatureToString(signature.substring(n), chopit); 1533 // corrected concurrent private static field acess 1534 // Utility.consumed_chars += consumed_chars; is replaced by: 1535 final int temp = unwrap(Utility.CONSUMER_CHARS) + consumedChars; 1536 wrap(Utility.CONSUMER_CHARS, temp); 1537 return type + brackets.toString(); 1538 } 1539 case 'V': 1540 return "void"; 1541 default: 1542 throw new ClassFormatException("Invalid signature: '" + signature + "'"); 1543 } 1544 } catch (final StringIndexOutOfBoundsException e) { // Should never occur 1545 throw new ClassFormatException("Invalid signature: " + signature, e); 1546 } 1547 } 1548 1549 private static int unwrap(final ThreadLocal<Integer> tl) { 1550 return tl.get().intValue(); 1551 } 1552 1553 private static void wrap(final ThreadLocal<Integer> tl, final int value) { 1554 tl.set(Integer.valueOf(value)); 1555 } 1556 1557}