/** Read annotation entry from classfile. */
  private AnnotationInfo readAnnotation(final DataInputStream inp, Object[] constantPool)
      throws IOException {
    String annotationFieldDescriptor = readRefdString(inp, constantPool);
    String annotationClassName;
    if (annotationFieldDescriptor.charAt(0) == 'L'
        && annotationFieldDescriptor.charAt(annotationFieldDescriptor.length() - 1) == ';') {
      // Lcom/xyz/Annotation; -> com.xyz.Annotation
      annotationClassName =
          annotationFieldDescriptor
              .substring(1, annotationFieldDescriptor.length() - 1)
              .replace('/', '.');
    } else {
      // Should not happen
      annotationClassName = annotationFieldDescriptor;
    }

    AnnotationInfo annotation = new AnnotationInfo(classInfoLoader.get(annotationClassName));

    int numElementValuePairs = inp.readUnsignedShort();
    for (int i = 0; i < numElementValuePairs; i++) {
      String name = readRefdString(inp, constantPool);
      final Object value = readAnnotationElementValue(inp, constantPool);
      annotation.setAttribute(name, value);
    }

    return annotation;
  }
  /**
   * Read the full class information
   *
   * @return The object
   */
  public ClassInfo resolve() {
    if (loaded) {
      return this;
    }

    if (isArray()) {
      loaded = true;
      return this;
    }

    // Simple cases
    switch (name) {
      case "B":
        theClass = Byte.TYPE;
        break;
      case "C":
        theClass = Character.TYPE;
        break;
      case "D":
        theClass = Double.TYPE;
        break;
      case "F":
        theClass = Float.TYPE;
        break;
      case "I":
        theClass = Integer.TYPE;
        break;
      case "J":
        theClass = Long.TYPE;
        break;
      case "S":
        theClass = Short.TYPE;
        break;
      case "Z":
        theClass = Boolean.TYPE;
        break;
      default:
        final InputStream stream = classInfoLoader.getStream(name);
        if (stream != null) {
          try {
            readFromInputStream(stream);
          } catch (IOException e) {
            throw new RuntimeException(format("Could not read class %s", name), e);
          }
        } else {
          try {
            theClass = classInfoLoader.classLoader.loadClass(name);
          } catch (ClassNotFoundException e) {
            throw new RuntimeException(format("Could not find class %s", name), e);
          }
        }
    }

    loaded = true;
    return this;
  }
  public ClassInfo getComponentType() {
    if (!isArray()) return null;

    return classInfoLoader.get(name.substring(1));
  }
  /** Directly examine contents of classfile binary header. */
  private void readFromInputStream(final InputStream inputStream) throws IOException {
    DataInputStream inp = new DataInputStream(new BufferedInputStream(inputStream, 1024));

    // Magic
    if (inp.readInt() != 0xCAFEBABE) {
      // Not classfile
      throw new IOException("Not a class file");
    }

    // Minor version
    inp.readUnsignedShort();
    // Major version
    inp.readUnsignedShort();

    // Constant pool count (1-indexed, zeroth entry not used)
    int cpCount = inp.readUnsignedShort();
    // Constant pool
    Object[] constantPool = new Object[cpCount];
    for (int i = 1; i < cpCount; ++i) {
      final int tag = inp.readUnsignedByte();
      switch (tag) {
        case 1: // Modified UTF8
          constantPool[i] = inp.readUTF();
          break;
        case 3: // int
          constantPool[i] = inp.readInt();
          break;
        case 4: // float
          constantPool[i] = inp.readFloat();
          break;
        case 5: // long
          constantPool[i] = inp.readLong();
          i++;
          break;
        case 6: // double
          constantPool[i] = inp.readDouble();
          i++; // double slot
          break;
        case 7: // Class
        case 8: // String
          // Forward or backward reference a Modified UTF8 entry
          constantPool[i] = inp.readUnsignedShort();
          break;
        case 9: // field ref
        case 10: // method ref
        case 11: // interface ref
        case 12: // name and type
          inp.skipBytes(4); // two shorts
          break;
        case 15: // method handle
          inp.skipBytes(3);
          break;
        case 16: // method type
          inp.skipBytes(2);
          break;
        case 18: // invoke dynamic
          inp.skipBytes(4);
          break;
        default:
          throw new ClassFormatError("Unkown tag value for constant pool entry: " + tag);
      }
    }

    // Access flags
    int flags = inp.readUnsignedShort();
    isInterface = (flags & 0x0200) != 0;

    // This class name, with slashes replaced with dots
    String name = readRefdString(inp, constantPool).replace('/', '.');
    if (this.name == null) {
      this.name = name;
    } else {
      if (!this.name.equals(name))
        throw new IllegalStateException(
            format("Class name %s and %s do not match", name, this.name));
    }
    // Superclass name, with slashes replaced with dots
    final String superclassName = toClassName(readRefdString(inp, constantPool));
    superclass = superclassName == null ? null : classInfoLoader.get(superclassName);

    // Interfaces
    int interfaceCount = inp.readUnsignedShort();
    for (int i = 0; i < interfaceCount; i++) {
      interfaces.add(classInfoLoader.get(toClassName(readRefdString(inp, constantPool))));
    }

    // Fields
    int fieldCount = inp.readUnsignedShort();
    for (int i = 0; i < fieldCount; i++) {
      inp.skipBytes(2); // access_flags
      final String fieldName = readRefdString(inp, constantPool); // name_index,

      String fieldDescriptor = readRefdString(inp, constantPool); // name_index,
      if (fieldDescriptor.startsWith("L") && fieldDescriptor.endsWith(";")) {
        fieldDescriptor =
            fieldDescriptor.substring(1, fieldDescriptor.length() - 1).replace("/", ".");
      }

      final FieldInfo fieldInfo = new FieldInfo(fieldName, classInfoLoader.get(fieldDescriptor));
      fields.put(fieldName, fieldInfo);

      int attributesCount = inp.readUnsignedShort();
      for (int j = 0; j < attributesCount; j++) {
        String attributeName = readRefdString(inp, constantPool);
        int attributeLength = inp.readInt();
        if ("RuntimeVisibleAnnotations".equals(attributeName)) {
          int annotationCount = inp.readUnsignedShort();
          for (int m = 0; m < annotationCount; m++) {
            AnnotationInfo annotation = readAnnotation(inp, constantPool);
            fieldInfo.annotations.put(annotation.annotationClass.getName(), annotation);
          }
        } else {
          inp.skipBytes(attributeLength);
        }
      }
    }

    // Methods
    int methodCount = inp.readUnsignedShort();
    for (int i = 0; i < methodCount; i++) {
      inp.skipBytes(6); // access_flags, name_index, descriptor_index
      int attributesCount = inp.readUnsignedShort();
      for (int j = 0; j < attributesCount; j++) {
        inp.skipBytes(2); // attribute_name_index
        int attributeLength = inp.readInt();
        inp.skipBytes(attributeLength);
      }
    }

    // Attributes (including class annotations)
    int attributesCount = inp.readUnsignedShort();
    for (int i = 0; i < attributesCount; i++) {
      String attributeName = readRefdString(inp, constantPool);
      int attributeLength = inp.readInt();
      if ("RuntimeVisibleAnnotations".equals(attributeName)) {
        int annotationCount = inp.readUnsignedShort();
        for (int m = 0; m < annotationCount; m++) {
          AnnotationInfo annotation = readAnnotation(inp, constantPool);
          annotations.put(annotation.annotationClass.getName(), annotation);
        }
      } else {
        inp.skipBytes(attributeLength);
      }
    }
  }