package net.wuffies.japi;
import java.lang.reflect.*;
import java.util.zip.*;
import java.io.IOException;
import java.util.Enumeration;

/**
 * Process a Java API and emit a machine-readable description of the API,
 * suitable for comparison to other APIs. Specifically, the perl script
 * japicompat.pl can test APIs for source/binary compatibility with each other.
 *
 * @author Stuart Ballard &lt;<a href="mailto:sballard@wuffies.net">sballard@wuffies.net</a>&gt;
 */
public class Japize {

  /**
   * Parse the program's arguments and perform the main processing.
   */
  public static void main(String[] args) throws IllegalAccessException, IOException, ClassNotFoundException {
    if (args.length == 0) {
      System.err.println("Usage: Japize [-c classname ... | -f zipfile package ...] >output.japi");
    } 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 NoSuchMethodException, 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";
    Class sup = c;
    int smods = mods;
    while (sup.getSuperclass() != null) {
      sup = sup.getSuperclass();
      smods = sup.getModifiers();
      if (!Modifier.isPublic(smods) && !Modifier.isProtected(smods)) {
        System.err.print("^");
      } else {
        type += ":" + sup.getName();
      }
    }
    Class[] ifaces = c.getInterfaces();
    for (int i = 0; i < ifaces.length; i++) {
      int mds = ifaces[i].getModifiers();
      if (Modifier.isPublic(mds) || Modifier.isProtected(mds)) {
        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) &&
          (fields[i].getType().isPrimitive() ||
           type.equals("java.lang.String"))) {
        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;
      }
      try {
        if (!methods[i].equals(c.getMethod(methods[i].getName(),
                                           methods[i].getParameterTypes()))) {
          System.err.print("!");
          continue;
        }
      } catch (NoSuchMethodException e) {
        System.err.print("M");
      }
      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 NoSuchMethodException, 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);
  }
}
