Пример #1
0
 void uncaughtType(Type t, Position pos) throws SemanticException {
   SemanticException e =
       new SemanticException(
           codeType
               + " cannot throw a \""
               + t
               + "\"; the exception must either be caught or declared to be thrown.",
           pos);
   Map<String, Object> map = CollectionFactory.newHashMap();
   map.put(CodedErrorInfo.ERROR_CODE_KEY, CodedErrorInfo.ERROR_CODE_SURROUND_THROW);
   map.put("TYPE", t.toString());
   e.setAttributes(map);
   throw e;
 }
Пример #2
0
public abstract class X10Doc implements Doc {
  private String rawComment;
  private String comment;
  X10Tag[] firstSentenceTags, inlineTags;
  List<Tag> blockTags = new ArrayList<Tag>();
  List<Tag> paramTags = new ArrayList<Tag>();

  public void processComment(String rawComment) {
    this.rawComment = rawComment;
    procComment(rawCommentToText(rawComment));
  }

  /**
   * Initializes fields comment, inlineTags of the object
   *
   * @param commentText the processed comment text
   */
  private void procComment(String commentText) {
    // initialize inlineTags
    ArrayList<Tag> result = new ArrayList<Tag>();
    String noInlineTags = replaceAtSigns(commentText);

    /*
     * Pattern p = Pattern.compile("\\{\\s*@[^}]*\\}"); // matches inline
     * tags // Pattern p =
     * Pattern.compile("\\{\\s*@([^\\s\\}]+)\\s*([^\\}]*)\\s*}"); // matches
     * inline tags Matcher m = p.matcher(commentText); int start = 0, end =
     * 0; // create an array of tag objects of kind "Text" and "@link"; as
     * explained in the // doclet API, for a comment // This is a {@link Doc
     * commentlabel} example. // create an array of Tag objects: // *
     * tags[0] is a Tag with name "Text" and text consisting of "This is a "
     * // * tags[1] is a SeeTag with name "@link", and label "commentlabel"
     * // * tags[2] is a Tag with name "Text" and text consisting of
     * " example." while (m.find()) { end = m.start(); String linkText =
     * m.group(); // System.out.print("String = \"" +
     * commentText.substring(start, end)); //
     * System.out.println("\"; linkText = \"" + linkText + "\""); //
     * result.add(new X10Tag("Text", commentText.substring(start, end),
     * this)); result.add(X10Tag.processInlineTag(linkText, this)); //int
     * index = commentText.indexOf(linkText); //commentText =
     * commentText.substring(0, index) + commentText.substring(index +
     * linkText.length()); // result.add(new X10SeeTag(true, linkText,
     * this)); // "true" signifies an @link tag, as opposed to an @see tag
     * start = m.end(); }
     */
    if (!commentText.startsWith("@")) { // make sure that there is a
      // beginning paragraph
      // initialize comment
      int blockTagStart = noInlineTags.indexOf("@"); // start of block
      // tags within
      // comment
      blockTagStart = (blockTagStart == -1) ? commentText.length() : blockTagStart;
      this.comment = commentText.substring(0, blockTagStart).trim();
      if (!comment.equals("")) {
        result.addAll(createInlineTags(comment, this));
      }

      // }
      // add constraints, if any
      // String decl = declString();
      // if (decl != null) {
      // result.add(new X10Tag(decl, this));
      // }

      // initialize firstSentenceTags
      BreakIterator b = BreakIterator.getSentenceInstance();
      b.setText(comment);
      int start = 0;
      int end = 0;
      start = b.first();
      end = b.next();
      String firstSentence = ((start <= end) ? comment.substring(start, end).trim() : "");
      // System.out.println("X10Doc.initializeFields(): firstSentence = \""
      // + firstSentence + "\"");
      firstSentenceTags = createInlineTags(firstSentence, this).toArray(new X10Tag[0]);

    } else {
      firstSentenceTags = new X10Tag[0];
    }

    inlineTags = result.toArray(new X10Tag[0]);

    // TODO: creating Tag objects for block tags and storing them in a field
    // of this object
    Pattern blockTagPattern = Pattern.compile("\\s*@[^@]*");
    Matcher blockTagMatcher = blockTagPattern.matcher(noInlineTags);
    while (blockTagMatcher.find()) {
      String tagText = blockTagMatcher.group();
      int start = blockTagMatcher.start();
      processBlockTag(commentText.substring(start, start + tagText.length()));
    }
  }

  // replaces @ signs of inline tags in the comment with another char ("A")
  private String replaceAtSigns(String comment) {
    String result = comment;
    Pattern p = Pattern.compile("\\{\\s*@[^}]*\\}"); // matches inline tags
    Matcher m = p.matcher(comment);
    while (m.find()) {
      String linkText = m.group();
      int atIndex = linkText.indexOf("@") + m.start();
      result = result.substring(0, atIndex) + "A" + result.substring(atIndex + 1);
    }
    return result;
  }

  private void processBlockTag(String tagText) {
    String kind = null;
    String text = null;
    Pattern p = Pattern.compile("@[^\\s]*");
    Matcher m = p.matcher(tagText);
    if (m.find()) {
      kind = m.group().substring(1);
      text = tagText.substring(m.end()).trim();
    }
    if (kind.equals(X10Tag.PARAM)) {
      Pattern p1 = Pattern.compile("[^\\s]*");
      Matcher m1 = p1.matcher(text);
      if (m1.find()) {
        String name = m1.group();
        String paramComment = text.substring(m1.end()).trim();
        X10Tag[] inTags = createInlineTags(paramComment, this).toArray(new X10Tag[0]);
        X10ParamTag t = new X10ParamTag(false, name, inTags, paramComment, text, this);
        blockTags.add(t);
        paramTags.add(t);
      }

    } else if (kind.equals("see") || kind.equals("link") || kind.equals("linkplain")) {
      blockTags.add(new X10SeeTag("@" + kind, text, text, this));
    } else if (kind.equals(X10Tag.THROWS)) {
      blockTags.add(new X10ThrowsTag(kind, text, this));
    } else {
      blockTags.add(new X10Tag(kind, text, this));
    }
  }

  public X10Tag[] getX10Tags() {
    return new X10Tag[0];
  }

  public void addGuardTags(List<X10Tag> list) {
    StringBuilder sb = new StringBuilder();
    Tag[] tags = tags(X10Tag.GUARD);
    if (tags.length > 0) {
      sb.append("<DL><DT><B>Guard:</B>");
      for (Tag tag : tags) {
        sb.append("<DD><CODE>");
        String code = tag.text();
        String tokens[] = code.split("\\s");

        if (tokens.length > 1) {
          sb.append(tokens[0]);
          sb.append("</CODE> - ");
          sb.append(code.replace(tokens[0], "").trim());
        } else {
          sb.append("</CODE>");
        }
      }
      sb.append("</DL><P>");
    }

    list.addAll(createInlineTags(sb.toString(), this));
  }

  // used to add comment lines displaying X10 class declarations, specifically
  // the class constraints in
  // the declarations; the declaration string argument contains @link tags to
  // entities in the constraint
  // TODO: update this.rawComment, this.comment appropriately
  public void addDeclTag(String declString) {
    if (declString == null) {
      return;
    }
    X10Tag[] declTags = createInlineTags(declString, this).toArray(new X10Tag[0]);

    // firstSentenceTags = concat(declTags, firstSentenceTags);

    // place declaration before the first sentence of the existing comment
    // but do not add
    // it to firstSentenceTags; this ensures that the declaration string is
    // not displayed
    // in tables such as the "Class Summary" table
    inlineTags = concat(declTags, inlineTags);

    // int len = inlineTags.length + declTags.length;
    // X10Tag[] newTags = new X10Tag[len];
    // int i;
    // for (i = 0; i < inlineTags.length; i++) {
    // newTags[i] = inlineTags[i];
    // }
    // for (int j = 0 ; i < len; i++, j++) {
    // newTags[i] = declTags[j];
    // }
    // inlineTags = newTags;
  }

  public static X10Tag[] concat(X10Tag[] orig, X10Tag[] newTags) {
    int len = orig.length + newTags.length;
    X10Tag[] result = new X10Tag[len];
    int i;
    for (i = 0; i < orig.length; i++) {
      result[i] = orig[i];
    }
    for (int j = 0; i < len; i++, j++) {
      result[i] = newTags[j];
    }
    return result;
  }

  // method to be overriden in sub-classes such as X10FieldDoc, X10MethodDoc,
  // X10ConstructorDoc,
  // X10TypeDefDoc, etc; the return string may be a description of the field
  // type or declaration of
  // the method etc., and is expected to be added to the comments of the
  // associated Doc object, by
  // a method such as X10Doc.addDeclTag(...)
  public String declString() {
    return "";
  }

  // used to add a comment line displaying X10 type of a field,
  // method/constructor return value,
  // and method/constructor parameter, specifically the constraints in these
  // X10 types; argument str
  // cannot have any inline (or block) tags
  // TODO: will not work if comment contains block tags
  public void addNewLineToComment(String str) {
    if (str == null || str.equals("")) {
      return;
    }
    String newLine = "<PRE>\n</PRE>" + str;
    this.rawComment = this.rawComment + newLine;
    processComment(comment + newLine);
  }

  /**
   * Creates an array of Tag objects of kind "Text" and "@link" for the specified string.
   *
   * @param text processed comment text which may contain inline tags, but not block tags
   */
  public static List<X10Tag> createInlineTags(String text, X10Doc holder) {
    ArrayList<X10Tag> result = new ArrayList<X10Tag>();
    Pattern p = Pattern.compile("\\{\\s*@[^}]*\\}");
    Matcher m = p.matcher(text);
    int start = 0, end = 0;
    while (m.find()) {
      end = m.start();
      String linkText = m.group();
      // System.out.print("String = \"" + text.substring(start, end));
      // System.out.println("\"; linkText = \"" + linkText + "\"");
      result.add(new X10Tag("Text", text.substring(start, end), holder));
      result.add(X10Tag.processInlineTag(linkText, holder));
      // result.add(new X10Tag("Text", text.substring(start, end), this));
      // result.add(new X10SeeTag(true, linkText, this));
      // "true" signifies an @link tag, as opposed to an @see tag
      start = m.end();
    }
    result.add(new X10Tag("Text", text.substring(start, text.length()), holder));

    // System.out.println("Rest = \"" + text.substring(start, text.length())
    // + "\"");
    if (result.isEmpty()) {
      result.add(new X10Tag("Text", text, holder));
    }
    return result;
  }

  public String commentText() {
    if (X10RootDoc.printSwitch) System.out.println("Doc.commentText() called for " + name());
    return comment;
  }

  public Tag[] firstSentenceTags() {
    if (X10RootDoc.printSwitch) System.out.println("Doc.firstSentenceTags() called for " + name());
    return firstSentenceTags;
  }

  public String getRawCommentText() {
    if (X10RootDoc.printSwitch) System.out.println("Doc.getRawCommentText() called for " + name());
    new Exception().printStackTrace();
    return rawComment;
  }

  /**
   * Return comment as an array of tags. Includes inline tags (i.e. {@link reference} tags) but not
   * block tags.
   */
  public Tag[] inlineTags() {
    if (X10RootDoc.printSwitch) System.out.println("Doc.inlineTags() called for " + name());
    return inlineTags;
  }

  public boolean isAnnotationType() {
    return false;
  }

  public boolean isAnnotationTypeElement() {
    return false;
  }

  public boolean isClass() {
    return false;
  }

  public boolean isConstructor() {
    return false;
  }

  public boolean isEnum() {
    return false;
  }

  public boolean isEnumConstant() {
    return false;
  }

  public boolean isError() {
    return false;
  }

  public boolean isException() {
    return false;
  }

  public boolean isField() {
    return false;
  }

  public boolean isIncluded() {
    return false;
  }

  public boolean isInterface() {
    return false;
  }

  public boolean isMethod() {
    return false;
  }

  public boolean isOrdinaryClass() {
    return false;
  }

  public int compareTo(Object other) {
    return name().compareTo(((X10Doc) other).name());
  }

  public String name() {
    if (X10RootDoc.printSwitch) System.out.println("Doc.name() called for " + this);
    return "Dummy Doc Name";
  }

  public SourcePosition position() {
    if (X10RootDoc.printSwitch) System.out.println("Doc.position() called for " + name());
    return null;
  }

  /** Return the see also tags in this Doc item. */
  public SeeTag[] seeTags() {
    if (X10RootDoc.printSwitch) System.out.println("Doc.seeTags() called for " + name());

    Tag[] tags = tags(X10Tag.SEE);
    SeeTag[] newTags = new SeeTag[tags.length];
    System.arraycopy(tags, 0, newTags, 0, tags.length);
    return newTags;
  }

  public void setRawCommentText(String arg0) {
    if (X10RootDoc.printSwitch)
      System.out.println("Doc.setRawCommentText(String) called for " + name());
    this.rawComment = arg0;
    processComment(rawComment);
  }

  /** Return all tags in this Doc item. */
  public Tag[] tags() {
    if (X10RootDoc.printSwitch) System.out.println("Doc.tags() called for " + name());
    Tag[] result = new Tag[blockTags.size() + inlineTags.length];
    if (blockTags.size() > 0)
      System.arraycopy(blockTags.toArray(new Tag[0]), 0, result, 0, blockTags.size());
    if (inlineTags.length > 0)
      System.arraycopy(inlineTags, 0, result, blockTags.size(), inlineTags.length);
    return result;
    // return inlineTags();
  }

  /** Return tags of the specified kind in this Doc item. */
  public Tag[] tags(String kind) {
    // TODO Auto-generated method stub
    if (X10RootDoc.printSwitch) System.out.println("Doc.tags(" + kind + ") called for " + name());
    if (kind.equals("Text")) {
      return inlineTags();
    } else return getTags(kind);
  }

  private Tag[] getTags(String kind) {
    List<Tag> result = new ArrayList<Tag>();
    for (Tag t : blockTags) {
      if (t.kind().equals(kind)) {
        result.add(t);
      }
    }
    return result.toArray(new X10Tag[0]);
  }

  public static boolean isIncluded(String accessModFilter, ProgramElementDoc pd) {
    boolean isPublic = pd.isPublic();
    if (accessModFilter.equals("-public")) {
      return isPublic;
    }
    boolean isProtected = pd.isProtected();
    if (accessModFilter.equals("-protected")) {
      return (isPublic || isProtected);
    }
    boolean isPackage = pd.isPackagePrivate();
    if (accessModFilter.equals("-package")) {
      return (isPublic || isProtected || isPackage);
    }
    return true;
  }

  public static String rawCommentToText(String rawComment) {
    if (rawComment == null || rawComment.length() == 0) {
      return "";
    }
    assert (rawComment.startsWith("/**")) : "Non-javadoc comment: " + rawComment;
    assert (rawComment.endsWith("*/")) : "No comment terminator: " + rawComment;
    String result = rawComment;
    result = result.replaceFirst("^/\\*\\*\\s*", "");
    result = result.replaceFirst("\\s*\\*/$", "");
    result = result.replaceAll("\\n*\\s*(\\*)+\\s?", "\n");
    return result.trim();
  }

  private static final Map<String, Integer> flagsToHex = CollectionFactory.newHashMap();

  static {
    flagsToHex.put(Flags.PUBLIC.toString(), 0x0001);
    flagsToHex.put(Flags.PRIVATE.toString(), 0x0002);
    flagsToHex.put(Flags.PROTECTED.toString(), 0x0004);
    flagsToHex.put(Flags.STATIC.toString(), 0x0008);
    flagsToHex.put(Flags.FINAL.toString(), 0x0010);
    // flagsToHex.put(Flags.SYNCHRONIZED.toString(), 0x0020);
    flagsToHex.put(Flags.NATIVE.toString(), 0x0100);
    flagsToHex.put(Flags.ABSTRACT.toString(), 0x0400);
    // flagsToHex.put(Flags.STRICTFP.toString(), 0x0800);
  }

  public static int flagsToModifierSpecifier(Set<String> flags) {
    int r = 0;
    for (String flag : flags) {
      // flag could be "property" which is not in flagsToHex (and not
      // recognized by
      // the standard doclet)
      if (flagsToHex.containsKey(flag)) {
        r |= flagsToHex.get(flag);
      }
    }
    return r;
  }
}
Пример #3
0
/**
 * ClassFile represents a Java classfile as it is found on disk. The classfile is modelled according
 * to the Java Virtual Machine Specification. Methods are provided to access the classfile at a very
 * low level.
 *
 * @see polyglot.types.reflect Attribute
 * @see polyglot.types.reflect ConstantValue
 * @see polyglot.types.reflect Field
 * @see polyglot.types.reflect Method
 * @author Nate Nystrom
 */
public class ClassFile {
  protected Constant[] constants; // The constant pool
  protected int modifiers; // This class's modifier bit field
  protected int thisClass;
  protected int superClass;
  protected int[] interfaces;
  protected Field[] fields;
  protected Method[] methods;
  protected Attribute[] attrs;
  protected InnerClasses innerClasses;
  protected InnerClasses.Info innerClassInfo;
  protected Signature signature;
  protected File classFileSource;
  protected ExtensionInfo extensionInfo;

  protected Map<String, JLCInfo> jlcInfoCache = CollectionFactory.newHashMap();

  /**
   * Constructor. This constructor parses the class file from the byte array
   *
   * @param code A byte array containing the class data
   */
  public ClassFile(File classFileSource, byte[] code, ExtensionInfo ext) {
    this.classFileSource = classFileSource;
    this.extensionInfo = ext;

    try {
      ByteArrayInputStream bin = new ByteArrayInputStream(code);
      DataInputStream in = new DataInputStream(bin);
      read(in);
      in.close();
      bin.close();
    } catch (IOException e) {
      throw new InternalCompilerError("I/O exception on ByteArrayInputStream");
    }
  }

  JLCInfo getJLCInfo(String typeSystemKey) {
    // Check if already set.
    JLCInfo jlc = (JLCInfo) jlcInfoCache.get(typeSystemKey);

    if (jlc != null) {
      return jlc;
    }

    jlc = new JLCInfo();
    jlcInfoCache.put(typeSystemKey, jlc);

    try {
      int mask = 0;

      for (int i = 0; i < fields.length; i++) {
        if (fields[i].name().equals("jlc$SourceLastModified$" + typeSystemKey)) {
          jlc.sourceLastModified = fields[i].getLong();
          mask |= 1;
        } else if (fields[i].name().equals("jlc$CompilerVersion$" + typeSystemKey)) {
          jlc.compilerVersion = fields[i].getString();
          mask |= 2;
        } else if (fields[i].name().equals("jlc$ClassType$" + typeSystemKey)) {
          // there is encoded class type information.
          StringBuffer encodedClassTypeInfo = new StringBuffer(fields[i].getString());
          // check to see if there are more fields.
          int seeking = 1;
          boolean found;
          do {
            found = false;
            String suffix = ("$" + seeking);
            String seekingFieldName = "jlc$ClassType$" + typeSystemKey + suffix;
            for (int j = 0; j < fields.length; j++) {
              if (fields[j].name().equals(seekingFieldName)) {
                encodedClassTypeInfo.append(fields[j].getString());
                found = true;
                seeking++;
                break;
              }
            }
          } while (found);
          jlc.encodedClassType = encodedClassTypeInfo.toString();
          mask |= 4;
        }
      }

      if (mask != 7) {
        // Not all the information is there.  Reset to default.
        jlc.sourceLastModified = 0;
        jlc.compilerVersion = null;
        jlc.encodedClassType = null;
      }
    } catch (SemanticException e) {
      jlc.sourceLastModified = 0;
      jlc.compilerVersion = null;
      jlc.encodedClassType = null;
    }

    return jlc;
  }

  /** Get the encoded source modified time. */
  public long sourceLastModified(String ts) {
    JLCInfo jlc = getJLCInfo(ts);
    return jlc.sourceLastModified;
  }

  /** Get the encoded compiler version used to compile the source. */
  public String compilerVersion(String ts) {
    JLCInfo jlc = getJLCInfo(ts);
    return jlc.compilerVersion;
  }

  /** Get the encoded class type for the given type system. */
  public String encodedClassType(String typeSystemKey) {
    JLCInfo jlc = getJLCInfo(typeSystemKey);
    return jlc.encodedClassType;
  }

  /** Read the class file. */
  void read(DataInputStream in) throws IOException {
    // Read in file contents from stream
    readHeader(in);
    readConstantPool(in);
    readAccessFlags(in);
    readClassInfo(in);
    readFields(in);
    readMethods(in);
    readAttributes(in);
  }

  /** Get the class name at the given constant pool index. */
  public String classNameCP(int index) {
    Constant c = constants[index];

    if (c != null && c.tag() == Constant.CLASS) {
      Integer nameIndex = (Integer) c.value();
      if (nameIndex != null) {
        return className(nameIndex.intValue());
      }
    }

    return null;
  }

  public String className(int idx) {
    Constant c;
    c = constants[idx];
    if (c.tag() == Constant.UTF8) {
      String s = (String) c.value();
      return s.replace('/', '.');
    }
    return null;
  }

  /**
   * Get the name of the class, including the package name.
   *
   * @return The name of the class.
   */
  public String name() {
    Constant c = constants[thisClass];
    if (c.tag() == Constant.CLASS) {
      Integer nameIndex = (Integer) c.value();
      if (nameIndex != null) {
        c = constants[nameIndex.intValue()];
        if (c.tag() == Constant.UTF8) {
          return (String) c.value();
        }
      }
    }

    throw new ClassFormatError("Couldn't find class name in file");
  }

  /**
   * Get the source of the class.
   *
   * @return The source of the class.
   */
  public File classFileSource() {
    return this.classFileSource;
  }

  /**
   * Read a constant from the constant pool.
   *
   * @param in The stream from which to read.
   * @return The constant.
   * @exception IOException If an error occurs while reading.
   */
  Constant readConstant(DataInputStream in) throws IOException {
    int tag = in.readUnsignedByte();
    Object value;

    switch (tag) {
      case Constant.CLASS:
      case Constant.STRING:
      case Constant.METHOD_TYPE: // @since 1.8
        value = Integer.valueOf(in.readUnsignedShort());
        break;
      case Constant.FIELD_REF:
      case Constant.METHOD_REF:
      case Constant.INTERFACE_METHOD_REF:
      case Constant.NAME_AND_TYPE:
      case Constant.INVOKE_DYNAMIC: // @since 1.8
        value = new int[2];

        ((int[]) value)[0] = in.readUnsignedShort();
        ((int[]) value)[1] = in.readUnsignedShort();
        break;
      case Constant.INTEGER:
        value = Integer.valueOf(in.readInt());
        break;
      case Constant.FLOAT:
        value = Float.valueOf(in.readFloat());
        break;
      case Constant.LONG:
        // Longs take up 2 constant pool entries.
        value = Long.valueOf(in.readLong());
        break;
      case Constant.DOUBLE:
        // Doubles take up 2 constant pool entries.
        value = Double.valueOf(in.readDouble());
        break;
      case Constant.UTF8:
        value = in.readUTF();
        break;
      case Constant.METHOD_HANDLE: // @since 1.8
        value = new int[2];

        ((int[]) value)[0] = in.readUnsignedByte();
        ((int[]) value)[1] = in.readUnsignedShort();
        break;
      default:
        throw new ClassFormatError("Invalid constant tag: " + tag);
    }

    return new Constant(tag, value);
  }

  /**
   * Read the class file header.
   *
   * @param in The stream from which to read.
   * @exception IOException If an error occurs while reading.
   */
  void readHeader(DataInputStream in) throws IOException {
    int magic = in.readInt();

    if (magic != 0xCAFEBABE) {
      throw new ClassFormatError("Bad magic number.");
    }

    int major = in.readUnsignedShort();
    int minor = in.readUnsignedShort();
  }

  /**
   * Read the class's constant pool. Constants in the constant pool are modeled by an array of
   * <tt>reflect.Constant</tt>/
   *
   * @param in The stream from which to read.
   * @exception IOException If an error occurs while reading.
   * @see Constant
   * @see #constants
   */
  void readConstantPool(DataInputStream in) throws IOException {
    int count = in.readUnsignedShort();

    constants = new Constant[count];

    // The first constant is reserved for internal use by the JVM.
    constants[0] = null;

    // Read the constants.
    for (int i = 1; i < count; i++) {
      constants[i] = readConstant(in);

      switch (constants[i].tag()) {
        case Constant.LONG:
        case Constant.DOUBLE:
          // Longs and doubles take up 2 constant pool entries.
          constants[++i] = null;
          break;
      }
    }
  }

  /**
   * Read the class's access flags.
   *
   * @param in The stream from which to read.
   * @exception IOException If an error occurs while reading.
   */
  void readAccessFlags(DataInputStream in) throws IOException {
    modifiers = in.readUnsignedShort();
  }

  /**
   * Read the class's name, superclass, and interfaces.
   *
   * @param in The stream from which to read.
   * @exception IOException If an error occurs while reading.
   */
  void readClassInfo(DataInputStream in) throws IOException {
    int index;

    thisClass = in.readUnsignedShort();
    superClass = in.readUnsignedShort();

    int numInterfaces = in.readUnsignedShort();

    interfaces = new int[numInterfaces];

    for (int i = 0; i < numInterfaces; i++) {
      interfaces[i] = in.readUnsignedShort();
    }
  }

  /**
   * Read the class's fields.
   *
   * @param in The stream from which to read.
   * @exception IOException If an error occurs while reading.
   */
  void readFields(DataInputStream in) throws IOException {
    int numFields = in.readUnsignedShort();

    fields = new Field[numFields];

    for (int i = 0; i < numFields; i++) {
      fields[i] = createField(in);
    }
  }

  /**
   * Read the class's methods.
   *
   * @param in The stream from which to read.
   * @exception IOException If an error occurs while reading.
   */
  void readMethods(DataInputStream in) throws IOException {
    int numMethods = in.readUnsignedShort();

    methods = new Method[numMethods];

    for (int i = 0; i < numMethods; i++) {
      methods[i] = createMethod(in);
    }
  }

  /**
   * Read the class's attributes. Since none of the attributes are required, just read the length of
   * each attribute and skip that many bytes.
   *
   * @param in The stream from which to read.
   * @exception IOException If an error occurs while reading.
   */
  public void readAttributes(DataInputStream in) throws IOException {
    int numAttributes = in.readUnsignedShort();

    attrs = new Attribute[numAttributes];

    for (int i = 0; i < numAttributes; i++) {
      int nameIndex = in.readUnsignedShort();
      int length = in.readInt();
      String name = (String) constants[nameIndex].value();
      Attribute a = createAttribute(in, name, nameIndex, length);
      if (a != null) {
        attrs[i] = a;
      } else {
        long n = in.skip(length);
        if (n != length) {
          throw new EOFException();
        }
      }
    }
  }

  public Method createMethod(DataInputStream in) throws IOException {
    Method m = new Method(in, this);
    m.initialize();
    return m;
  }

  public Field createField(DataInputStream in) throws IOException {
    Field f = new Field(in, this);
    f.initialize();
    return f;
  }

  public Attribute createAttribute(DataInputStream in, String name, int nameIndex, int length)
      throws IOException {
    if (name.equals("InnerClasses")) {
      innerClasses = new InnerClasses(in, nameIndex, length);
      Info[] allInnerInfos = innerClasses.getClasses();
      for (int i = 0; i < allInnerInfos.length; i++) {
        if (allInnerInfos[i].classIndex == getThisClass()) {
          innerClassInfo = allInnerInfos[i];
        }
      }
      return innerClasses;
    }
    if (name.equals("Signature")) {
      signature = new Signature(this, in, nameIndex, length);
      return signature;
    }
    return null;
  }

  public Attribute[] getAttrs() {
    return attrs;
  }

  public Constant[] getConstants() {
    return constants;
  }

  public Field[] getFields() {
    return fields;
  }

  public InnerClasses getInnerClasses() {
    return innerClasses;
  }

  public InnerClasses.Info getInnerClassInfo() {
    return innerClassInfo;
  }

  public int[] getInterfaces() {
    return interfaces;
  }

  public Method[] getMethods() {
    return methods;
  }

  public int getModifiers() {
    if (innerClassInfo != null) return innerClassInfo.modifiers;
    return modifiers;
  }

  public int getSuperClass() {
    return superClass;
  }

  public int getThisClass() {
    return thisClass;
  }

  public Signature getSignature() {
    return signature;
  }

  public String signature() {
    if (this.signature != null) {
      return (String) constants[this.signature.getSignature()].value();
    }
    return "";
  }

  public String toString() {
    return Modifier.toString(modifiers)
        + "("
        + Integer.toHexString(modifiers)
        + ") "
        + name()
        + signature();
  }
}