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