Reflection bug?

Stuart Ballard sballard at netreach.net
Sun Jun 11 14:18:28 PDT 2000


I am working on a small java program and perl script to test binary
compatibility between Java APIs. It uses reflection to identify every
class and member in the public and protected API, and prints them out in
a format that is easy to parse and compare with other APIs. The perl
script then uses Sun's definition of "binary compatibility" to compare
two output files from different java implementations.

I've noticed that Kaffe gives some strange results in some situations;
one particular case that I noticed is java.io.BufferedWriter. Running
the attached class as "kaffe net.wuffies.japi.Japize -c
java.io.BufferedWriter" I get duplicate lines for some methods. In
particular, the methods close() and flush() appear twice - once
concrete, once abstract. I suspect that Kaffe is including all methods
from all superclasses in the results of Class.getMethods(), even though
some of the superclass methods have been overridden by corresponding
methods in the class itself.

Can anyone confirm that this is or is not a bug? Blackdown JDK 1.1.8
does not display this behavior, and I can't think of an easy workaround
in my code to get the results I expect.

Thanks,
Stuart.

PS Would people here consider a class such as this to be legitimate
"reverse engineering" that could be used to monitor Kaffe's progress
towards complete JDK compatibility? It seems to me that, since it's all
"black box" testing using public APIs, and I'm only using it to obtain
information that is publically available in the JavaDoc anyway, there
shouldn't be any reason not to make use of it. But I'd rather know what
people here think before I post any results to the group and potentially
"contaminate" everyone here with knowledge they shouldn't possess.
-------------- next part --------------
package net.wuffies.japi;
import java.lang.reflect.*;
import java.util.zip.*;
import java.io.IOException;
import java.util.Enumeration;

public class Japize {

  public static void main(String[] args) throws IllegalAccessException, IOException, ClassNotFoundException {
    if (args.length == 0) {
      System.err.println("Usage: Japize [-c classname ... | -f zipfile package ...]");
    } else if ("-c".equals(args[0])) {
      for (int i = 1; i < args.length; i++) {
        japizeClass(args[i]);
      }
    } else if ("-f".equals(args[0]) && args.length > 2) {
      for (int i = 2; i < args.length; i++) {
        processZip(args[1], args[i]);
        System.exit(0);
      }
    } else {
      System.err.println("Usage: Japize [-c classname ... | -f zipfile package ...]");
    }
  }

  public static boolean japizeClass(String cname) throws IllegalAccessException, ClassNotFoundException {
    Class c = Class.forName(cname);
    int mods = c.getModifiers();
    if (!Modifier.isPublic(mods) && !Modifier.isProtected(mods)) return false;
    String entry = c.getName() + "#";
    String type = c.isInterface() ? "interface" : "class";
    if (c.getSuperclass() != null) {
      type += ":" + c.getSuperclass().getName();
    }
    Class[] ifaces = c.getInterfaces();
    for (int i = 0; i < ifaces.length; i++) {
      type += "*" + ifaces[i].getName();
    }
    printEntry(entry, type, c.getModifiers());
    Field[] fields = c.getFields();
    Method[] methods = c.getMethods();
    Constructor[] constrs = c.getConstructors();
    for (int i = 0; i < fields.length; i++) {
      int dmods = fields[i].getDeclaringClass().getModifiers();
      if (!Modifier.isPublic(dmods) && !Modifier.isProtected(dmods)) {
        System.err.print(">");
        continue;
      }
      mods = fields[i].getModifiers();
      type = fields[i].getType().getName();
      if (Modifier.isFinal(mods) && Modifier.isStatic(mods) &&
          Modifier.isPublic(mods)) {
        type += ":" + fields[i].get(null);
      }
      printEntry(c.getName() + "#" + fields[i].getName(), type, mods);
    }
    for (int i = 0; i < constrs.length; i++) {
      entry = c.getName() + "#(";
      Class[] params = constrs[i].getParameterTypes();
      String comma = "";
      for (int j = 0; j < params.length; j++) {
        entry += comma + params[j].getName();
        comma = ",";
      }
      entry += ")";
      type = "constructor";
      Class[] excps = constrs[i].getExceptionTypes();
      for (int j = 0; j < excps.length; j++) {
        type += "*" + excps[j].getName();
      }
      printEntry(entry, type, constrs[i].getModifiers());
    }
    for (int i = 0; i < methods.length; i++) {
      int dmods = methods[i].getDeclaringClass().getModifiers();
      if (!Modifier.isPublic(dmods) && !Modifier.isProtected(dmods)) {
        System.err.print("}");
        continue;
      }
      entry = c.getName() + "#" + methods[i].getName() + "(";
      Class[] params = methods[i].getParameterTypes();
      String comma = "";
      for (int j = 0; j < params.length; j++) {
        entry += comma + params[j].getName();
        comma = ",";
      }
      entry += ")";
      type = methods[i].getReturnType().getName();
      Class[] excps = methods[i].getExceptionTypes();
      for (int j = 0; j < excps.length; j++) {
        type += "*" + excps[j].getName();
      }
      printEntry(entry, type, methods[i].getModifiers());
    }
    return true;
  }

  public static void printEntry(String thing, String type, int mods) {
    if (!Modifier.isPublic(mods) && !Modifier.isProtected(mods)) return;
    System.out.print(thing + " ");
    System.out.print(Modifier.isPublic(mods) ? "public " : "protected ");
    System.out.print(Modifier.isAbstract(mods) ? "abstract " : "concrete ");
    System.out.print(Modifier.isStatic(mods) ? "static " : "instance ");
    System.out.print(Modifier.isFinal(mods) ? "final " : "nonfinal ");
    System.out.println(type);
  }

  public static void processZip(String fname, String pkg) throws IllegalAccessException, IOException, ClassNotFoundException {
    ZipFile z = new ZipFile(fname);
    Enumeration ents = z.entries();
    String pkgDir = pkg.replace('.', '/') + "/";
    while (ents.hasMoreElements()) {
      String ze = ((ZipEntry)ents.nextElement()).getName();
      if (ze.startsWith(pkgDir) && ze.endsWith(".class")) {
        if (japizeClass(ze.substring(0, ze.length() - 6).replace('/', '.'))) {
          System.err.print("*");
        } else {
          System.err.print("-");
        }
      } else {
        System.err.print("_");
      }
      System.err.flush();
    }
    System.err.println("\nDone scanning for " + pkg);
  }
}


More information about the kaffe mailing list