package soot.coffi;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 1997 Clark Verbrugge
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import soot.Timers;
import soot.options.Options;

/**
 * A ClassFile object represents the contents of a <tt>.class</tt> file.
 * <p>
 * A ClassFile contains code for manipulation of its constituents.
 *
 * @author Clark Verbrugge
 */
public class ClassFile {
  private static final Logger logger = LoggerFactory.getLogger(ClassFile.class);

  /** Magic number. */
  static final long MAGIC = 0xCAFEBABEL;

  /** Access bit flag. */
  static final short ACC_PUBLIC = 0x0001;
  /** Access bit flag. */
  static final short ACC_PRIVATE = 0x0002;
  /** Access bit flag. */
  static final short ACC_PROTECTED = 0x0004;
  /** Access bit flag. */
  static final short ACC_STATIC = 0x0008;
  /** Access bit flag. */
  static final short ACC_FINAL = 0x0010;
  /** Access bit flag. */
  static final short ACC_SUPER = 0x0020;
  /** Access bit flag. */
  static final short ACC_VOLATILE = 0x0040;
  /** Access bit flag. */
  static final short ACC_TRANSIENT = 0x0080;
  /** Access bit flag. */
  static final short ACC_INTERFACE = 0x0200;
  /** Access bit flag. */
  static final short ACC_ABSTRACT = 0x0400;
  /** Access bit flag. */
  static final short ACC_STRICT = 0x0800;
  /** Access bit flag. */
  static final short ACC_ANNOTATION = 0x2000;
  /** Access bit flag. */
  static final short ACC_ENUM = 0x4000;
  /** Remaining bits in the access bit flag. */
  static final short ACC_UNKNOWN = 0x7000;

  /** Descriptor code string. */
  static final String DESC_BYTE = "B";
  /** Descriptor code string. */
  static final String DESC_CHAR = "C";
  /** Descriptor code string. */
  static final String DESC_DOUBLE = "D";
  /** Descriptor code string. */
  static final String DESC_FLOAT = "F";
  /** Descriptor code string. */
  static final String DESC_INT = "I";
  /** Descriptor code string. */
  static final String DESC_LONG = "J";
  /** Descriptor code string. */
  static final String DESC_OBJECT = "L";
  /** Descriptor code string. */
  static final String DESC_SHORT = "S";
  /** Descriptor code string. */
  static final String DESC_BOOLEAN = "Z";
  /** Descriptor code string. */
  static final String DESC_VOID = "V";
  /** Descriptor code string. */
  static final String DESC_ARRAY = "[";

  /** Debugging flag. */
  boolean debug;

  /** File name of the <tt>.class</tt> this represents. */
  String fn;

  /*
   * For chaining ClassFiles into a list. ClassFile next;
   */

  /**
   * Magic number read in.
   *
   * @see ClassFile#MAGIC
   */
  long magic;
  /** Minor version. */
  int minor_version;
  /** Major version. */
  int major_version;
  /** Number of items in the constant pool. */
  public int constant_pool_count;
  /**
   * Array of constant pool items.
   *
   * @see cp_info
   */
  public cp_info constant_pool[];
  /**
   * Access flags for this Class.
   */
  public int access_flags;
  /**
   * Constant pool index of the Class constant describing <i>this</i>.
   *
   * @see CONSTANT_Class_info
   */
  public int this_class;
  /**
   * Constant pool index of the Class constant describing <i>super</i>.
   *
   * @see CONSTANT_Class_info
   */
  public int super_class;
  /** Count of interfaces implemented. */
  public int interfaces_count;
  /**
   * Array of constant pool indices of Class constants describing each interace implemented by this class, as given in the
   * source for this class.
   *
   * @see CONSTANT_Class_info
   */
  public int interfaces[];
  /** Count of fields this Class contains. */
  public int fields_count;
  /**
   * Array of field_info objects describing each field.
   *
   * @see field_info
   */
  public field_info fields[];
  /** Count of methods this Class contains. */
  public int methods_count;
  /**
   * Array of method_info objects describing each field.
   *
   * @see method_info
   */
  public method_info methods[];
  /** Count of attributes this class contains. */
  public int attributes_count;
  /**
   * Array of attribute_info objects for this class.
   *
   * @see attribute_info
   */
  public attribute_info attributes[];

  /** bootstrap-methods attribute (if any) */
  public BootstrapMethods_attribute bootstrap_methods_attribute;

  /**
   * Creates a new ClassFile object given the name of the file.
   *
   * @param nfn
   *          file name which this ClassFile will represent.
   */
  public ClassFile(String nfn) {
    fn = nfn;
  }

  /** Returns the name of this Class. */
  public String toString() {
    return (constant_pool[this_class].toString(constant_pool));
  }

  public boolean loadClassFile(InputStream is) {
    InputStream f = null;
    InputStream classFileStream;
    DataInputStream d;
    boolean b;

    classFileStream = is;

    byte[] data;

    if (Options.v().time()) {
      Timers.v().readTimer.start();
    }

    try {
      DataInputStream classDataStream = new DataInputStream(classFileStream);
      data = new byte[classDataStream.available()];
      classDataStream.readFully(data);
      f = new ByteArrayInputStream(data);

    } catch (IOException e) {
      logger.debug(e.getMessage(), e);
    }

    if (Options.v().time()) {
      Timers.v().readTimer.end();
    }

    d = new DataInputStream(f);
    b = readClass(d);

    try {
      classFileStream.close();
      d.close();
      if (f != null) {
        f.close();
      }
    } catch (IOException e) {
      logger.debug("IOException with " + fn + ": " + e.getMessage());
      return false;
    }

    if (!b) {
      return false;
    }
    // parse(); // parse all methods & builds CFGs
    // logger.debug("-- Read " + cf + " --");
    return true;
  }

  /**
   * Main entry point for writing a class file. The file name is given in the constructor; this opens the file and writes the
   * internal representation.
   *
   * @return <i>true</i> on success.
   */
  boolean saveClassFile() {
    FileOutputStream f;
    DataOutputStream d;
    boolean b;
    try {
      f = new FileOutputStream(fn);
    } catch (FileNotFoundException e) {
      if (fn.indexOf(".class") >= 0) {
        logger.debug("Can't find " + fn);
        return false;
      }
      fn = fn + ".class";
      try {
        f = new FileOutputStream(fn);
      } catch (FileNotFoundException ee) {
        logger.debug("Can't find " + fn);
        return false;
      }
    }
    d = new DataOutputStream(f);
    b = writeClass(d);
    try {
      d.close();
      f.close();
    } catch (IOException e) {
      logger.debug("IOException with " + fn + ": " + e.getMessage());
      return false;
    }
    return b;
  }

  /**
   * Returns a String constructed by parsing the bits in the given access code (as defined by the ACC_* constants).
   *
   * @param af
   *          access code.
   * @param separator
   *          String used to separate words used for access bits.
   * @see ClassFile#access_flags
   * @see method_info#access_flags
   * @see field_info#access_flags
   */
  static String access_string(int af, String separator) {
    boolean hasone = false;
    String s = "";
    if ((af & ACC_PUBLIC) != 0) {
      s = "public";
      hasone = true;
    }
    if ((af & ACC_PRIVATE) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "private";
    }
    if ((af & ACC_PROTECTED) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "protected";
    }
    if ((af & ACC_STATIC) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "static";
    }
    if ((af & ACC_FINAL) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "final";
    }
    if ((af & ACC_SUPER) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "super";
    }
    if ((af & ACC_VOLATILE) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "volatile";
    }
    if ((af & ACC_TRANSIENT) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "transient";
    }
    if ((af & ACC_INTERFACE) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "interface";
    }
    if ((af & ACC_ABSTRACT) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "abstract";
    }
    if ((af & ACC_STRICT) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "strict";
    }
    if ((af & ACC_ANNOTATION) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "annotation";
    }
    if ((af & ACC_ENUM) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "enum";
    }
    if ((af & ACC_UNKNOWN) != 0) {
      if (hasone) {
        s = s + separator;
      } else {
        hasone = true;
      }
      s = s + "unknown";
    }
    return s;
  }

  /**
   * Builds the internal representation of this Class by reading in the given class file.
   *
   * @param d
   *          Stream forming the <tt>.class</tt> file.
   * @return <i>true</i> if read was successful, <i>false</i> on some error.
   */
  public boolean readClass(DataInputStream d) {
    try {
      // first read in magic number
      magic = d.readInt() & 0xFFFFFFFFL;
      if (magic != MAGIC) {
        logger.debug("Wrong magic number in " + fn + ": " + magic);
        return false;
      }
      minor_version = d.readUnsignedShort();
      major_version = d.readUnsignedShort();
      constant_pool_count = d.readUnsignedShort();

      if (!readConstantPool(d)) {
        return false;
      }

      access_flags = d.readUnsignedShort();
      this_class = d.readUnsignedShort();
      super_class = d.readUnsignedShort();
      interfaces_count = d.readUnsignedShort();
      if (interfaces_count > 0) {
        interfaces = new int[interfaces_count];
        int j;
        for (j = 0; j < interfaces_count; j++) {
          interfaces[j] = d.readUnsignedShort();
        }
      }

      if (Options.v().time()) {
        Timers.v().fieldTimer.start();
      }

      fields_count = d.readUnsignedShort();
      readFields(d);

      if (Options.v().time()) {
        Timers.v().fieldTimer.end();
      }

      if (Options.v().time()) {
        Timers.v().methodTimer.start();
      }
      methods_count = d.readUnsignedShort();
      readMethods(d);

      if (Options.v().time()) {
        Timers.v().methodTimer.end();
      }

      if (Options.v().time()) {
        Timers.v().attributeTimer.start();
      }

      attributes_count = d.readUnsignedShort();
      if (attributes_count > 0) {
        attributes = new attribute_info[attributes_count];
        readAttributes(d, attributes_count, attributes);
      }
      if (Options.v().time()) {
        Timers.v().attributeTimer.end();
      }

    } catch (IOException e) {
      throw new RuntimeException("IOException with " + fn + ": " + e.getMessage(), e);
    }

    return true;
  }

  /**
   * Reads in the constant pool from the given stream.
   *
   * @param d
   *          Stream forming the <tt>.class</tt> file.
   * @return <i>true</i> if read was successful, <i>false</i> on some error.
   * @exception java.io.IOException
   *              on error.
   */
  protected boolean readConstantPool(DataInputStream d) throws IOException {
    byte tag;
    cp_info cp;
    int i;
    boolean skipone; // set if next cp entry is to be skipped

    constant_pool = new cp_info[constant_pool_count];
    // Instruction.constant_pool = constant_pool;
    skipone = false;

    for (i = 1; i < constant_pool_count; i++) {
      if (skipone) {
        skipone = false;
        continue;
      }
      tag = (byte) d.readUnsignedByte();
      switch (tag) {
        case cp_info.CONSTANT_Class:
          cp = new CONSTANT_Class_info();
          ((CONSTANT_Class_info) cp).name_index = d.readUnsignedShort();
          if (debug) {
            logger.debug("Constant pool[" + i + "]: Class");
          }
          break;
        case cp_info.CONSTANT_Fieldref:
          cp = new CONSTANT_Fieldref_info();
          ((CONSTANT_Fieldref_info) cp).class_index = d.readUnsignedShort();
          ((CONSTANT_Fieldref_info) cp).name_and_type_index = d.readUnsignedShort();
          if (debug) {
            logger.debug("Constant pool[" + i + "]: Fieldref");
          }
          break;
        case cp_info.CONSTANT_Methodref:
          cp = new CONSTANT_Methodref_info();
          ((CONSTANT_Methodref_info) cp).class_index = d.readUnsignedShort();
          ((CONSTANT_Methodref_info) cp).name_and_type_index = d.readUnsignedShort();
          if (debug) {
            logger.debug("Constant pool[" + i + "]: Methodref");
          }
          break;
        case cp_info.CONSTANT_InterfaceMethodref:
          cp = new CONSTANT_InterfaceMethodref_info();
          ((CONSTANT_InterfaceMethodref_info) cp).class_index = d.readUnsignedShort();
          ((CONSTANT_InterfaceMethodref_info) cp).name_and_type_index = d.readUnsignedShort();
          if (debug) {
            logger.debug("Constant pool[" + i + "]: MethodHandle");
          }
          break;
        case cp_info.CONSTANT_String:
          cp = new CONSTANT_String_info();
          ((CONSTANT_String_info) cp).string_index = d.readUnsignedShort();
          if (debug) {
            logger.debug("Constant pool[" + i + "]: String");
          }
          break;
        case cp_info.CONSTANT_Integer:
          cp = new CONSTANT_Integer_info();
          ((CONSTANT_Integer_info) cp).bytes = d.readInt();
          if (debug) {
            logger.debug("Constant pool[" + i + "]: Integer = " + ((CONSTANT_Integer_info) cp).bytes);
          }
          break;
        case cp_info.CONSTANT_Float:
          cp = new CONSTANT_Float_info();
          ((CONSTANT_Float_info) cp).bytes = d.readInt();
          if (debug) {
            logger.debug("Constant pool[" + i + "]: Float = " + ((CONSTANT_Float_info) cp).convert());
          }
          break;
        case cp_info.CONSTANT_Long:
          cp = new CONSTANT_Long_info();
          ((CONSTANT_Long_info) cp).high = d.readInt() & 0xFFFFFFFFL;
          ((CONSTANT_Long_info) cp).low = d.readInt() & 0xFFFFFFFFL;

          if (debug) {
            String temp = cp.toString(constant_pool);
            logger.debug("Constant pool[" + i + "]: Long = " + temp);
            /*
             * logger.debug("Constant pool[" + i + "]: that's " + cp.printBits(((CONSTANT_Long_info)cp).high) + " <<32 + " +
             * cp.printBits(((CONSTANT_Long_info)cp).low) + " = " + cp.printBits(((CONSTANT_Long_info)cp).convert()));
             */
          }
          skipone = true; // next entry needs to be skipped
          break;
        case cp_info.CONSTANT_Double:
          cp = new CONSTANT_Double_info();
          ((CONSTANT_Double_info) cp).high = d.readInt() & 0xFFFFFFFFL;
          ((CONSTANT_Double_info) cp).low = d.readInt() & 0xFFFFFFFFL;
          if (debug) {
            logger.debug("Constant pool[" + i + "]: Double = " + ((CONSTANT_Double_info) cp).convert());
          }
          skipone = true; // next entry needs to be skipped
          break;
        case cp_info.CONSTANT_NameAndType:
          cp = new CONSTANT_NameAndType_info();
          ((CONSTANT_NameAndType_info) cp).name_index = d.readUnsignedShort();
          ((CONSTANT_NameAndType_info) cp).descriptor_index = d.readUnsignedShort();
          if (debug) {
            logger.debug("Constant pool[" + i + "]: Name and Type");
          }
          break;
        case cp_info.CONSTANT_Utf8:
          CONSTANT_Utf8_info cputf8 = new CONSTANT_Utf8_info(d);
          // If an equivalent CONSTANT_Utf8 already exists, we return
          // the pre-existing one and allow cputf8 to be GC'd.
          cp = (cp_info) CONSTANT_Utf8_collector.v().add(cputf8);
          if (debug) {
            logger.debug("Constant pool[" + i + "]: Utf8 = \"" + cputf8.convert() + "\"");
          }
          break;
        case cp_info.CONSTANT_MethodHandle:
          cp = new CONSTANT_MethodHandle_info();
          ((CONSTANT_MethodHandle_info) cp).kind = d.readByte();
          ((CONSTANT_MethodHandle_info) cp).target_index = d.readUnsignedShort();
          break;
        case cp_info.CONSTANT_InvokeDynamic:
          cp = new CONSTANT_InvokeDynamic_info();
          ((CONSTANT_InvokeDynamic_info) cp).bootstrap_method_index = d.readUnsignedShort();
          ((CONSTANT_InvokeDynamic_info) cp).name_and_type_index = d.readUnsignedShort();
          break;
        default:
          logger.debug("Unknown tag in constant pool: " + tag + " at entry " + i);
          return false;
      }
      cp.tag = tag;
      constant_pool[i] = cp;
    }
    return true;
  }

  private void readAllBytes(byte[] dest, DataInputStream d) throws IOException {
    int total_len = dest.length;
    int read_len = 0;
    while (read_len < total_len) {
      int to_read = total_len - read_len;
      int curr_read = d.read(dest, read_len, to_read);
      read_len += curr_read;
    }
  }

  /**
   * Reads in the given number of attributes from the given stream.
   *
   * @param d
   *          Stream forming the <tt>.class</tt> file.
   * @param attributes_count
   *          number of attributes to read in.
   * @param ai
   *          pre-allocated array of attributes to be filled in.
   * @return <i>true</i> if read was successful, <i>false</i> on some error.
   * @exception java.io.IOException
   *              on error.
   */
  protected boolean readAttributes(DataInputStream d, int attributes_count, attribute_info[] ai) throws IOException {
    attribute_info a = null;
    int i;
    int j;
    long len;
    String s;

    for (i = 0; i < attributes_count; i++) {

      j = d.readUnsignedShort(); // read attribute name before allocating
      len = d.readInt() & 0xFFFFFFFFL;
      s = ((CONSTANT_Utf8_info) (constant_pool[j])).convert();
      if (s.compareTo(attribute_info.SourceFile) == 0) {
        SourceFile_attribute sa = new SourceFile_attribute();
        sa.sourcefile_index = d.readUnsignedShort();
        a = (attribute_info) sa;
      } else if (s.compareTo(attribute_info.ConstantValue) == 0) {
        ConstantValue_attribute ca = new ConstantValue_attribute();
        ca.constantvalue_index = d.readUnsignedShort();
        a = (attribute_info) ca;
      } else if (s.compareTo(attribute_info.Code) == 0) {
        Code_attribute ca = new Code_attribute();
        ca.max_stack = d.readUnsignedShort();
        ca.max_locals = d.readUnsignedShort();
        ca.code_length = d.readInt() & 0xFFFFFFFFL;
        ca.code = new byte[(int) ca.code_length];
        readAllBytes(ca.code, d);
        ca.exception_table_length = d.readUnsignedShort();
        ca.exception_table = new exception_table_entry[ca.exception_table_length];
        int k;
        exception_table_entry e;
        for (k = 0; k < ca.exception_table_length; k++) {
          e = new exception_table_entry();
          e.start_pc = d.readUnsignedShort();
          e.end_pc = d.readUnsignedShort();
          e.handler_pc = d.readUnsignedShort();
          e.catch_type = d.readUnsignedShort();
          ca.exception_table[k] = e;
        }
        ca.attributes_count = d.readUnsignedShort();
        ca.attributes = new attribute_info[ca.attributes_count];
        readAttributes(d, ca.attributes_count, ca.attributes);
        a = (attribute_info) ca;

      } else if (s.compareTo(attribute_info.Exceptions) == 0) {
        Exception_attribute ea = new Exception_attribute();
        ea.number_of_exceptions = d.readUnsignedShort();
        if (ea.number_of_exceptions > 0) {
          int k;
          ea.exception_index_table = new int[ea.number_of_exceptions];
          for (k = 0; k < ea.number_of_exceptions; k++) {
            ea.exception_index_table[k] = d.readUnsignedShort();
          }
        }
        a = (attribute_info) ea;
      } else if (s.compareTo(attribute_info.LineNumberTable) == 0) {
        LineNumberTable_attribute la = new LineNumberTable_attribute();
        la.line_number_table_length = d.readUnsignedShort();
        int k;
        line_number_table_entry e;
        la.line_number_table = new line_number_table_entry[la.line_number_table_length];
        for (k = 0; k < la.line_number_table_length; k++) {
          e = new line_number_table_entry();
          e.start_pc = d.readUnsignedShort();
          e.line_number = d.readUnsignedShort();
          la.line_number_table[k] = e;
        }
        a = (attribute_info) la;
      } else if (s.compareTo(attribute_info.LocalVariableTable) == 0) {
        LocalVariableTable_attribute la = new LocalVariableTable_attribute();
        la.local_variable_table_length = d.readUnsignedShort();
        int k;
        local_variable_table_entry e;
        la.local_variable_table = new local_variable_table_entry[la.local_variable_table_length];
        for (k = 0; k < la.local_variable_table_length; k++) {
          e = new local_variable_table_entry();
          e.start_pc = d.readUnsignedShort();
          e.length = d.readUnsignedShort();
          e.name_index = d.readUnsignedShort();
          e.descriptor_index = d.readUnsignedShort();
          e.index = d.readUnsignedShort();
          la.local_variable_table[k] = e;
        }
        a = (attribute_info) la;
      } else if (s.compareTo(attribute_info.LocalVariableTypeTable) == 0) {
        LocalVariableTypeTable_attribute la = new LocalVariableTypeTable_attribute();
        la.local_variable_type_table_length = d.readUnsignedShort();
        int k;
        local_variable_type_table_entry e;
        la.local_variable_type_table = new local_variable_type_table_entry[la.local_variable_type_table_length];
        for (k = 0; k < la.local_variable_type_table_length; k++) {
          e = new local_variable_type_table_entry();
          e.start_pc = d.readUnsignedShort();
          e.length = d.readUnsignedShort();
          e.name_index = d.readUnsignedShort();
          e.signature_index = d.readUnsignedShort();
          e.index = d.readUnsignedShort();
          la.local_variable_type_table[k] = e;
        }
        a = (attribute_info) la;
      } else if (s.compareTo(attribute_info.Synthetic) == 0) {
        Synthetic_attribute ia = new Synthetic_attribute();
        a = (attribute_info) ia;
      } else if (s.compareTo(attribute_info.Signature) == 0) {
        Signature_attribute ia = new Signature_attribute();
        ia.signature_index = d.readUnsignedShort();
        a = (attribute_info) ia;
      } else if (s.compareTo(attribute_info.Deprecated) == 0) {
        Deprecated_attribute da = new Deprecated_attribute();
        a = (attribute_info) da;
      } else if (s.compareTo(attribute_info.EnclosingMethod) == 0) {
        EnclosingMethod_attribute ea = new EnclosingMethod_attribute();
        ea.class_index = d.readUnsignedShort();
        ea.method_index = d.readUnsignedShort();
        a = (attribute_info) ea;
      } else if (s.compareTo(attribute_info.InnerClasses) == 0) {
        InnerClasses_attribute ia = new InnerClasses_attribute();
        ia.inner_classes_length = d.readUnsignedShort();
        ia.inner_classes = new inner_class_entry[ia.inner_classes_length];
        for (int k = 0; k < ia.inner_classes_length; k++) {
          inner_class_entry e = new inner_class_entry();
          e.inner_class_index = d.readUnsignedShort();
          e.outer_class_index = d.readUnsignedShort();
          e.name_index = d.readUnsignedShort();
          e.access_flags = d.readUnsignedShort();
          ia.inner_classes[k] = e;
        }
        a = (attribute_info) ia;
      } else if (s.compareTo(attribute_info.RuntimeVisibleAnnotations) == 0) {
        RuntimeVisibleAnnotations_attribute ra = new RuntimeVisibleAnnotations_attribute();
        ra.number_of_annotations = d.readUnsignedShort();
        ra.annotations = new annotation[ra.number_of_annotations];
        for (int k = 0; k < ra.number_of_annotations; k++) {
          annotation annot = new annotation();
          annot.type_index = d.readUnsignedShort();
          annot.num_element_value_pairs = d.readUnsignedShort();
          annot.element_value_pairs = readElementValues(annot.num_element_value_pairs, d, true, 0);
          ra.annotations[k] = annot;
        }

        a = (attribute_info) ra;
      } else if (s.compareTo(attribute_info.RuntimeInvisibleAnnotations) == 0) {
        RuntimeInvisibleAnnotations_attribute ra = new RuntimeInvisibleAnnotations_attribute();
        ra.number_of_annotations = d.readUnsignedShort();
        ra.annotations = new annotation[ra.number_of_annotations];
        for (int k = 0; k < ra.number_of_annotations; k++) {
          annotation annot = new annotation();
          annot.type_index = d.readUnsignedShort();
          annot.num_element_value_pairs = d.readUnsignedShort();
          annot.element_value_pairs = readElementValues(annot.num_element_value_pairs, d, true, 0);
          ra.annotations[k] = annot;
        }
        a = (attribute_info) ra;
      } else if (s.compareTo(attribute_info.RuntimeVisibleParameterAnnotations) == 0) {
        RuntimeVisibleParameterAnnotations_attribute ra = new RuntimeVisibleParameterAnnotations_attribute();
        ra.num_parameters = d.readUnsignedByte();
        ra.parameter_annotations = new parameter_annotation[ra.num_parameters];
        for (int x = 0; x < ra.num_parameters; x++) {
          parameter_annotation pAnnot = new parameter_annotation();
          pAnnot.num_annotations = d.readUnsignedShort();
          pAnnot.annotations = new annotation[pAnnot.num_annotations];
          for (int k = 0; k < pAnnot.num_annotations; k++) {
            annotation annot = new annotation();
            annot.type_index = d.readUnsignedShort();
            annot.num_element_value_pairs = d.readUnsignedShort();
            annot.element_value_pairs = readElementValues(annot.num_element_value_pairs, d, true, 0);
            pAnnot.annotations[k] = annot;
          }
          ra.parameter_annotations[x] = pAnnot;
        }
        a = (attribute_info) ra;
      } else if (s.compareTo(attribute_info.RuntimeInvisibleParameterAnnotations) == 0) {
        RuntimeInvisibleParameterAnnotations_attribute ra = new RuntimeInvisibleParameterAnnotations_attribute();
        ra.num_parameters = d.readUnsignedByte();
        ra.parameter_annotations = new parameter_annotation[ra.num_parameters];
        for (int x = 0; x < ra.num_parameters; x++) {
          parameter_annotation pAnnot = new parameter_annotation();
          pAnnot.num_annotations = d.readUnsignedShort();
          pAnnot.annotations = new annotation[pAnnot.num_annotations];
          for (int k = 0; k < pAnnot.num_annotations; k++) {
            annotation annot = new annotation();
            annot.type_index = d.readUnsignedShort();
            annot.num_element_value_pairs = d.readUnsignedShort();
            annot.element_value_pairs = readElementValues(annot.num_element_value_pairs, d, true, 0);
            pAnnot.annotations[k] = annot;
          }
          ra.parameter_annotations[x] = pAnnot;
        }
        a = (attribute_info) ra;
      } else if (s.compareTo(attribute_info.AnnotationDefault) == 0) {
        AnnotationDefault_attribute da = new AnnotationDefault_attribute();
        element_value[] result = readElementValues(1, d, false, 0);
        da.default_value = result[0];
        a = (attribute_info) da;
      } else if (s.equals(attribute_info.BootstrapMethods)) {
        BootstrapMethods_attribute bsma = new BootstrapMethods_attribute();
        int count = d.readUnsignedShort();
        bsma.method_handles = new short[count];
        bsma.arg_indices = new short[count][];
        for (int num = 0; num < count; num++) {
          short index = (short) d.readUnsignedShort();
          bsma.method_handles[num] = index;
          int argCount = d.readUnsignedShort();
          bsma.arg_indices[num] = new short[argCount];
          for (int numArg = 0; numArg < argCount; numArg++) {
            short indexArg = (short) d.readUnsignedShort();
            bsma.arg_indices[num][numArg] = indexArg;
          }
        }
        assert bootstrap_methods_attribute == null : "More than one bootstrap methods attribute!";
        a = bootstrap_methods_attribute = bsma;
      } else {
        // unknown attribute
        // logger.debug("Generic/Unknown Attribute: " + s);
        Generic_attribute ga = new Generic_attribute();
        if (len > 0) {
          ga.info = new byte[(int) len];
          readAllBytes(ga.info, d);
        }
        a = (attribute_info) ga;
      }
      a.attribute_name = j;
      a.attribute_length = len;
      ai[i] = a;
    }
    return true;
  }

  private element_value[] readElementValues(int count, DataInputStream d, boolean needName, int name_index)
      throws IOException {
    element_value[] list = new element_value[count];
    for (int x = 0; x < count; x++) {
      if (needName) {
        name_index = d.readUnsignedShort();
      }
      int tag = d.readUnsignedByte();
      char kind = (char) tag;
      if (kind == 'B' || kind == 'C' || kind == 'D' || kind == 'F' || kind == 'I' || kind == 'J' || kind == 'S'
          || kind == 'Z' || kind == 's') {
        constant_element_value elem = new constant_element_value();
        elem.name_index = name_index;
        elem.tag = kind;
        elem.constant_value_index = d.readUnsignedShort();
        list[x] = elem;
      } else if (kind == 'e') {
        enum_constant_element_value elem = new enum_constant_element_value();
        elem.name_index = name_index;
        elem.tag = kind;
        elem.type_name_index = d.readUnsignedShort();
        elem.constant_name_index = d.readUnsignedShort();
        list[x] = elem;
      } else if (kind == 'c') {
        class_element_value elem = new class_element_value();
        elem.name_index = name_index;
        elem.tag = kind;
        elem.class_info_index = d.readUnsignedShort();
        list[x] = elem;
      } else if (kind == '[') {
        array_element_value elem = new array_element_value();
        elem.name_index = name_index;
        elem.tag = kind;
        elem.num_values = d.readUnsignedShort();
        elem.values = readElementValues(elem.num_values, d, false, name_index);
        list[x] = elem;
      } else if (kind == '@') {
        annotation_element_value elem = new annotation_element_value();
        elem.name_index = name_index;
        elem.tag = kind;
        annotation annot = new annotation();
        annot.type_index = d.readUnsignedShort();
        annot.num_element_value_pairs = d.readUnsignedShort();
        annot.element_value_pairs = readElementValues(annot.num_element_value_pairs, d, true, 0);
        elem.annotation_value = annot;
        list[x] = elem;
      } else {
        throw new RuntimeException("Unknown element value pair kind: " + kind);
      }
    }
    return list;
  }

  /**
   * Reads in the fields from the given stream.
   *
   * @param d
   *          Stream forming the <tt>.class</tt> file.
   * @return <i>true</i> if read was successful, <i>false</i> on some error.
   * @exception java.io.IOException
   *              on error.
   */
  protected boolean readFields(DataInputStream d) throws IOException {
    field_info fi;
    int i;

    fields = new field_info[fields_count];

    for (i = 0; i < fields_count; i++) {
      fi = new field_info();
      fi.access_flags = d.readUnsignedShort();
      fi.name_index = d.readUnsignedShort();
      fi.descriptor_index = d.readUnsignedShort();
      fi.attributes_count = d.readUnsignedShort();
      if (fi.attributes_count > 0) {
        fi.attributes = new attribute_info[fi.attributes_count];
        readAttributes(d, fi.attributes_count, fi.attributes);
      }
      /*
       * CONSTANT_Utf8_info ci; ci = (CONSTANT_Utf8_info)(constant_pool[fi.name_index]); logger.debug("Field: " +
       * ci.convert());
       */
      fields[i] = fi;
    }

    return true;
  }

  /**
   * Reads in the methods from the given stream.
   *
   * @param d
   *          Stream forming the <tt>.class</tt> file.
   * @return <i>true</i> if read was successful, <i>false</i> on some error.
   * @exception java.io.IOException
   *              on error.
   */
  protected boolean readMethods(DataInputStream d) throws IOException {
    method_info mi;
    int i;

    methods = new method_info[methods_count];

    for (i = 0; i < methods_count; i++) {
      mi = new method_info();
      mi.access_flags = d.readUnsignedShort();

      mi.name_index = d.readUnsignedShort();

      mi.descriptor_index = d.readUnsignedShort();

      mi.attributes_count = d.readUnsignedShort();

      // logger.debug("Has " + mi.attributes_count + " attribute(s)");

      if (mi.attributes_count > 0) {
        mi.attributes = new attribute_info[mi.attributes_count];
        readAttributes(d, mi.attributes_count, mi.attributes);

        for (int j = 0; j < mi.attributes_count; j++) {
          if (mi.attributes[j] instanceof Code_attribute) {
            mi.code_attr = (Code_attribute) mi.attributes[j];
            break;
          }
        }
      }

      /*
       * if ("main".compareTo(ci.convert())==0) { decompile(mi); }
       */

      methods[i] = mi;
    }

    return true;
  }

  /*
   * DEPRECATED public void showByteCode(Code_attribute ca) { int i=0,j;
   *
   * logger.debug("Code bytes follow..."); while(i<ca.code_length) { j = (int)(ca.code[i]); j &= 0xff;
   * logger.debug(""+Integer.toString(j) + " "); i++; } logger.debug(""); }
   */

  /**
   * Writes the current constant pool to the given stream.
   *
   * @param dd
   *          output stream.
   * @return <i>true</i> if write was successful, <i>false</i> on some error.
   * @exception java.io.IOException
   *              on error.
   */
  protected boolean writeConstantPool(DataOutputStream dd) throws IOException {
    cp_info cp;
    int i;
    boolean skipone = false;

    for (i = 1; i < constant_pool_count; i++) {
      if (skipone) {
        skipone = false;
        continue;
      }
      cp = constant_pool[i];
      dd.writeByte(cp.tag);
      switch (cp.tag) {
        case cp_info.CONSTANT_Class:
          dd.writeShort(((CONSTANT_Class_info) cp).name_index);
          break;
        case cp_info.CONSTANT_Fieldref:
          dd.writeShort(((CONSTANT_Fieldref_info) cp).class_index);
          dd.writeShort(((CONSTANT_Fieldref_info) cp).name_and_type_index);
          break;
        case cp_info.CONSTANT_Methodref:
          dd.writeShort(((CONSTANT_Methodref_info) cp).class_index);
          dd.writeShort(((CONSTANT_Methodref_info) cp).name_and_type_index);
          break;
        case cp_info.CONSTANT_InterfaceMethodref:
          dd.writeShort(((CONSTANT_InterfaceMethodref_info) cp).class_index);
          dd.writeShort(((CONSTANT_InterfaceMethodref_info) cp).name_and_type_index);
          break;
        case cp_info.CONSTANT_String:
          dd.writeShort(((CONSTANT_String_info) cp).string_index);
          break;
        case cp_info.CONSTANT_Integer:
          dd.writeInt((int) ((CONSTANT_Integer_info) cp).bytes);
          break;
        case cp_info.CONSTANT_Float:
          dd.writeInt((int) ((CONSTANT_Float_info) cp).bytes);
          break;
        case cp_info.CONSTANT_Long:
          dd.writeInt((int) ((CONSTANT_Long_info) cp).high);
          dd.writeInt((int) ((CONSTANT_Long_info) cp).low);
          skipone = true;
          break;
        case cp_info.CONSTANT_Double:
          dd.writeInt((int) ((CONSTANT_Double_info) cp).high);
          dd.writeInt((int) ((CONSTANT_Double_info) cp).low);
          skipone = true;
          break;
        case cp_info.CONSTANT_NameAndType:
          dd.writeShort(((CONSTANT_NameAndType_info) cp).name_index);
          dd.writeShort(((CONSTANT_NameAndType_info) cp).descriptor_index);
          break;
        case cp_info.CONSTANT_Utf8:
          ((CONSTANT_Utf8_info) cp).writeBytes(dd);
          break;
        default:
          logger.debug("Unknown tag in constant pool: " + cp.tag);
          return false;
      }
    }
    return true;
  }

  /**
   * Writes the given array of attributes to the given stream.
   *
   * @param dd
   *          output stream.
   * @param attributes_count
   *          number of attributes to write.
   * @param ai
   *          array of attributes to write.
   * @return <i>true</i> if write was successful, <i>false</i> on some error.
   * @exception java.io.IOException
   *              on error.
   */
  protected boolean writeAttributes(DataOutputStream dd, int attributes_count, attribute_info[] ai) throws IOException {
    attribute_info a = null;
    int i;

    for (i = 0; i < attributes_count; i++) {
      a = ai[i];
      dd.writeShort(a.attribute_name);
      dd.writeInt((int) a.attribute_length);
      if (a instanceof SourceFile_attribute) {
        SourceFile_attribute sa = (SourceFile_attribute) a;
        dd.writeShort(sa.sourcefile_index);
      } else if (a instanceof ConstantValue_attribute) {
        ConstantValue_attribute ca = (ConstantValue_attribute) a;
        dd.writeShort(ca.constantvalue_index);
      } else if (a instanceof Code_attribute) {
        Code_attribute ca = (Code_attribute) a;
        dd.writeShort(ca.max_stack);
        dd.writeShort(ca.max_locals);
        dd.writeInt((int) ca.code_length);
        dd.write(ca.code, 0, (int) ca.code_length);
        dd.writeShort(ca.exception_table_length);
        int k;
        exception_table_entry e;
        for (k = 0; k < ca.exception_table_length; k++) {
          e = ca.exception_table[k];
          dd.writeShort(e.start_pc);
          dd.writeShort(e.end_pc);
          dd.writeShort(e.handler_pc);
          dd.writeShort(e.catch_type);
        }
        dd.writeShort(ca.attributes_count);
        if (ca.attributes_count > 0) {
          writeAttributes(dd, ca.attributes_count, ca.attributes);
        }
      } else if (a instanceof Exception_attribute) {
        Exception_attribute ea = (Exception_attribute) a;
        dd.writeShort(ea.number_of_exceptions);
        if (ea.number_of_exceptions > 0) {
          int k;
          for (k = 0; k < ea.number_of_exceptions; k++) {
            dd.writeShort(ea.exception_index_table[k]);
          }
        }
      } else if (a instanceof LineNumberTable_attribute) {
        LineNumberTable_attribute la = (LineNumberTable_attribute) a;
        dd.writeShort(la.line_number_table_length);
        int k;
        line_number_table_entry e;
        for (k = 0; k < la.line_number_table_length; k++) {
          e = la.line_number_table[k];
          dd.writeShort(e.start_pc);
          dd.writeShort(e.line_number);
        }
      } else if (a instanceof LocalVariableTable_attribute) {
        LocalVariableTable_attribute la = (LocalVariableTable_attribute) a;
        dd.writeShort(la.local_variable_table_length);
        int k;
        local_variable_table_entry e;
        for (k = 0; k < la.local_variable_table_length; k++) {
          e = la.local_variable_table[k];
          dd.writeShort(e.start_pc);
          dd.writeShort(e.length);
          dd.writeShort(e.name_index);
          dd.writeShort(e.descriptor_index);
          dd.writeShort(e.index);
        }
      } else {
        // unknown attribute
        logger.debug("Generic/Unknown Attribute in output");
        Generic_attribute ga = (Generic_attribute) a;
        if (ga.attribute_length > 0) {
          dd.write(ga.info, 0, (int) ga.attribute_length);
        }
      }
    }
    return true;
  }

  /**
   * Writes the fields to the given stream.
   *
   * @param dd
   *          output stream.
   * @return <i>true</i> if write was successful, <i>false</i> on some error.
   * @exception java.io.IOException
   *              on error.
   */
  protected boolean writeFields(DataOutputStream dd) throws IOException {
    field_info fi;
    int i;

    for (i = 0; i < fields_count; i++) {
      fi = fields[i];
      dd.writeShort(fi.access_flags);
      dd.writeShort(fi.name_index);
      dd.writeShort(fi.descriptor_index);
      dd.writeShort(fi.attributes_count);
      if (fi.attributes_count > 0) {
        writeAttributes(dd, fi.attributes_count, fi.attributes);
      }
    }
    return true;
  }

  /**
   * Writes the methods to the given stream.
   *
   * @param dd
   *          output stream.
   * @return <i>true</i> if write was successful, <i>false</i> on some error.
   * @exception java.io.IOException
   *              on error.
   */
  protected boolean writeMethods(DataOutputStream dd) throws IOException {
    method_info mi;
    int i;

    for (i = 0; i < methods_count; i++) {
      mi = methods[i];
      dd.writeShort(mi.access_flags);
      dd.writeShort(mi.name_index);
      dd.writeShort(mi.descriptor_index);
      dd.writeShort(mi.attributes_count);
      if (mi.attributes_count > 0) {
        writeAttributes(dd, mi.attributes_count, mi.attributes);
      }
    }
    return true;
  }

  /**
   * Writes this entire ClassFile object to the given stream.
   *
   * @param dd
   *          output stream.
   * @return <i>true</i> if write was successful, <i>false</i> on some error.
   */
  boolean writeClass(DataOutputStream dd) {
    // outputs the .class file from the loaded one
    try {
      // first write magic number
      dd.writeInt((int) magic);

      dd.writeShort(minor_version);
      dd.writeShort(major_version);
      dd.writeShort(constant_pool_count);

      if (!writeConstantPool(dd)) {
        return false;
      }

      dd.writeShort(access_flags);
      dd.writeShort(this_class);
      dd.writeShort(super_class);
      dd.writeShort(interfaces_count);
      if (interfaces_count > 0) {
        int j;
        for (j = 0; j < interfaces_count; j++) {
          dd.writeShort(interfaces[j]);
        }
      }

      dd.writeShort(fields_count);
      writeFields(dd);

      dd.writeShort(methods_count);
      writeMethods(dd);

      dd.writeShort(attributes_count);
      if (attributes_count > 0) {
        writeAttributes(dd, attributes_count, attributes);
      }
    } catch (IOException e) {
      logger.debug("IOException with " + fn + ": " + e.getMessage());
      return false;
    }
    return true;
  }

  /**
   * Parses the given method, converting its bytecode array into a list of Instruction objects.
   *
   * @param m
   *          method to parse.
   * @return head of a list of Instructions.
   * @see Instruction
   * @see ByteCode
   * @see ByteCode#disassemble_bytecode
   */
  public Instruction parseMethod(method_info m) {
    // first task, look through attributes for a code attribute
    int j;
    Code_attribute ca;
    ByteCode bc;
    Instruction inst, head, tail;
    exception_table_entry e;

    head = null;
    tail = null;
    bc = new ByteCode();

    ca = m.locate_code_attribute();
    if (ca == null) {
      return null;
    }

    j = 0;
    while (j < ca.code_length) {
      inst = bc.disassemble_bytecode(ca.code, j);
      inst.originalIndex = j;
      // logger.debug(""+inst + ": " + (((int)(inst.code))&0xff));
      // logger.debug(""+j + " : " + inst);

      if (inst instanceof Instruction_Unknown) {
        logger.debug("Unknown instruction in \"" + m.toName(constant_pool) + "\" at offset " + j);
        logger.debug(" bytecode = " + (((int) (inst.code)) & 0xff));
      }
      // logger.debug("before: " + j);
      j = inst.nextOffset(j);
      // logger.debug("after: " + j);

      if (head == null) {
        head = inst;
      } else {
        tail.next = inst;
        inst.prev = tail;
      }
      tail = inst;
    }

    // bytecode converted into instructions, now build pointers
    bc.build(head);

    // also change exception table to use pointers instead of absolute addresses
    for (j = 0; j < ca.exception_table_length; j++) {
      e = ca.exception_table[j];
      e.start_inst = bc.locateInst(e.start_pc);
      if (e.end_pc == ca.code_length) {
        e.end_inst = null;
      } else {
        e.end_inst = bc.locateInst(e.end_pc);
      }
      e.handler_inst = bc.locateInst(e.handler_pc);
      if (e.handler_inst != null) {
        e.handler_inst.labelled = true;
      }
    }

    m.instructions = head;

    for (attribute_info element : ca.attributes) {
      if (element instanceof LineNumberTable_attribute) {
        LineNumberTable_attribute lntattr = (LineNumberTable_attribute) element;

        for (line_number_table_entry element0 : lntattr.line_number_table) {
          element0.start_inst = bc.locateInst(element0.start_pc);
        }
      }
    }

    return head;
  }

  /**
   * For every method, this calls parseMethod, storing the list of Instructions in the method_info object, and also
   * constructs the corresponding CFG.
   *
   * @see ClassFile#parseMethod
   * @see CFG
   */
  public void parse() {
    method_info mi;
    int i;

    for (i = 0; i < methods_count; i++) {
      mi = methods[i];
      mi.instructions = parseMethod(mi);
      // new CFG(mi);
      // don't build it right away for now
    }
  }

  /**
   * Recomputes the offset of each Instruction starting from 0; used when converting references back to offsets.
   *
   * @param i
   *          list of Instructions to process.
   * @return length of corresponding bytecode.
   * @see Instruction#nextOffset
   */
  int relabel(Instruction i) {
    int index = 0;
    while (i != null) {
      i.label = index;
      index = i.nextOffset(index);
      i = i.next;
    }
    return index;
  }

  /**
   * Inversive to parseMethod, this converts the list of Instructions stored in a method_info object back to an array of
   * bytecode.
   *
   * @param m
   *          method to unparse.
   * @return array of bytecode, or <i>null</i> on error.
   * @see CFG#reconstructInstructions
   * @see ClassFile#parseMethod
   * @see ClassFile#relabel
   * @see Instruction#compile
   */
  byte[] unparseMethod(method_info m) {
    int codesize;
    byte bc[];
    Instruction i;

    // Rebuild instruction sequence
    m.cfg.reconstructInstructions();

    // relabel instructions and get size of code array
    codesize = relabel(m.instructions);

    // construct a new array for the byte-code
    bc = new byte[codesize];

    // then recompile the instructions into byte-code
    i = m.instructions;
    codesize = 0;
    while (i != null) {
      codesize = i.compile(bc, codesize);
      i = i.next;
    }
    if (codesize != bc.length) {
      logger.warn("code size doesn't match array length!");
    }

    return bc;
  }

  /**
   * Inversive to parse, this method calls unparseMethod for each method, storing the resulting bytecode in the method's code
   * attribute, and recomputing offsets for exception handlers.
   *
   * @see ClassFile#unparseMethod
   */
  void unparse() {
    int i, j;
    Code_attribute ca;
    byte bc[];
    method_info mi;
    exception_table_entry e;

    for (i = 0; i < methods_count; i++) {
      mi = methods[i];
      // locate code attribute
      ca = mi.locate_code_attribute();
      if (ca == null) {
        continue;
      }
      bc = unparseMethod(mi);
      if (bc == null) {
        logger.debug("Recompile of " + mi.toName(constant_pool) + " failed!");
      } else {
        ca.code_length = bc.length;
        ca.code = bc;
        // also recompile exception table
        for (j = 0; j < ca.exception_table_length; j++) {
          e = ca.exception_table[j];
          e.start_pc = (e.start_inst.label);
          if (e.end_inst != null) {
            e.end_pc = (e.end_inst.label);
          } else {
            e.end_pc = (int) (ca.code_length);
          }
          e.handler_pc = (e.handler_inst.label);
        }
      }
    }
  }

  /**
   * Static utility method to parse the given method descriptor string.
   *
   * @param s
   *          descriptor string.
   * @return return type of method.
   * @see ClassFile#parseDesc
   * @see ClassFile#parseMethodDesc_params
   */
  static String parseMethodDesc_return(String s) {
    int j;
    j = s.lastIndexOf(')');
    if (j >= 0) {
      return parseDesc(s.substring(j + 1), ",");
    }
    return parseDesc(s, ",");
  }

  /**
   * Static utility method to parse the given method descriptor string.
   *
   * @param s
   *          descriptor string.
   * @return comma-separated ordered list of parameter types
   * @see ClassFile#parseDesc
   * @see ClassFile#parseMethodDesc_return
   */
  static String parseMethodDesc_params(String s) {
    int i, j;
    i = s.indexOf('(');
    if (i >= 0) {
      j = s.indexOf(')', i + 1);
      if (j >= 0) {
        return parseDesc(s.substring(i + 1, j), ",");
      }
    }
    return "<parse error>";
  }

  /**
   * Static utility method to parse the given method descriptor string.
   *
   * @param desc
   *          descriptor string.
   * @param sep
   *          String to use as a separator between types.
   * @return String of types parsed.
   * @see ClassFile#parseDesc
   * @see ClassFile#parseMethodDesc_return
   */
  static String parseDesc(String desc, String sep) {
    String params = "", param;
    char c;
    int i, len, arraylevel = 0;
    boolean didone = false;

    len = desc.length();
    for (i = 0; i < len; i++) {
      c = desc.charAt(i);
      if (c == DESC_BYTE.charAt(0)) {
        param = "byte";
      } else if (c == DESC_CHAR.charAt(0)) {
        param = "char";
      } else if (c == DESC_DOUBLE.charAt(0)) {
        param = "double";
      } else if (c == DESC_FLOAT.charAt(0)) {
        param = "float";
      } else if (c == DESC_INT.charAt(0)) {
        param = "int";
      } else if (c == DESC_LONG.charAt(0)) {
        param = "long";
      } else if (c == DESC_SHORT.charAt(0)) {
        param = "short";
      } else if (c == DESC_BOOLEAN.charAt(0)) {
        param = "boolean";
      } else if (c == DESC_VOID.charAt(0)) {
        param = "void";
      } else if (c == DESC_ARRAY.charAt(0)) {
        arraylevel++;
        continue;
      } else if (c == DESC_OBJECT.charAt(0)) {
        int j;
        j = desc.indexOf(';', i + 1);
        if (j < 0) {
          logger.warn("Parse error -- can't find a ; in " + desc.substring(i + 1));
          param = "<error>";
        } else {
          if (j - i > 10 && desc.substring(i + 1, i + 11).compareTo("java/lang/") == 0) {
            i = i + 10;
          }
          param = desc.substring(i + 1, j);
          // replace '/'s with '.'s
          param = param.replace('/', '.');
          i = j;
        }
      } else {
        param = "???";
      }
      if (didone) {
        params = params + sep;
      }
      params = params + param;
      while (arraylevel > 0) {
        params = params + "[]";
        arraylevel--;
      }
      didone = true;
    }
    return params;
  }

  /**
   * Locates a method by name.
   *
   * @param s
   *          name of method.
   * @return method_info object representing method, or <i>null</i> if not found.
   * @see method_info#toName
   */
  method_info findMethod(String s) {
    method_info m;
    int i;

    for (i = 0; i < methods_count; i++) {
      m = methods[i];
      if (s.equals(m.toName(constant_pool))) {
        return m;
      }
    }
    return null;
  }

  /**
   * Displays a the prototypes for all the methods defined in this ClassFile.
   *
   * @see ClassFile#methods
   * @see ClassFile#methods_count
   * @see method_info#prototype
   */
  void listMethods() {
    int i;

    for (i = 0; i < methods_count; i++) {
      logger.debug("" + methods[i].prototype(constant_pool));
    }
  }

  /**
   * Displays the entire constant pool.
   *
   * @see ClassFile#constant_pool
   * @see ClassFile#constant_pool_count
   * @see cp_info#toString
   */
  void listConstantPool() {
    cp_info c;
    int i;

    // note that we start at 1 in the constant pool
    for (i = 1; i < constant_pool_count; i++) {
      c = constant_pool[i];
      logger.debug("[" + i + "] " + c.typeName() + "=" + c.toString(constant_pool));
      if ((constant_pool[i]).tag == cp_info.CONSTANT_Long || (constant_pool[i]).tag == cp_info.CONSTANT_Double) {
        // must skip an entry after a long or double constant
        i++;
      }
    }
  }

  /**
   * Displays the list of fields defined in this ClassFile, including any static initializers (constants).
   *
   * @see ClassFile#fields
   * @see ClassFile#fields_count
   * @see field_info#prototype
   * @see ConstantValue_attribute
   */
  void listFields() {
    field_info fi;
    ConstantValue_attribute cva;
    CONSTANT_Utf8_info cm;
    int i, j;

    for (i = 0; i < fields_count; i++) {
      fi = fields[i];
      logger.debug("" + fi.prototype(constant_pool));
      // see if has a constant value attribute
      for (j = 0; j < fi.attributes_count; j++) {
        cm = (CONSTANT_Utf8_info) (constant_pool[fi.attributes[j].attribute_name]);
        if (cm.convert().compareTo(attribute_info.ConstantValue) == 0) {
          cva = (ConstantValue_attribute) (fi.attributes[j]);
          // dm = (CONSTANT_Utf8_info)(constant_pool[cva.constantvalue_index]);
          logger.debug(" = " + constant_pool[cva.constantvalue_index].toString(constant_pool));
          break;
        }
      }
      logger.debug(";");
    }
  }

  /**
   * Moves a method to a different index in the methods array.
   *
   * @param m
   *          name of method to move.
   * @param pos
   *          desired index.
   * @see ClassFile#methods
   */
  void moveMethod(String m, int pos) {
    int i, j;
    method_info mthd;
    logger.debug("Moving " + m + " to position " + pos + " of " + methods_count);

    for (i = 0; i < methods_count; i++) {
      if (m.compareTo(methods[i].toName(constant_pool)) == 0) {
        mthd = methods[i];
        if (i > pos) {
          for (j = i; j > pos && j > 0; j--) {
            methods[j] = methods[j - 1];
          }
          methods[pos] = mthd;
        } else if (i < pos) {
          for (j = i; j < pos && j < methods_count - 1; j++) {
            methods[j] = methods[j + 1];
          }
          methods[pos] = mthd;
        }
        return;
      }
    }
  }

  /**
   * Answers whether this class is an immediate descendant (as subclass or as an implementation of an interface) of the given
   * class.
   *
   * @param cf
   *          ClassFile of supposed parent.
   * @return <i>true</i> if it is a parent, <i>false</i> otherwise.
   * @see ClassFile#descendsFrom(String)
   */
  boolean descendsFrom(ClassFile cf) {
    return descendsFrom(cf.toString());
  }

  /**
   * Answers whether this class is an immediate descendant (as subclass or as an implementation of an interface) of the given
   * class.
   *
   * @param cname
   *          name of supposed parent.
   * @return <i>true</i> if it is a parent, <i>false</i> otherwise.
   * @see ClassFile#descendsFrom(ClassFile)
   */
  boolean descendsFrom(String cname) {
    cp_info cf;
    int i;
    cf = constant_pool[super_class];
    if (cf.toString(constant_pool).compareTo(cname) == 0) {
      return true;
    }
    for (i = 0; i < interfaces_count; i++) {
      cf = constant_pool[interfaces[i]];
      if (cf.toString(constant_pool).compareTo(cname) == 0) {
        return true;
      }
    }
    return false;
  }

  /**
   * Answers whether this class can have subclasses outside its package.
   *
   * @return <i>true</i> if it cannot, <i>false</i> if it might.
   */
  boolean isSterile() {
    if ((access_flags & ACC_PUBLIC) != 0 && (access_flags & ACC_FINAL) == 0) {
      return false;
    }
    return true;
  }

  /**
   * Given the name of a class --- possibly with <tt>.class</tt> after it, this answers whether the class might refer to this
   * ClassFile object.
   *
   * @return <i>true</i> if it does, <i>false</i> if it doesn't.
   */
  boolean sameClass(String cfn) {
    String s = cfn;
    int i = s.lastIndexOf(".class");
    if (i > 0) { // has .class after it
      s = s.substring(0, i); // cut off the .class
    }
    if (s.compareTo(toString()) == 0) {
      return true;
    }
    return false;
  }

  /**
   * Returns the name of a specific field in the field array.
   *
   * @param i
   *          index of field in field array.
   * @return name of field.
   */
  String fieldName(int i) {
    return fields[i].toName(constant_pool);
  }

  /*
   * DEPRECATED // Locates the given classfile, and extracts it from the list. // It cannot be the first one in the list, and
   * this returns null // or the classfile. static ClassFile removeClassFile(ClassFile cfhead,String cfn) { ClassFile
   * cf,cfprev; cf = cfhead; cfprev = null; while (cf!=null) { if (cf.sameClass(cfn)) { if (cfprev==null) return null; //
   * this shouldn't happen cfprev.next = cf.next; cf.next = null; return cf; } cfprev = cf; cf = cf.next; } return null; }
   *
   * // returns true if this class contains any references to the given // cuClass.cuName, which is of type cuDesc. Searches
   * for methods if // ismethod is true, fields otherwise. boolean refersTo(boolean ismethod,CONSTANT_Utf8_info cuClass,
   * CONSTANT_Utf8_info cuName,CONSTANT_Utf8_info cuDesc) { int i; CONSTANT_Utf8_info cu; // note that we start at 1 in the
   * constant pool if (ismethod) { for (i=1;i<constant_pool_count;i++) { if
   * ((constant_pool[i]).tag==cp_info.CONSTANT_Methodref) { CONSTANT_Methodref_info cf =
   * (CONSTANT_Methodref_info)(constant_pool[i]); CONSTANT_Class_info cc = (CONSTANT_Class_info)
   * (constant_pool[cf.class_index]); if (cuClass.equals((CONSTANT_Utf8_info) (constant_pool[cc.name_index]))) {
   * CONSTANT_NameAndType_info cn = (CONSTANT_NameAndType_info) (constant_pool[cf.name_and_type_index]); if
   * (cuName.equals((CONSTANT_Utf8_info) (constant_pool[cn.name_index])) && cuDesc.equals((CONSTANT_Utf8_info)
   * (constant_pool[cn.descriptor_index]))) return true; } } else if ((constant_pool[i]).tag==
   * cp_info.CONSTANT_InterfaceMethodref) { CONSTANT_InterfaceMethodref_info cf =
   * (CONSTANT_InterfaceMethodref_info)(constant_pool[i]); CONSTANT_Class_info cc = (CONSTANT_Class_info)
   * (constant_pool[cf.class_index]); if (cuClass.equals((CONSTANT_Utf8_info) (constant_pool[cc.name_index]))) {
   * CONSTANT_NameAndType_info cn = (CONSTANT_NameAndType_info) (constant_pool[cf.name_and_type_index]); if
   * (cuName.equals((CONSTANT_Utf8_info) (constant_pool[cn.name_index])) && cuDesc.equals((CONSTANT_Utf8_info)
   * (constant_pool[cn.descriptor_index]))) return true; } } else if ((constant_pool[i]).tag==cp_info.CONSTANT_Long ||
   * (constant_pool[i]).tag==cp_info.CONSTANT_Double) { // must skip an entry after a long or double constant i++; } } } else
   * { for (i=1;i<constant_pool_count;i++) { if ((constant_pool[i]).tag==cp_info.CONSTANT_Fieldref) { CONSTANT_Fieldref_info
   * cf = (CONSTANT_Fieldref_info)(constant_pool[i]); CONSTANT_Class_info cc = (CONSTANT_Class_info)
   * (constant_pool[cf.class_index]); if (cuClass.equals((CONSTANT_Utf8_info) (constant_pool[cc.name_index]))) {
   * CONSTANT_NameAndType_info cn = (CONSTANT_NameAndType_info) (constant_pool[cf.name_and_type_index]); if
   * (cuName.equals((CONSTANT_Utf8_info) (constant_pool[cn.name_index])) && cuDesc.equals((CONSTANT_Utf8_info)
   * (constant_pool[cn.descriptor_index]))) return true; } } else if ((constant_pool[i]).tag==cp_info.CONSTANT_Long ||
   * (constant_pool[i]).tag==cp_info.CONSTANT_Double) { // must skip an entry after a long or double constant i++; } } }
   * return false; }
   *
   * // produces a sorted array of constant pool indices, one for each Utf8 entry used // by any field short[]
   * forbiddenFields() { short fFields[] = new short[fields_count]; for (int i=0;i<fields_count;i++) { fFields[i] =
   * fields[i].name_index; } // now to sort the array return sortShorts(fFields); }
   *
   * // sorts an array of shorts using selection sort. It's assumed no valid // entry is 0. static short[] sortShorts(short
   * a[]) { int i,largest; short s; for(largest = a.length-1;largest>=1;largest--) { for (i=0;i<largest;i++) { if
   * (a[i]>a[largest]) { s = a[i]; a[i] = a[largest]; a[largest] = s; } } } return a; }
   *
   * // Given a new constant pool, and a list of redirections // (new index = redirect[old index]), this changes all constant
   * // pool entries, and installs the new constant pool of size size void changeConstantPool(short redirect[],cp_info
   * newCP[],short size) { Debig d = new Debig(this); d.redirectCPRefs(redirect); constant_pool = newCP; constant_pool_count
   * = size; }
   *
   * // the constant pool is typically a few hundred entries in size, and so // is just a bit too big to make use of
   * insertion/selection sort. // However, the variable size of the entries makes using a heapsort // or quicksort rather
   * cumbersome, so since it is quite close to the // limits of efficient insertion/selection sort, we'll use that anyway.
   * void sortConstantPool() { cp_info newcp[] = new cp_info[constant_pool_count]; short redirect[] = new
   * short[constant_pool_count]; newcp[0] = constant_pool[0]; // the 0-entry stays put redirect[0] = (short)0; int
   * smallest,j; for (int i=1;i<constant_pool_count;i++) redirect[i] = (short)0; for (int i=1;i<constant_pool_count;i++) {
   * for (smallest = 1;smallest<constant_pool_count;smallest++) if (redirect[smallest]==(short)0) break;
   * //logger.debug(" smallest = " + smallest); j = (constant_pool[smallest].tag==cp_info.CONSTANT_Double ||
   * constant_pool[smallest].tag==cp_info.CONSTANT_Long) ? smallest+2 : smallest+1; for (;j<constant_pool_count;j++) { if
   * ((redirect[j]==(short)0) && constant_pool[j]. compareTo(constant_pool,constant_pool[smallest],constant_pool)<0) {
   * smallest = j; } if (constant_pool[j].tag==cp_info.CONSTANT_Double || constant_pool[j].tag==cp_info.CONSTANT_Long) j++; }
   * redirect[smallest] = (short)i; newcp[i] = constant_pool[smallest]; //logger.debug(" Smallest cp entry is [" + smallest +
   * "] = " + constant_pool[smallest] // + " -> " + i);
   *
   * if (constant_pool[smallest].tag==cp_info.CONSTANT_Double || constant_pool[smallest].tag==cp_info.CONSTANT_Long) {
   * redirect[++smallest] = (short)(++i); newcp[i] = constant_pool[smallest]; } } // constant pool is now sorted into newcp
   * changeConstantPool(redirect,newcp,constant_pool_count); logger.debug("Finished sorting constant pool"); }
   *
   * // just a wrapper for the debigulation, so we can elegantly allocate // a new debigulator, debigualte and then produce
   * some output void debigulate(boolean attribs,boolean privates) { Debig debigulator = new Debig(this);
   * debigulator.debigulate(attribs,privates); debigulator.setCF(null);
   *
   * inf.verboseReport(G.v().out); }
   */

}
