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