View Javadoc
1   /*
2    * reserved comment block
3    * DO NOT REMOVE OR ALTER!
4    */
5   package com.sun.org.apache.bcel.internal.classfile;
6   
7   /* ====================================================================
8    * The Apache Software License, Version 1.1
9    *
10   * Copyright (c) 2001 The Apache Software Foundation.  All rights
11   * reserved.
12   *
13   * Redistribution and use in source and binary forms, with or without
14   * modification, are permitted provided that the following conditions
15   * are met:
16   *
17   * 1. Redistributions of source code must retain the above copyright
18   *    notice, this list of conditions and the following disclaimer.
19   *
20   * 2. Redistributions in binary form must reproduce the above copyright
21   *    notice, this list of conditions and the following disclaimer in
22   *    the documentation and/or other materials provided with the
23   *    distribution.
24   *
25   * 3. The end-user documentation included with the redistribution,
26   *    if any, must include the following acknowledgment:
27   *       "This product includes software developed by the
28   *        Apache Software Foundation (http://www.apache.org/)."
29   *    Alternately, this acknowledgment may appear in the software itself,
30   *    if and wherever such third-party acknowledgments normally appear.
31   *
32   * 4. The names "Apache" and "Apache Software Foundation" and
33   *    "Apache BCEL" must not be used to endorse or promote products
34   *    derived from this software without prior written permission. For
35   *    written permission, please contact apache@apache.org.
36   *
37   * 5. Products derived from this software may not be called "Apache",
38   *    "Apache BCEL", nor may "Apache" appear in their name, without
39   *    prior written permission of the Apache Software Foundation.
40   *
41   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52   * SUCH DAMAGE.
53   * ====================================================================
54   *
55   * This software consists of voluntary contributions made by many
56   * individuals on behalf of the Apache Software Foundation.  For more
57   * information on the Apache Software Foundation, please see
58   * <http://www.apache.org/>.
59   */
60  
61  import com.sun.org.apache.bcel.internal.Constants;
62  import com.sun.org.apache.bcel.internal.util.ByteSequence;
63  import java.io.*;
64  import java.util.ArrayList;
65  import java.util.zip.*;
66  
67  /**
68   * Utility functions that do not really belong to any class in particular.
69   *
70   * @author  <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A>
71   */
72  public abstract class Utility {
73    private static int consumed_chars; /* How many chars have been consumed
74                                        * during parsing in signatureToString().
75                                        * Read by methodSignatureToString().
76                                        * Set by side effect,but only internally.
77                                        */
78    private static boolean wide=false; /* The `WIDE' instruction is used in the
79                                        * byte code to allow 16-bit wide indices
80                                        * for local variables. This opcode
81                                        * precedes an `ILOAD', e.g.. The opcode
82                                        * immediately following takes an extra
83                                        * byte which is combined with the
84                                        * following byte to form a
85                                        * 16-bit value.
86                                        */
87    /**
88     * Convert bit field of flags into string such as `static final'.
89     *
90     * @param  access_flags Access flags
91     * @return String representation of flags
92     */
93    public static final String accessToString(int access_flags) {
94      return accessToString(access_flags, false);
95    }
96  
97    /**
98     * Convert bit field of flags into string such as `static final'.
99     *
100    * Special case: Classes compiled with new compilers and with the
101    * `ACC_SUPER' flag would be said to be "synchronized". This is
102    * because SUN used the same value for the flags `ACC_SUPER' and
103    * `ACC_SYNCHRONIZED'.
104    *
105    * @param  access_flags Access flags
106    * @param  for_class access flags are for class qualifiers ?
107    * @return String representation of flags
108    */
109   public static final String accessToString(int access_flags,
110                                             boolean for_class)
111   {
112     StringBuffer buf = new StringBuffer();
113 
114     int p = 0;
115     for(int i=0; p < Constants.MAX_ACC_FLAG; i++) { // Loop through known flags
116       p = pow2(i);
117 
118       if((access_flags & p) != 0) {
119         /* Special case: Classes compiled with new compilers and with the
120          * `ACC_SUPER' flag would be said to be "synchronized". This is
121          * because SUN used the same value for the flags `ACC_SUPER' and
122          * `ACC_SYNCHRONIZED'.
123          */
124         if(for_class && ((p == Constants.ACC_SUPER) || (p == Constants.ACC_INTERFACE)))
125           continue;
126 
127         buf.append(Constants.ACCESS_NAMES[i] + " ");
128       }
129     }
130 
131     return buf.toString().trim();
132   }
133 
134   /**
135    * @return "class" or "interface", depending on the ACC_INTERFACE flag
136    */
137   public static final String classOrInterface(int access_flags) {
138     return ((access_flags & Constants.ACC_INTERFACE) != 0)? "interface" : "class";
139   }
140 
141   /**
142    * Disassemble a byte array of JVM byte codes starting from code line
143    * `index' and return the disassembled string representation. Decode only
144    * `num' opcodes (including their operands), use -1 if you want to
145    * decompile everything.
146    *
147    * @param  code byte code array
148    * @param  constant_pool Array of constants
149    * @param  index offset in `code' array
150    * <EM>(number of opcodes, not bytes!)</EM>
151    * @param  length number of opcodes to decompile, -1 for all
152    * @param  verbose be verbose, e.g. print constant pool index
153    * @return String representation of byte codes
154    */
155   public static final String codeToString(byte[] code,
156                                           ConstantPool constant_pool,
157                                           int index, int length, boolean verbose)
158   {
159     StringBuffer buf    = new StringBuffer(code.length * 20); // Should be sufficient
160     ByteSequence stream = new ByteSequence(code);
161 
162     try {
163       for(int i=0; i < index; i++) // Skip `index' lines of code
164         codeToString(stream, constant_pool, verbose);
165 
166       for(int i=0; stream.available() > 0; i++) {
167         if((length < 0) || (i < length)) {
168           String indices = fillup(stream.getIndex() + ":", 6, true, ' ');
169           buf.append(indices + codeToString(stream, constant_pool, verbose) + '\n');
170         }
171       }
172     } catch(IOException e) {
173       System.out.println(buf.toString());
174       e.printStackTrace();
175       throw new ClassFormatException("Byte code error: " + e);
176     }
177 
178     return buf.toString();
179   }
180 
181   public static final String codeToString(byte[] code,
182                                           ConstantPool constant_pool,
183                                           int index, int length) {
184     return codeToString(code, constant_pool, index, length, true);
185   }
186 
187   /**
188    * Disassemble a stream of byte codes and return the
189    * string representation.
190    *
191    * @param  bytes stream of bytes
192    * @param  constant_pool Array of constants
193    * @param  verbose be verbose, e.g. print constant pool index
194    * @return String representation of byte code
195    */
196   public static final String codeToString(ByteSequence bytes,
197                                           ConstantPool constant_pool, boolean verbose)
198        throws IOException
199   {
200     short        opcode = (short)bytes.readUnsignedByte();
201     int          default_offset=0, low, high, npairs;
202     int          index, vindex, constant;
203     int[]        match, jump_table;
204     int          no_pad_bytes=0, offset;
205     StringBuffer buf = new StringBuffer(Constants.OPCODE_NAMES[opcode]);
206 
207     /* Special case: Skip (0-3) padding bytes, i.e., the
208      * following bytes are 4-byte-aligned
209      */
210     if((opcode == Constants.TABLESWITCH) || (opcode == Constants.LOOKUPSWITCH)) {
211       int remainder = bytes.getIndex() % 4;
212       no_pad_bytes  = (remainder == 0)? 0 : 4 - remainder;
213 
214       for(int i=0; i < no_pad_bytes; i++) {
215         byte b;
216 
217         if((b=bytes.readByte()) != 0)
218           System.err.println("Warning: Padding byte != 0 in " +
219                              Constants.OPCODE_NAMES[opcode] + ":" + b);
220       }
221 
222       // Both cases have a field default_offset in common
223       default_offset = bytes.readInt();
224     }
225 
226     switch(opcode) {
227       /* Table switch has variable length arguments.
228        */
229     case Constants.TABLESWITCH:
230       low  = bytes.readInt();
231       high = bytes.readInt();
232 
233       offset = bytes.getIndex() - 12 - no_pad_bytes - 1;
234       default_offset += offset;
235 
236       buf.append("\tdefault = " + default_offset + ", low = " + low +
237                  ", high = " + high + "(");
238 
239       jump_table = new int[high - low + 1];
240       for(int i=0; i < jump_table.length; i++) {
241         jump_table[i] = offset + bytes.readInt();
242         buf.append(jump_table[i]);
243 
244         if(i < jump_table.length - 1)
245           buf.append(", ");
246       }
247       buf.append(")");
248 
249       break;
250 
251       /* Lookup switch has variable length arguments.
252        */
253     case Constants.LOOKUPSWITCH: {
254 
255       npairs = bytes.readInt();
256       offset = bytes.getIndex() - 8 - no_pad_bytes - 1;
257 
258       match      = new int[npairs];
259       jump_table = new int[npairs];
260       default_offset += offset;
261 
262       buf.append("\tdefault = " + default_offset + ", npairs = " + npairs +
263                  " (");
264 
265       for(int i=0; i < npairs; i++) {
266         match[i]      = bytes.readInt();
267 
268         jump_table[i] = offset + bytes.readInt();
269 
270         buf.append("(" + match[i] + ", " + jump_table[i] + ")");
271 
272         if(i < npairs - 1)
273           buf.append(", ");
274       }
275       buf.append(")");
276     }
277     break;
278 
279     /* Two address bytes + offset from start of byte stream form the
280      * jump target
281      */
282     case Constants.GOTO:      case Constants.IFEQ:      case Constants.IFGE:      case Constants.IFGT:
283     case Constants.IFLE:      case Constants.IFLT:      case Constants.JSR: case Constants.IFNE:
284     case Constants.IFNONNULL: case Constants.IFNULL:    case Constants.IF_ACMPEQ:
285     case Constants.IF_ACMPNE: case Constants.IF_ICMPEQ: case Constants.IF_ICMPGE: case Constants.IF_ICMPGT:
286     case Constants.IF_ICMPLE: case Constants.IF_ICMPLT: case Constants.IF_ICMPNE:
287       buf.append("\t\t#" + ((bytes.getIndex() - 1) + bytes.readShort()));
288       break;
289 
290       /* 32-bit wide jumps
291        */
292     case Constants.GOTO_W: case Constants.JSR_W:
293       buf.append("\t\t#" + ((bytes.getIndex() - 1) + bytes.readInt()));
294       break;
295 
296       /* Index byte references local variable (register)
297        */
298     case Constants.ALOAD:  case Constants.ASTORE: case Constants.DLOAD:  case Constants.DSTORE: case Constants.FLOAD:
299     case Constants.FSTORE: case Constants.ILOAD:  case Constants.ISTORE: case Constants.LLOAD:  case Constants.LSTORE:
300     case Constants.RET:
301       if(wide) {
302         vindex = bytes.readUnsignedShort();
303         wide=false; // Clear flag
304       }
305       else
306         vindex = bytes.readUnsignedByte();
307 
308       buf.append("\t\t%" + vindex);
309       break;
310 
311       /*
312        * Remember wide byte which is used to form a 16-bit address in the
313        * following instruction. Relies on that the method is called again with
314        * the following opcode.
315        */
316     case Constants.WIDE:
317       wide      = true;
318       buf.append("\t(wide)");
319       break;
320 
321       /* Array of basic type.
322        */
323     case Constants.NEWARRAY:
324       buf.append("\t\t<" + Constants.TYPE_NAMES[bytes.readByte()] + ">");
325       break;
326 
327       /* Access object/class fields.
328        */
329     case Constants.GETFIELD: case Constants.GETSTATIC: case Constants.PUTFIELD: case Constants.PUTSTATIC:
330       index = bytes.readUnsignedShort();
331       buf.append("\t\t" +
332                  constant_pool.constantToString(index, Constants.CONSTANT_Fieldref) +
333                  (verbose? " (" + index + ")" : ""));
334       break;
335 
336       /* Operands are references to classes in constant pool
337        */
338     case Constants.NEW:
339     case Constants.CHECKCAST:
340       buf.append("\t");
341     case Constants.INSTANCEOF:
342       index = bytes.readUnsignedShort();
343       buf.append("\t<" + constant_pool.constantToString(index,
344                                                         Constants.CONSTANT_Class) +
345                  ">" + (verbose? " (" + index + ")" : ""));
346       break;
347 
348       /* Operands are references to methods in constant pool
349        */
350     case Constants.INVOKESPECIAL: case Constants.INVOKESTATIC: case Constants.INVOKEVIRTUAL:
351       index = bytes.readUnsignedShort();
352       buf.append("\t" + constant_pool.constantToString(index,
353                                                        Constants.CONSTANT_Methodref) +
354                  (verbose? " (" + index + ")" : ""));
355       break;
356 
357     case Constants.INVOKEINTERFACE:
358       index = bytes.readUnsignedShort();
359       int nargs = bytes.readUnsignedByte(); // historical, redundant
360       buf.append("\t" +
361                  constant_pool.constantToString(index,
362                                                 Constants.CONSTANT_InterfaceMethodref) +
363                  (verbose? " (" + index + ")\t" : "") + nargs + "\t" +
364                  bytes.readUnsignedByte()); // Last byte is a reserved space
365       break;
366 
367       /* Operands are references to items in constant pool
368        */
369     case Constants.LDC_W: case Constants.LDC2_W:
370       index = bytes.readUnsignedShort();
371 
372       buf.append("\t\t" + constant_pool.constantToString
373                  (index, constant_pool.getConstant(index).getTag()) +
374                  (verbose? " (" + index + ")" : ""));
375       break;
376 
377     case Constants.LDC:
378       index = bytes.readUnsignedByte();
379 
380       buf.append("\t\t" +
381                  constant_pool.constantToString
382                  (index, constant_pool.getConstant(index).getTag()) +
383                  (verbose? " (" + index + ")" : ""));
384       break;
385 
386       /* Array of references.
387        */
388     case Constants.ANEWARRAY:
389       index = bytes.readUnsignedShort();
390 
391       buf.append("\t\t<" + compactClassName(constant_pool.getConstantString
392                                           (index, Constants.CONSTANT_Class), false) +
393                  ">" + (verbose? " (" + index + ")": ""));
394       break;
395 
396       /* Multidimensional array of references.
397        */
398     case Constants.MULTIANEWARRAY: {
399       index          = bytes.readUnsignedShort();
400       int dimensions = bytes.readUnsignedByte();
401 
402       buf.append("\t<" + compactClassName(constant_pool.getConstantString
403                                           (index, Constants.CONSTANT_Class), false) +
404                  ">\t" + dimensions + (verbose? " (" + index + ")" : ""));
405     }
406     break;
407 
408     /* Increment local variable.
409      */
410     case Constants.IINC:
411       if(wide) {
412         vindex   = bytes.readUnsignedShort();
413         constant = bytes.readShort();
414         wide     = false;
415       }
416       else {
417         vindex   = bytes.readUnsignedByte();
418         constant = bytes.readByte();
419       }
420       buf.append("\t\t%" + vindex + "\t" + constant);
421       break;
422 
423     default:
424       if(Constants.NO_OF_OPERANDS[opcode] > 0) {
425         for(int i=0; i < Constants.TYPE_OF_OPERANDS[opcode].length; i++) {
426           buf.append("\t\t");
427           switch(Constants.TYPE_OF_OPERANDS[opcode][i]) {
428           case Constants.T_BYTE:  buf.append(bytes.readByte()); break;
429           case Constants.T_SHORT: buf.append(bytes.readShort());       break;
430           case Constants.T_INT:   buf.append(bytes.readInt());         break;
431 
432           default: // Never reached
433             System.err.println("Unreachable default case reached!");
434             buf.setLength(0);
435           }
436         }
437       }
438     }
439 
440     return buf.toString();
441   }
442 
443   public static final String codeToString(ByteSequence bytes, ConstantPool constant_pool)
444     throws IOException
445   {
446     return codeToString(bytes, constant_pool, true);
447   }
448 
449   /**
450    * Shorten long class names, <em>java/lang/String</em> becomes
451    * <em>String</em>.
452    *
453    * @param str The long class name
454    * @return Compacted class name
455    */
456   public static final String compactClassName(String str) {
457     return compactClassName(str, true);
458   }
459 
460   /**
461    * Shorten long class name <em>str</em>, i.e., chop off the <em>prefix</em>,
462    * if the
463    * class name starts with this string and the flag <em>chopit</em> is true.
464    * Slashes <em>/</em> are converted to dots <em>.</em>.
465    *
466    * @param str The long class name
467    * @param prefix The prefix the get rid off
468    * @param chopit Flag that determines whether chopping is executed or not
469    * @return Compacted class name
470    */
471   public static final String compactClassName(String str,
472                                               String prefix,
473                                               boolean chopit)
474   {
475     int len = prefix.length();
476 
477     str = str.replace('/', '.'); // Is `/' on all systems, even DOS
478 
479     if(chopit) {
480       // If string starts with `prefix' and contains no further dots
481       if(str.startsWith(prefix) &&
482          (str.substring(len).indexOf('.') == -1))
483         str = str.substring(len);
484     }
485 
486     return str;
487   }
488 
489   /**
490    * Shorten long class names, <em>java/lang/String</em> becomes
491    * <em>java.lang.String</em>,
492    * e.g.. If <em>chopit</em> is <em>true</em> the prefix <em>java.lang</em>
493    * is also removed.
494    *
495    * @param str The long class name
496    * @param chopit Flag that determines whether chopping is executed or not
497    * @return Compacted class name
498    */
499   public static final String compactClassName(String str, boolean chopit) {
500     return compactClassName(str, "java.lang.", chopit);
501   }
502 
503   private static final boolean is_digit(char ch) {
504     return (ch >= '0') && (ch <= '9');
505   }
506 
507   private static final boolean is_space(char ch) {
508     return (ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n');
509   }
510 
511   /**
512    * @return `flag' with bit `i' set to 1
513    */
514   public static final int setBit(int flag, int i) {
515     return flag | pow2(i);
516   }
517 
518   /**
519    * @return `flag' with bit `i' set to 0
520    */
521   public static final int clearBit(int flag, int i) {
522     int bit = pow2(i);
523     return (flag & bit) == 0? flag : flag ^ bit;
524   }
525 
526   /**
527    * @return true, if bit `i' in `flag' is set
528    */
529   public static final boolean isSet(int flag, int i) {
530     return (flag & pow2(i)) != 0;
531   }
532 
533   /**
534    * Converts string containing the method return and argument types
535    * to a byte code method signature.
536    *
537    * @param  ret Return type of method
538    * @param  argv Types of method arguments
539    * @return Byte code representation of method signature
540    */
541   public final static String methodTypeToSignature(String ret, String[] argv)
542     throws ClassFormatException
543   {
544     StringBuffer buf = new StringBuffer("(");
545     String       str;
546 
547     if(argv != null)
548       for(int i=0; i < argv.length; i++) {
549         str = getSignature(argv[i]);
550 
551         if(str.endsWith("V")) // void can't be a method argument
552           throw new ClassFormatException("Invalid type: " + argv[i]);
553 
554         buf.append(str);
555       }
556 
557     str = getSignature(ret);
558 
559     buf.append(")" + str);
560 
561     return buf.toString();
562   }
563 
564   /**
565    * @param  signature    Method signature
566    * @return Array of argument types
567    * @throws  ClassFormatException
568    */
569   public static final String[] methodSignatureArgumentTypes(String signature)
570     throws ClassFormatException
571   {
572     return methodSignatureArgumentTypes(signature, true);
573   }
574 
575   /**
576    * @param  signature    Method signature
577    * @param chopit Shorten class names ?
578    * @return Array of argument types
579    * @throws  ClassFormatException
580    */
581   public static final String[] methodSignatureArgumentTypes(String signature,
582                                                             boolean chopit)
583     throws ClassFormatException
584   {
585     ArrayList vec = new ArrayList();
586     int       index;
587     String[]  types;
588 
589     try { // Read all declarations between for `(' and `)'
590       if(signature.charAt(0) != '(')
591         throw new ClassFormatException("Invalid method signature: " + signature);
592 
593       index = 1; // current string position
594 
595       while(signature.charAt(index) != ')') {
596         vec.add(signatureToString(signature.substring(index), chopit));
597         index += consumed_chars; // update position
598       }
599     } catch(StringIndexOutOfBoundsException e) { // Should never occur
600       throw new ClassFormatException("Invalid method signature: " + signature);
601     }
602 
603     types = new String[vec.size()];
604     vec.toArray(types);
605     return types;
606   }
607   /**
608    * @param  signature    Method signature
609    * @return return type of method
610    * @throws  ClassFormatException
611    */
612   public static final String methodSignatureReturnType(String signature)
613        throws ClassFormatException
614   {
615     return methodSignatureReturnType(signature, true);
616   }
617   /**
618    * @param  signature    Method signature
619    * @param chopit Shorten class names ?
620    * @return return type of method
621    * @throws  ClassFormatException
622    */
623   public static final String methodSignatureReturnType(String signature,
624                                                        boolean chopit)
625        throws ClassFormatException
626   {
627     int    index;
628     String type;
629 
630     try {
631       // Read return type after `)'
632       index = signature.lastIndexOf(')') + 1;
633       type = signatureToString(signature.substring(index), chopit);
634     } catch(StringIndexOutOfBoundsException e) { // Should never occur
635       throw new ClassFormatException("Invalid method signature: " + signature);
636     }
637 
638     return type;
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 final String methodSignatureToString(String signature,
650                                                      String name,
651                                                      String access) {
652     return methodSignatureToString(signature, name, access, true);
653   }
654 
655   public static final String methodSignatureToString(String signature,
656                                                      String name,
657                                                      String access,
658                                                      boolean chopit) {
659     return methodSignatureToString(signature, name, access, chopit, null);
660   }
661 
662   /**
663    * A return type signature represents the return value from a method.
664    * It is a series of bytes in the following grammar:
665    *
666    * <return_signature> ::= <field_type> | V
667    *
668    * The character V indicates that the method returns no value. Otherwise, the
669    * signature indicates the type of the return value.
670    * An argument signature represents an argument passed to a method:
671    *
672    * <argument_signature> ::= <field_type>
673    *
674    * A method signature represents the arguments that the method expects, and
675    * the value that it returns.
676    * <method_signature> ::= (<arguments_signature>) <return_signature>
677    * <arguments_signature>::= <argument_signature>*
678    *
679    * This method converts such a string into a Java type declaration like
680    * `void _main(String[])' and throws a `ClassFormatException' when the parsed
681    * type is invalid.
682    *
683    * @param  signature    Method signature
684    * @param  name         Method name
685    * @param  access       Method access rights
686    * @return Java type declaration
687    * @throws  ClassFormatException
688    */
689   public static final String methodSignatureToString(String signature,
690                                                      String name,
691                                                      String access,
692                                                      boolean chopit,
693                                                      LocalVariableTable vars)
694     throws ClassFormatException
695   {
696     StringBuffer buf = new StringBuffer("(");
697     String       type;
698     int          index;
699     int          var_index = (access.indexOf("static") >= 0)? 0 : 1;
700 
701     try { // Read all declarations between for `(' and `)'
702       if(signature.charAt(0) != '(')
703         throw new ClassFormatException("Invalid method signature: " + signature);
704 
705       index = 1; // current string position
706 
707       while(signature.charAt(index) != ')') {
708         String param_type = signatureToString(signature.substring(index), chopit);
709         buf.append(param_type);
710 
711         if(vars != null) {
712           LocalVariable l = vars.getLocalVariable(var_index);
713 
714           if(l != null)
715             buf.append(" " + l.getName());
716         } else
717           buf.append(" arg" + var_index);
718 
719         if("double".equals(param_type) || "long".equals(param_type))
720           var_index += 2;
721         else
722           var_index++;
723 
724         buf.append(", ");
725         index += consumed_chars; // update position
726       }
727 
728       index++; // update position
729 
730       // Read return type after `)'
731       type = signatureToString(signature.substring(index), chopit);
732 
733     } catch(StringIndexOutOfBoundsException e) { // Should never occur
734       throw new ClassFormatException("Invalid method signature: " + signature);
735     }
736 
737     if(buf.length() > 1) // Tack off the extra ", "
738       buf.setLength(buf.length() - 2);
739 
740     buf.append(")");
741 
742     return access + ((access.length() > 0)? " " : "") + // May be an empty string
743       type + " " + name + buf.toString();
744   }
745 
746   // Guess what this does
747   private static final int pow2(int n) {
748     return 1 << n;
749   }
750 
751   /**
752    * Replace all occurences of <em>old</em> in <em>str</em> with <em>new</em>.
753    *
754    * @param str String to permute
755    * @param old String to be replaced
756    * @param new Replacement string
757    * @return new String object
758    */
759   public static final String replace(String str, String old, String new_) {
760     int          index, old_index;
761     StringBuffer buf = new StringBuffer();
762 
763     try {
764       if((index = str.indexOf(old)) != -1) { // `old' found in str
765         old_index = 0;                       // String start offset
766 
767         // While we have something to replace
768         while((index = str.indexOf(old, old_index)) != -1) {
769           buf.append(str.substring(old_index, index)); // append prefix
770           buf.append(new_);                            // append replacement
771 
772           old_index = index + old.length(); // Skip `old'.length chars
773         }
774 
775         buf.append(str.substring(old_index)); // append rest of string
776         str = buf.toString();
777       }
778     } catch(StringIndexOutOfBoundsException e) { // Should not occur
779       System.err.println(e);
780     }
781 
782     return str;
783   }
784 
785   /**
786    * Converts signature to string with all class names compacted.
787    *
788    * @param signature to convert
789    * @return Human readable signature
790    */
791   public static final String signatureToString(String signature) {
792     return signatureToString(signature, true);
793   }
794 
795   /**
796    * The field signature represents the value of an argument to a function or
797    * the value of a variable. It is a series of bytes generated by the
798    * following grammar:
799    *
800    * <PRE>
801    * <field_signature> ::= <field_type>
802    * <field_type>      ::= <base_type>|<object_type>|<array_type>
803    * <base_type>       ::= B|C|D|F|I|J|S|Z
804    * <object_type>     ::= L<fullclassname>;
805    * <array_type>      ::= [<field_type>
806    *
807    * The meaning of the base types is as follows:
808    * B byte signed byte
809    * C char character
810    * D double double precision IEEE float
811    * F float single precision IEEE float
812    * I int integer
813    * J long long integer
814    * L<fullclassname>; ... an object of the given class
815    * S short signed short
816    * Z boolean true or false
817    * [<field sig> ... array
818    * </PRE>
819    *
820    * This method converts this string into a Java type declaration such as
821    * `String[]' and throws a `ClassFormatException' when the parsed type is
822    * invalid.
823    *
824    * @param  signature  Class signature
825    * @param chopit Flag that determines whether chopping is executed or not
826    * @return Java type declaration
827    * @throws ClassFormatException
828    */
829   public static final String signatureToString(String signature,
830                                                boolean chopit)
831   {
832     consumed_chars = 1; // This is the default, read just one char like `B'
833 
834     try {
835       switch(signature.charAt(0)) {
836       case 'B' : return "byte";
837       case 'C' : return "char";
838       case 'D' : return "double";
839       case 'F' : return "float";
840       case 'I' : return "int";
841       case 'J' : return "long";
842 
843       case 'L' : { // Full class name
844         int    index = signature.indexOf(';'); // Look for closing `;'
845 
846         if(index < 0)
847           throw new ClassFormatException("Invalid signature: " + signature);
848 
849         consumed_chars = index + 1; // "Lblabla;" `L' and `;' are removed
850 
851         return compactClassName(signature.substring(1, index), chopit);
852       }
853 
854       case 'S' : return "short";
855       case 'Z' : return "boolean";
856 
857       case '[' : { // Array declaration
858         int          n;
859         StringBuffer buf, brackets;
860         String       type;
861         char         ch;
862         int          consumed_chars; // Shadows global var
863 
864         brackets = new StringBuffer(); // Accumulate []'s
865 
866         // Count opening brackets and look for optional size argument
867         for(n=0; signature.charAt(n) == '['; n++)
868           brackets.append("[]");
869 
870         consumed_chars = n; // Remember value
871 
872         // The rest of the string denotes a `<field_type>'
873         type = signatureToString(signature.substring(n), chopit);
874 
875         Utility.consumed_chars += consumed_chars;
876         return type + brackets.toString();
877       }
878 
879       case 'V' : return "void";
880 
881       default  : throw new ClassFormatException("Invalid signature: `" +
882                                             signature + "'");
883       }
884     } catch(StringIndexOutOfBoundsException e) { // Should never occur
885       throw new ClassFormatException("Invalid signature: " + e + ":" + signature);
886     }
887   }
888 
889   /** Parse Java type such as "char", or "java.lang.String[]" and return the
890    * signature in byte code format, e.g. "C" or "[Ljava/lang/String;" respectively.
891    *
892    * @param  type Java type
893    * @return byte code signature
894    */
895   public static String getSignature(String type) {
896     StringBuffer buf        = new StringBuffer();
897     char[]       chars      = type.toCharArray();
898     boolean      char_found = false, delim = false;
899     int          index      = -1;
900 
901   loop:
902     for(int i=0; i < chars.length; i++) {
903       switch(chars[i]) {
904       case ' ': case '\t': case '\n': case '\r': case '\f':
905         if(char_found)
906           delim = true;
907         break;
908 
909       case '[':
910         if(!char_found)
911           throw new RuntimeException("Illegal type: " + type);
912 
913         index = i;
914         break loop;
915 
916       default:
917         char_found = true;
918         if(!delim)
919           buf.append(chars[i]);
920       }
921     }
922 
923     int brackets = 0;
924 
925     if(index > 0)
926       brackets = countBrackets(type.substring(index));
927 
928     type = buf.toString();
929     buf.setLength(0);
930 
931     for(int i=0; i < brackets; i++)
932       buf.append('[');
933 
934     boolean found = false;
935 
936     for(int i=Constants.T_BOOLEAN; (i <= Constants.T_VOID) && !found; i++) {
937       if(Constants.TYPE_NAMES[i].equals(type)) {
938         found = true;
939         buf.append(Constants.SHORT_TYPE_NAMES[i]);
940       }
941     }
942 
943     if(!found) // Class name
944       buf.append('L' + type.replace('.', '/') + ';');
945 
946     return buf.toString();
947   }
948 
949   private static int countBrackets(String brackets) {
950     char[]  chars = brackets.toCharArray();
951     int     count = 0;
952     boolean open  = false;
953 
954     for(int i=0; i<chars.length; i++) {
955       switch(chars[i]) {
956       case '[':
957         if(open)
958           throw new RuntimeException("Illegally nested brackets:" + brackets);
959         open = true;
960         break;
961 
962       case ']':
963         if(!open)
964           throw new RuntimeException("Illegally nested brackets:" + brackets);
965         open = false;
966         count++;
967         break;
968 
969       default:
970         // Don't care
971       }
972     }
973 
974     if(open)
975       throw new RuntimeException("Illegally nested brackets:" + brackets);
976 
977     return count;
978   }
979 
980   /**
981    * Return type of method signature as a byte value as defined in <em>Constants</em>
982    *
983    * @param  signature in format described above
984    * @return type of method signature
985    * @see    Constants
986    */
987   public static final byte typeOfMethodSignature(String signature)
988     throws ClassFormatException
989   {
990     int index;
991 
992     try {
993       if(signature.charAt(0) != '(')
994         throw new ClassFormatException("Invalid method signature: " + signature);
995 
996       index = signature.lastIndexOf(')') + 1;
997       return typeOfSignature(signature.substring(index));
998     } catch(StringIndexOutOfBoundsException e) {
999       throw new ClassFormatException("Invalid method signature: " + signature);
1000     }
1001   }
1002 
1003   /**
1004    * Return type of signature as a byte value as defined in <em>Constants</em>
1005    *
1006    * @param  signature in format described above
1007    * @return type of signature
1008    * @see    Constants
1009    */
1010   public static final byte typeOfSignature(String signature)
1011     throws ClassFormatException
1012   {
1013     try {
1014       switch(signature.charAt(0)) {
1015       case 'B' : return Constants.T_BYTE;
1016       case 'C' : return Constants.T_CHAR;
1017       case 'D' : return Constants.T_DOUBLE;
1018       case 'F' : return Constants.T_FLOAT;
1019       case 'I' : return Constants.T_INT;
1020       case 'J' : return Constants.T_LONG;
1021       case 'L' : return Constants.T_REFERENCE;
1022       case '[' : return Constants.T_ARRAY;
1023       case 'V' : return Constants.T_VOID;
1024       case 'Z' : return Constants.T_BOOLEAN;
1025       case 'S' : return Constants.T_SHORT;
1026       default:
1027         throw new ClassFormatException("Invalid method signature: " + signature);
1028       }
1029     } catch(StringIndexOutOfBoundsException e) {
1030       throw new ClassFormatException("Invalid method signature: " + signature);
1031     }
1032   }
1033 
1034   /** Map opcode names to opcode numbers. E.g., return Constants.ALOAD for "aload"
1035    */
1036   public static short searchOpcode(String name) {
1037     name = name.toLowerCase();
1038 
1039     for(short i=0; i < Constants.OPCODE_NAMES.length; i++)
1040       if(Constants.OPCODE_NAMES[i].equals(name))
1041         return i;
1042 
1043     return -1;
1044   }
1045 
1046   /**
1047    * Convert (signed) byte to (unsigned) short value, i.e., all negative
1048    * values become positive.
1049    */
1050   private static final short byteToShort(byte b) {
1051     return (b < 0)? (short)(256 + b) : (short)b;
1052   }
1053 
1054   /** Convert bytes into hexidecimal string
1055    *
1056    * @return bytes as hexidecimal string, e.g. 00 FA 12 ...
1057    */
1058   public static final String toHexString(byte[] bytes) {
1059     StringBuffer buf = new StringBuffer();
1060 
1061     for(int i=0; i < bytes.length; i++) {
1062       short  b   = byteToShort(bytes[i]);
1063       String hex = Integer.toString(b, 0x10);
1064 
1065       if(b < 0x10) // just one digit, prepend '0'
1066         buf.append('0');
1067 
1068       buf.append(hex);
1069 
1070       if(i < bytes.length - 1)
1071         buf.append(' ');
1072     }
1073 
1074     return buf.toString();
1075   }
1076 
1077   /**
1078    * Return a string for an integer justified left or right and filled up with
1079    * `fill' characters if necessary.
1080    *
1081    * @param i integer to format
1082    * @param length length of desired string
1083    * @param left_justify format left or right
1084    * @param fill fill character
1085    * @return formatted int
1086    */
1087   public static final String format(int i, int length, boolean left_justify, char fill) {
1088     return fillup(Integer.toString(i), length, left_justify, fill);
1089   }
1090 
1091   /**
1092    * Fillup char with up to length characters with char `fill' and justify it left or right.
1093    *
1094    * @param str string to format
1095    * @param length length of desired string
1096    * @param left_justify format left or right
1097    * @param fill fill character
1098    * @return formatted string
1099    */
1100   public static final String fillup(String str, int length, boolean left_justify, char fill) {
1101     int    len = length - str.length();
1102     char[] buf = new char[(len < 0)? 0 : len];
1103 
1104     for(int j=0; j < buf.length; j++)
1105       buf[j] = fill;
1106 
1107     if(left_justify)
1108       return str + new String(buf);
1109     else
1110       return new String(buf) + str;
1111   }
1112 
1113   static final boolean equals(byte[] a, byte[] b) {
1114     int size;
1115 
1116     if((size=a.length) != b.length)
1117       return false;
1118 
1119     for(int i=0; i < size; i++)
1120       if(a[i] != b[i])
1121         return false;
1122 
1123     return true;
1124   }
1125 
1126   public static final void printArray(PrintStream out, Object[] obj) {
1127     out.println(printArray(obj, true));
1128   }
1129 
1130   public static final void printArray(PrintWriter out, Object[] obj) {
1131     out.println(printArray(obj, true));
1132   }
1133 
1134   public static final String printArray(Object[] obj) {
1135     return printArray(obj, true);
1136   }
1137 
1138   public static final String printArray(Object[] obj, boolean braces) {
1139     return printArray(obj, braces, false);
1140   }
1141 
1142   public static final String printArray(Object[] obj, boolean braces,
1143                                         boolean quote) {
1144     if(obj == null)
1145       return null;
1146 
1147     StringBuffer buf = new StringBuffer();
1148     if(braces)
1149       buf.append('{');
1150 
1151     for(int i=0; i < obj.length; i++) {
1152       if(obj[i] != null) {
1153         buf.append((quote? "\"" : "") + obj[i].toString() + (quote? "\"" : ""));
1154       } else {
1155         buf.append("null");
1156       }
1157 
1158       if(i < obj.length - 1) {
1159         buf.append(", ");
1160       }
1161     }
1162 
1163     if(braces)
1164       buf.append('}');
1165 
1166     return buf.toString();
1167   }
1168 
1169   /** @return true, if character is one of (a, ... z, A, ... Z, 0, ... 9, _)
1170    */
1171   public static boolean isJavaIdentifierPart(char ch) {
1172     return ((ch >= 'a') && (ch <= 'z')) ||
1173       ((ch >= 'A') && (ch <= 'Z')) ||
1174       ((ch >= '0') && (ch <= '9')) ||
1175       (ch == '_');
1176   }
1177 
1178   /** Encode byte array it into Java identifier string, i.e., a string
1179    * that only contains the following characters: (a, ... z, A, ... Z,
1180    * 0, ... 9, _, $).  The encoding algorithm itself is not too
1181    * clever: if the current byte's ASCII value already is a valid Java
1182    * identifier part, leave it as it is. Otherwise it writes the
1183    * escape character($) followed by <p><ul><li> the ASCII value as a
1184    * hexadecimal string, if the value is not in the range
1185    * 200..247</li> <li>a Java identifier char not used in a lowercase
1186    * hexadecimal string, if the value is in the range
1187    * 200..247</li><ul></p>
1188    *
1189    * <p>This operation inflates the original byte array by roughly 40-50%</p>
1190    *
1191    * @param bytes the byte array to convert
1192    * @param compress use gzip to minimize string
1193    */
1194   public static String encode(byte[] bytes, boolean compress) throws IOException {
1195     if(compress) {
1196       ByteArrayOutputStream baos = new ByteArrayOutputStream();
1197       GZIPOutputStream      gos  = new GZIPOutputStream(baos);
1198 
1199       gos.write(bytes, 0, bytes.length);
1200       gos.close();
1201       baos.close();
1202 
1203       bytes = baos.toByteArray();
1204     }
1205 
1206     CharArrayWriter caw = new CharArrayWriter();
1207     JavaWriter      jw  = new JavaWriter(caw);
1208 
1209     for(int i=0; i < bytes.length; i++) {
1210       int in = bytes[i] & 0x000000ff; // Normalize to unsigned
1211       jw.write(in);
1212     }
1213 
1214     return caw.toString();
1215   }
1216 
1217   /** Decode a string back to a byte array.
1218    *
1219    * @param bytes the byte array to convert
1220    * @param uncompress use gzip to uncompress the stream of bytes
1221    */
1222   public static byte[] decode(String s, boolean uncompress) throws IOException {
1223     char[] chars = s.toCharArray();
1224 
1225     CharArrayReader car = new CharArrayReader(chars);
1226     JavaReader      jr  = new JavaReader(car);
1227 
1228     ByteArrayOutputStream bos = new ByteArrayOutputStream();
1229 
1230     int ch;
1231 
1232     while((ch = jr.read()) >= 0) {
1233       bos.write(ch);
1234     }
1235 
1236     bos.close();
1237     car.close();
1238     jr.close();
1239 
1240     byte[] bytes = bos.toByteArray();
1241 
1242     if(uncompress) {
1243       GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(bytes));
1244 
1245       byte[] tmp   = new byte[bytes.length * 3]; // Rough estimate
1246       int    count = 0;
1247       int    b;
1248 
1249       while((b = gis.read()) >= 0)
1250         tmp[count++] = (byte)b;
1251 
1252       bytes = new byte[count];
1253       System.arraycopy(tmp, 0, bytes, 0, count);
1254     }
1255 
1256     return bytes;
1257   }
1258 
1259   // A-Z, g-z, _, $
1260   private static final int   FREE_CHARS  = 48;
1261   private static       int[] CHAR_MAP    = new int[FREE_CHARS];
1262   private static       int[] MAP_CHAR    = new int[256]; // Reverse map
1263   private static final char  ESCAPE_CHAR = '$';
1264 
1265   static {
1266     int j = 0, k = 0;
1267     for(int i='A'; i <= 'Z'; i++) {
1268       CHAR_MAP[j] = i;
1269       MAP_CHAR[i] = j;
1270       j++;
1271     }
1272 
1273     for(int i='g'; i <= 'z'; i++) {
1274       CHAR_MAP[j] = i;
1275       MAP_CHAR[i] = j;
1276       j++;
1277     }
1278 
1279     CHAR_MAP[j]   = '$';
1280     MAP_CHAR['$'] = j;
1281     j++;
1282 
1283     CHAR_MAP[j]   = '_';
1284     MAP_CHAR['_'] = j;
1285   }
1286 
1287   /** Decode characters into bytes.
1288    * Used by <a href="Utility.html#decode(java.lang.String, boolean)">decode()</a>
1289    */
1290   private static class JavaReader extends FilterReader {
1291     public JavaReader(Reader in) {
1292       super(in);
1293     }
1294 
1295     public int read() throws IOException {
1296       int b = in.read();
1297 
1298       if(b != ESCAPE_CHAR) {
1299         return b;
1300       } else {
1301         int i = in.read();
1302 
1303         if(i < 0)
1304           return -1;
1305 
1306         if(((i >= '0') && (i <= '9')) || ((i >= 'a') && (i <= 'f'))) { // Normal escape
1307           int j = in.read();
1308 
1309           if(j < 0)
1310             return -1;
1311 
1312           char[] tmp = { (char)i, (char)j };
1313           int    s   = Integer.parseInt(new String(tmp), 16);
1314 
1315           return s;
1316         } else { // Special escape
1317           return MAP_CHAR[i];
1318         }
1319       }
1320     }
1321 
1322     public int read(char[] cbuf, int off, int len) throws IOException {
1323       for(int i=0; i < len; i++)
1324         cbuf[off + i] = (char)read();
1325 
1326       return len;
1327     }
1328   }
1329 
1330   /** Encode bytes into valid java identifier characters.
1331    * Used by <a href="Utility.html#encode(byte[], boolean)">encode()</a>
1332    */
1333   private static class JavaWriter extends FilterWriter {
1334     public JavaWriter(Writer out) {
1335       super(out);
1336     }
1337 
1338     public void write(int b) throws IOException {
1339       if(isJavaIdentifierPart((char)b) && (b != ESCAPE_CHAR)) {
1340         out.write(b);
1341       } else {
1342         out.write(ESCAPE_CHAR); // Escape character
1343 
1344         // Special escape
1345         if(b >= 0 && b < FREE_CHARS) {
1346           out.write(CHAR_MAP[b]);
1347         } else { // Normal escape
1348           char[] tmp = Integer.toHexString(b).toCharArray();
1349 
1350           if(tmp.length == 1) {
1351             out.write('0');
1352             out.write(tmp[0]);
1353           } else {
1354             out.write(tmp[0]);
1355             out.write(tmp[1]);
1356           }
1357         }
1358       }
1359     }
1360 
1361     public void write(char[] cbuf, int off, int len) throws IOException {
1362       for(int i=0; i < len; i++)
1363         write(cbuf[off + i]);
1364     }
1365 
1366     public void write(String str, int off, int len) throws IOException {
1367       write(str.toCharArray(), off, len);
1368     }
1369   }
1370 
1371   /**
1372    * Escape all occurences of newline chars '\n', quotes \", etc.
1373    */
1374   public static final String convertString(String label) {
1375     char[]       ch  = label.toCharArray();
1376     StringBuffer buf = new StringBuffer();
1377 
1378     for(int i=0; i < ch.length; i++) {
1379       switch(ch[i]) {
1380       case '\n':
1381         buf.append("\\n"); break;
1382       case '\r':
1383         buf.append("\\r"); break;
1384       case '\"':
1385         buf.append("\\\""); break;
1386       case '\'':
1387         buf.append("\\'"); break;
1388       case '\\':
1389         buf.append("\\\\"); break;
1390       default:
1391         buf.append(ch[i]); break;
1392       }
1393     }
1394 
1395     return buf.toString();
1396   }
1397 }