/**
   * Parse the table part of either a {@code LocalVariableTable} or a {@code
   * LocalVariableTypeTable}.
   *
   * @param bytes {@code non-null;} bytes to parse, which should <i>only</i> contain the table data
   *     (no header)
   * @param pool {@code non-null;} constant pool to use
   * @param count {@code >= 0;} the number of entries
   * @param typeTable {@code true} iff this is for a type table
   * @return {@code non-null;} the constructed list
   */
  private LocalVariableList parseLocalVariables(
      ByteArray bytes, ConstantPool pool, ParseObserver observer, int count, boolean typeTable) {
    if (bytes.size() != (count * 10)) {
      // "+ 2" is for the count.
      throwBadLength((count * 10) + 2);
    }

    ByteArray.MyDataInputStream in = bytes.makeDataInputStream();
    LocalVariableList list = new LocalVariableList(count);

    try {
      for (int i = 0; i < count; i++) {
        int startPc = in.readUnsignedShort();
        int length = in.readUnsignedShort();
        int nameIdx = in.readUnsignedShort();
        int typeIdx = in.readUnsignedShort();
        int index = in.readUnsignedShort();
        CstString name = (CstString) pool.get(nameIdx);
        CstString type = (CstString) pool.get(typeIdx);
        CstString descriptor = null;
        CstString signature = null;

        if (typeTable) {
          signature = type;
        } else {
          descriptor = type;
        }

        list.set(i, startPc, length, name, descriptor, signature, index);

        if (observer != null) {
          observer.parsed(
              bytes,
              i * 10,
              10,
              Hex.u2(startPc)
                  + ".."
                  + Hex.u2(startPc + length)
                  + " "
                  + Hex.u2(index)
                  + " "
                  + name.toHuman()
                  + " "
                  + type.toHuman());
        }
      }
    } catch (IOException ex) {
      throw new RuntimeException("shouldn't happen", ex);
    }

    list.setImmutable();
    return list;
  }
  private BootstrapMethodList parseBootstrapMethods(
      ByteArray bytes, ConstantPool pool, ParseObserver observer, int count) {

    ByteArray.MyDataInputStream in = bytes.makeDataInputStream();
    BootstrapMethodList list = new BootstrapMethodList(count);

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

        int bootstrap_method_ref = in.readUnsignedShort();
        CstMethodHandle methodHandle = (CstMethodHandle) pool.get(bootstrap_method_ref);
        int num_bootstrap_arguments = in.readUnsignedShort();
        BootstrapArgumentList bootstrapArgumentList =
            new BootstrapArgumentList(num_bootstrap_arguments);

        for (int j = 0; j < num_bootstrap_arguments; ++j) {
          int bootstrap_argument_idx = in.readUnsignedShort();
          Constant constant = pool.get(bootstrap_argument_idx);
          bootstrapArgumentList.set(j, constant);
        }

        bootstrapArgumentList.setImmutable();
        list.set(i, new BootstrapMethodList.BootstrapMethod(methodHandle, bootstrapArgumentList));

        if (observer != null) {
          //                    observer.parsed(bytes, i * 10, 10, Hex.u2(startPc) +
          //                            ".." + Hex.u2(startPc + length) + " " +
          //                            Hex.u2(index) + " " + name.toHuman() + " " +
          //                            type.toHuman());
        }
      }
    } catch (IOException ex) {
      throw new RuntimeException("shouldn't happen", ex);
    }

    list.setImmutable();
    return list;
  }
  /** Parses an {@code InnerClasses} attribute. */
  private Attribute innerClasses(
      DirectClassFile cf, int offset, int length, ParseObserver observer) {
    if (length < 2) {
      return throwSeverelyTruncated();
    }

    ByteArray bytes = cf.getBytes();
    ConstantPool pool = cf.getConstantPool();
    int count = bytes.getUnsignedShort(offset); // number_of_classes

    if (observer != null) {
      observer.parsed(bytes, offset, 2, "number_of_classes: " + Hex.u2(count));
    }

    offset += 2;
    length -= 2;

    if (length != (count * 8)) {
      throwBadLength((count * 8) + 2);
    }

    InnerClassList list = new InnerClassList(count);

    for (int i = 0; i < count; i++) {
      int innerClassIdx = bytes.getUnsignedShort(offset);
      int outerClassIdx = bytes.getUnsignedShort(offset + 2);
      int nameIdx = bytes.getUnsignedShort(offset + 4);
      int accessFlags = bytes.getUnsignedShort(offset + 6);
      CstType innerClass = (CstType) pool.get(innerClassIdx);
      CstType outerClass = (CstType) pool.get0Ok(outerClassIdx);
      CstString name = (CstString) pool.get0Ok(nameIdx);
      list.set(i, innerClass, outerClass, name, accessFlags);
      if (observer != null) {
        observer.parsed(
            bytes, offset, 2, "inner_class: " + DirectClassFile.stringOrNone(innerClass));
        observer.parsed(
            bytes, offset + 2, 2, "  outer_class: " + DirectClassFile.stringOrNone(outerClass));
        observer.parsed(bytes, offset + 4, 2, "  name: " + DirectClassFile.stringOrNone(name));
        observer.parsed(
            bytes, offset + 6, 2, "  access_flags: " + AccessFlags.innerClassString(accessFlags));
      }
      offset += 8;
    }

    list.setImmutable();
    return new AttInnerClasses(list);
  }
  /** Parses a {@code SourceFile} attribute. */
  private Attribute sourceFile(DirectClassFile cf, int offset, int length, ParseObserver observer) {
    if (length != 2) {
      throwBadLength(2);
    }

    ByteArray bytes = cf.getBytes();
    ConstantPool pool = cf.getConstantPool();
    int idx = bytes.getUnsignedShort(offset);
    CstString cst = (CstString) pool.get(idx);
    Attribute result = new AttSourceFile(cst);

    if (observer != null) {
      observer.parsed(bytes, offset, 2, "source: " + cst);
    }

    return result;
  }
  /** Parses a {@code ConstantValue} attribute. */
  private Attribute constantValue(
      DirectClassFile cf, int offset, int length, ParseObserver observer) {
    if (length != 2) {
      return throwBadLength(2);
    }

    ByteArray bytes = cf.getBytes();
    ConstantPool pool = cf.getConstantPool();
    int idx = bytes.getUnsignedShort(offset);
    TypedConstant cst = (TypedConstant) pool.get(idx);
    Attribute result = new AttConstantValue(cst);

    if (observer != null) {
      observer.parsed(bytes, offset, 2, "value: " + cst);
    }

    return result;
  }
  /** Parses an {@code EnclosingMethod} attribute. */
  private Attribute enclosingMethod(
      DirectClassFile cf, int offset, int length, ParseObserver observer) {
    if (length != 4) {
      throwBadLength(4);
    }

    ByteArray bytes = cf.getBytes();
    ConstantPool pool = cf.getConstantPool();

    int idx = bytes.getUnsignedShort(offset);
    CstType type = (CstType) pool.get(idx);

    idx = bytes.getUnsignedShort(offset + 2);
    CstNat method = (CstNat) pool.get0Ok(idx);

    Attribute result = new AttEnclosingMethod(type, method);

    if (observer != null) {
      observer.parsed(bytes, offset, 2, "class: " + type);
      observer.parsed(bytes, offset + 2, 2, "method: " + DirectClassFile.stringOrNone(method));
    }

    return result;
  }