private static ClassMember readClassMember(ClassInvestigator ci, ClassInput in)
      throws IOException {

    ClassMember member = new ClassMember(ci, in.getU2(), in.getU2(), in.getU2());

    int attributeCount = in.getU2();
    if (attributeCount != 0) {
      member.attribute_info = new Attributes(attributeCount);
      for (int i = 0; i < attributeCount; i++)
        member.attribute_info.addEntry(new AttributeEntry(in));
    }

    return member;
  }
  private static ConstantPoolEntry getConstant(ClassInput in) throws IOException {

    ConstantPoolEntry item;
    int tag;
    tag = in.readUnsignedByte();

    switch (tag) {
      case VMDescriptor.CONSTANT_Class:
      case VMDescriptor.CONSTANT_String:
        item = new CONSTANT_Index_info(tag, in.getU2(), 0);
        break;

      case VMDescriptor.CONSTANT_NameAndType:
      case VMDescriptor.CONSTANT_Fieldref:
      case VMDescriptor.CONSTANT_Methodref:
      case VMDescriptor.CONSTANT_InterfaceMethodref:
        item = new CONSTANT_Index_info(tag, in.getU2(), in.getU2());
        break;

      case VMDescriptor.CONSTANT_Integer:
        item = new CONSTANT_Integer_info(in.getU4());
        break;

      case VMDescriptor.CONSTANT_Float:
        item = new CONSTANT_Float_info(in.readFloat());
        break;

      case VMDescriptor.CONSTANT_Long:
        item = new CONSTANT_Long_info(in.readLong());
        break;

      case VMDescriptor.CONSTANT_Double:
        item = new CONSTANT_Double_info(in.readDouble());
        break;

      case VMDescriptor.CONSTANT_Utf8:
        item = new CONSTANT_Utf8_info(in.readUTF());
        break;
      default:
        throw new ClassFormatError();
    }

    return item;
  }
  private void processCodeAttribute(ClassMember member, AttributeEntry ae) throws IOException {

    ClassInput ci = new ClassInput(new java.io.ByteArrayInputStream(ae.infoIn));

    DataInputUtil.skipFully(ci, 4); // puts us at code_length
    int len = ci.getU4();
    DataInputUtil.skipFully(ci, len); // puts us at exception_table_length
    int count = ci.getU2();
    if (count != 0) DataInputUtil.skipFully(ci, 8 * count);

    int nonAttrLength = 4 + 4 + len + 2 + (8 * count);

    // now at attributes

    count = ci.getU2();
    if (count == 0) return;

    int newCount = count;
    for (int i = 0; i < count; i++) {

      int nameIndex = ci.getU2();
      String name = nameIndexToString(nameIndex);
      if (name.equals("LineNumberTable") || name.equals("LocalVariableTable")) newCount--;
      else System.err.println("ERROR - Unknown code attribute " + name);

      len = ci.getU4();
      DataInputUtil.skipFully(ci, len);
    }

    if (newCount != 0) {
      System.err.println("ERROR - expecting all code attributes to be removed");
      System.exit(1);
    }

    // this is only coded for all attributes within a Code attribute being removed.

    byte[] newInfo = new byte[nonAttrLength + 2];
    System.arraycopy(ae.infoIn, 0, newInfo, 0, nonAttrLength);
    // last two bytes are left at 0 which means 0 attributes
    ae.infoIn = newInfo;
  }
  public static ClassInvestigator load(InputStream is) throws IOException {

    ClassInput classInput = new ClassInput(is);

    // Check the header
    int magic = classInput.getU4();
    int minor_version = classInput.getU2();
    int major_version = classInput.getU2();

    if (magic != VMDescriptor.JAVA_CLASS_FORMAT_MAGIC) throw new ClassFormatError();

    //	Read in the Constant Pool
    int constantPoolCount = classInput.getU2();

    ClassInvestigator ci = new ClassInvestigator(constantPoolCount);

    ci.minor_version = minor_version;
    ci.major_version = major_version;

    // Yes, index starts at 1, The '0'th constant pool entry
    // is reserved for the JVM and is not present in the class file.
    for (int i = 1; i < constantPoolCount; ) {
      ConstantPoolEntry item = ClassInvestigator.getConstant(classInput);
      i += ci.addEntry(item.getKey(), item);
    }

    // Read in access_flags and class indexes
    ci.access_flags = classInput.getU2();
    ci.this_class = classInput.getU2();
    ci.super_class = classInput.getU2();

    // interfaces is a simple int array
    int interfaceCount = classInput.getU2();
    if (interfaceCount != 0) {
      ci.interfaces = new int[interfaceCount];
      for (int i = 0; i < interfaceCount; i++) ci.interfaces[i] = classInput.getU2();
    }

    int fieldCount = classInput.getU2();
    if (fieldCount != 0) {
      ci.field_info = new MemberTable(fieldCount);
      for (int i = 0; i < fieldCount; i++) {
        ci.field_info.addEntry(readClassMember(ci, classInput));
      }
    }

    int methodCount = classInput.getU2();
    if (methodCount != 0) {
      ci.method_info = new MemberTable(methodCount);
      for (int i = 0; i < methodCount; i++) {
        ci.method_info.addEntry(readClassMember(ci, classInput));
      }
    }

    int attributeCount = classInput.getU2();
    if (attributeCount != 0) {
      ci.attribute_info = new Attributes(attributeCount);

      for (int i = 0; i < attributeCount; i++)
        ci.attribute_info.addEntry(new AttributeEntry(classInput));
    }
    return ci;
  }