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