/* --- Copyright (c) Chris Rathman 1999. All rights reserved. -----------------
 > File:        jasper/ClassFile.java
 > Purpose:     Java class file format
 > Author:      Chris Rathman, 12 June 1999
 > Version:     1.00
 */
package jasper;
import java.io.*;
import java.util.zip.*;

/*=======================================================================
 = Class:         ClassFile                                             =
 =                                                                      =
 = Desc:          java class file format                                =
 =======================================================================*/
public class ClassFile implements Serializable {
   static final int SPACER = 25;                  // Column spacer constant for jasmine output

   private int magic;                             // magic field of class file 0xcafebabe
   private int minorVersion;                      // compiler minor version number
   private int majorVersion;                      // compiler major version number
   private Pool_Collection pool;                  // constant pool collection (symbol table)
   private int accessFlags;                       // access flags for class
   private int thisClass;                         // class name (via constant pool index)
   private int superClass;                        // super class name (via constant pool index)
   private Interface_Collection interfaces;       // interfaces implemented by the class
   private Field_Collection fields;               // fields defined by the class
   private Method_Collection methods;             // methods defined by the class
   private Attribute_Collection attributes;       // class attributes:
                                                  //    (SourceFile, InnerClasses, Synthetic, Deprecated)

   /*-----------------------------------------------------------------------
    - Method:        Class Constructor                                     -
    -                                                                      -
    - Desc:          construct object by reading in java class file        -
    -----------------------------------------------------------------------*/
   public ClassFile(String name) {
      DataInputStream ios = null;
      ZipInputStream zip = null;
      ZipEntry zin = null;

      // normalize the file name to expected format
      String fileName = parseFileDir(name) + parseFileName(name) + "." + parseFileExt(name);
      String className = fileName.replace(File.separatorChar, '/');
      System.out.println("Reading:   " + fileName);

      try {
         FIND: {
            // first check if the file is in the current directory
            File f = new File(fileName);
            if (f.exists()) {
               ios = new DataInputStream(new FileInputStream(fileName));
               break FIND;
            }

            // now check if the file is anywhere in the class path (or in a jar file)
            String[] classPath = getClassPath();
            for (int i = 0; i < classPath.length; i++) {
               f = new File(classPath[i]);
               if (f.exists()) {
                  if (f.isFile()) {
                     // I'm assuming that any files specified in the class path must be jars
                     zip = new ZipInputStream(new FileInputStream(classPath[i]));
                     for (zin = zip.getNextEntry(); zin != null; zin = zip.getNextEntry()) {
                        if (zin.getName().equals(className)) {
                           ios = new DataInputStream(zip);
                           break FIND;
                        }
                     }
                  } else if (f.isDirectory()) {
                     // if class path is directory check if file found along that path
                     f = new File(classPath[i] + File.separatorChar + fileName);
                     if (f.isFile()) {
                        ios = new DataInputStream(new FileInputStream(f));
                        break FIND;
                     }
                  }
               }
            }
            // if we get to this pount then throw file not found exception
            if (ios == null) throw new FileNotFoundException(fileName);
         }

         // read the magic bytes - abort if not a class file
         magic = ios.readInt();
         if (magic != 0xcafebabe) throw new IOException("File is not a java class file.");

         // read the compiler version
         minorVersion = ios.readShort();
         majorVersion = ios.readShort();

         // read the constant pool (symbol table area)
         pool = new Pool_Collection(ios);

         // read the class access flags
         accessFlags = ios.readShort();

         // get the name of this class (index into constant pool)
         thisClass = ios.readShort();

         // get the name of the super class (index into constant pool)
         superClass = ios.readShort();

         // read the interfaces that are implemented
         interfaces = new Interface_Collection(ios, pool);

         // read the class fields
         fields = new Field_Collection(ios, pool);

         // read the class methods
         methods = new Method_Collection(ios, pool);

         // read the attributes (SourceFile)
         attributes = new Attribute_Collection(ios, pool);

      } catch (FileNotFoundException e) {
         // report the error
         System.out.println(e);

      } catch (IOException e) {
         // report the error
         System.out.println(e);

         // dump the remaining bytes of the file as hex output (useful for debugging)
         dump(ios,  Integer.MAX_VALUE);
      }
   }

   /*-----------------------------------------------------------------------
    - Method:        accessString                                          -
    -                                                                      -
    - Desc:          build a string for the class access flags             -
    -----------------------------------------------------------------------*/
   private String accessString() {
      String retVal = "";
      if ((accessFlags & 0x0001) > 0) retVal += "public ";
      if ((accessFlags & 0x0010) > 0) retVal += "final ";
      if ((accessFlags & 0x0400) > 0) retVal += "abstract ";
      return retVal;
   }

   /*-----------------------------------------------------------------------
    - Method:        jasmin                                                -
    -                                                                      -
    - Desc:          output a jasmin assembly file                         -
    -----------------------------------------------------------------------*/
   public void jasmin() {
      try{
         // jasmine uses a ".j" extension by default
         String name = browseClass() + ".j";
         String fileName = parseFileDir(name) + parseFileName(name) + "." + parseFileExt(name);

         // make sure there is a place to put it
         if (!parseFileDir(name).equals("")) {
            // test if directory for the class exists
            File f = new File("jasper.out" + File.separatorChar + parseFileDir(name));
            fileName = "jasper.out" + File.separatorChar + fileName;

            // if the directory path does exist, then create it under the current directory
            if (!f.exists()) f.mkdirs();
         }

         // open up the output stream to write the file
         PrintStream out = new PrintStream(new FileOutputStream(fileName));

         // print the .source directive
         attributes.jasmin(out);

         // print the .class or .interface directive
         if ((accessFlags & 0x0200) > 0) {
            out.print(pad(".interface", SPACER));
         } else {
            out.print(pad(".class", SPACER));
         }
         out.println(accessString() + pool.toString(thisClass));

         // print the .super directive
         if (superClass > 0) out.println(pad(".super", SPACER) + pool.toString(superClass));

         // print the .implements directives
         interfaces.jasmin(out);
         out.println("");

         // print the .field directives
         fields.jasmin(out);
         out.println("");

         // print the .method directives
         methods.jasmin(out);

         // echo that the jasmine file has been completed
         System.out.println("Generated: " + fileName);

      } catch (IOException e) {
         // report the error
         System.out.println(e);
      }
   }


   /*-----------------------------------------------------------------------
    - Method:        browseSourceFile                                      -
    -                                                                      -
    - Desc:          class source file name                                -
    -----------------------------------------------------------------------*/
   public String browseSourceFile() {
      return attributes.browseDeprecated() + attributes.browseSynthetic() + accessString() +
         attributes.browseSourceFile();
   }

   /*-----------------------------------------------------------------------
    - Method:        browseClass                                           -
    -                                                                      -
    - Desc:          class name (definition).                              -
    -----------------------------------------------------------------------*/
   public String browseClass() {
      // note: jasper will add the attributes 'synthetic' and 'deprecated' if present for the class
      return pool.browseString(thisClass);
   }

   /*-----------------------------------------------------------------------
    - Method:        browseSuper                                           -
    -                                                                      -
    - Desc:          super class name (extends)                            -
    -----------------------------------------------------------------------*/
   public String browseSuper() {
      if (superClass > 0) return pool.browseString(superClass); else return "";
   }

   /*-----------------------------------------------------------------------
    - Method:        browseInterfaces                                      -
    -                                                                      -
    - Desc:          interfaces which are implemented by the class         -
    -----------------------------------------------------------------------*/
   public String[] browseInterfaces() {
      return interfaces.browseInterfaces();
   }

   /*-----------------------------------------------------------------------
    - Method:        browseInnerClasses                                    -
    -                                                                      -
    - Desc:          inner classes of the class                            -
    -----------------------------------------------------------------------*/
   public String[][] browseInnerClasses() {
      return attributes.browseInnerClasses();
   }

   /*-----------------------------------------------------------------------
    - Method:        browseFields                                          -
    -                                                                      -
    - Desc:          fields defined by the class.                          -
    -----------------------------------------------------------------------*/
   public String[] browseFields() {
      // note: jasper will add the attributes 'synthetic' and 'deprecated' if present for the field
      return fields.browseFields(browseClass());
   }

   /*-----------------------------------------------------------------------
    - Method:        browseMethods                                         -
    -                                                                      -
    - Desc:          methods defined by the class                          -
    -----------------------------------------------------------------------*/
   public String[] browseMethods() {
      // note: jasper will add the attributes 'synthetic' and 'deprecated' if present for the method
      return methods.browseMethods(browseClass());
   }

   /*-----------------------------------------------------------------------
    - Method:        browseFieldrefs                                       -
    -                                                                      -
    - Desc:          fields accessed (indexed by class methods)            -
    -----------------------------------------------------------------------*/
   public String[][] browseFieldrefs() {
      return methods.browseFieldrefs();
   }

   /*-----------------------------------------------------------------------
    - Method:        browseMethodrefs                                      -
    -                                                                      -
    - Desc:          methods accessed (indexed by class methods)           -
    -----------------------------------------------------------------------*/
   public String[][] browseMethodrefs() {
      return methods.browseMethodrefs();
   }

   /*-----------------------------------------------------------------------
    - Method:        browseInterfaceMethodrefs                             -
    -                                                                      -
    - Desc:          interface methods accessed (indexed by class methods) -
    -----------------------------------------------------------------------*/
   public String[][] browseInterfaceMethodrefs() {
      return methods.browseInterfaceMethodrefs();
   }

   /*-----------------------------------------------------------------------
    - Method:        pad                                                   -
    -                                                                      -
    - Desc:          pad a string with specified spaces.  sure wish the    -
    -                String class would give me this automatically.        -
    -----------------------------------------------------------------------*/
   public static String pad(String s, int pad) {
      StringBuffer a = new StringBuffer(pad);
      for (int i = 0; i < pad; i++) a = a.append(" ");
      return s + a.substring(s.length());
   }

   /*-----------------------------------------------------------------------
    - Method:        pad                                                   -
    -                                                                      -
    - Desc:          pad a string with specified spaces.  this overload of -
    -                the function prevents having to convert int to string -
    -----------------------------------------------------------------------*/
   public static String pad(int n, int pad) {
      return pad(n + "", pad);
   }

   /*-----------------------------------------------------------------------
    - Method:        getClassPath                                          -
    -                                                                      -
    - Desc:          get the java class path                               -
    -----------------------------------------------------------------------*/
   private String[] getClassPath() {
      java.util.Vector x = new java.util.Vector();
      String classPath = System.getProperty("java.class.path");
      String s = "";
      for (int i = 0; i < classPath.length(); i++) {
         if (classPath.charAt(i) == ';') {
            x.add(s);
            s = "";
         } else {
            s += classPath.charAt(i);
         }
      }
      if (!s.equals("")) x.add(s);
      String javaHome = System.getProperty("java.home");
      if (javaHome != null) x.add(javaHome + File.separatorChar + "lib" + File.separatorChar + "rt.jar");
      String[] retVal = new String[x.size()];
      x.copyInto(retVal);
      return retVal;
   }

   /*-----------------------------------------------------------------------
    - Method:        parseFileDir                                          -
    -                                                                      -
    - Desc:          parse the directory path from a file string           -
    -----------------------------------------------------------------------*/
   public static String parseFileDir(String s) {
      String fileDir = s;
      fileDir = fileDir.replace('/', File.separatorChar);
      fileDir = fileDir.replace('\\', File.separatorChar);
      int i = fileDir.lastIndexOf('.');
      if (i > 0) {
         fileDir = fileDir.substring(0, i).replace('.', File.separatorChar) + fileDir.substring(i);
      }
      if (fileDir.lastIndexOf(File.separatorChar) >= 0) {
         fileDir = fileDir.substring(0, fileDir.lastIndexOf(File.separatorChar) + 1);
      } else {
         fileDir = "";
      }
      fileDir = fileDir.replace('.', File.separatorChar);
      if (!fileDir.equals("")) {
         if (fileDir.charAt(fileDir.length()-1) != File.separatorChar) fileDir += File.separatorChar;
      }
      return fileDir;
   }

   /*-----------------------------------------------------------------------
    - Method:        parseFileName                                         -
    -                                                                      -
    - Desc:          parse the base file name from a file string- kill ext -
    -----------------------------------------------------------------------*/
   public static String parseFileName(String s) {
      String fileName = s;
      fileName = fileName.replace('/', File.separatorChar);
      fileName = fileName.replace('\\', File.separatorChar);
      int i = fileName.lastIndexOf('.');
      if (i > 0) {
         fileName = fileName.substring(0, i).replace('.', File.separatorChar) + fileName.substring(i);
      }
      if (fileName.lastIndexOf(File.separatorChar) >= 0) {
         fileName = fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1);
      }
      if (fileName.lastIndexOf(".") >= 0) {
         fileName = fileName.substring(0, fileName.lastIndexOf("."));
      }
      return fileName;
   }

   /*-----------------------------------------------------------------------
    - Method:        parseFileExt                                          -
    -                                                                      -
    - Desc:          parse the file extension from a file string           -
    -----------------------------------------------------------------------*/
   public static String parseFileExt(String s) {
      String fileExt = s;
      if (fileExt.lastIndexOf(".") >= 0) {
         fileExt = fileExt.substring(fileExt.lastIndexOf(".") + 1);
      } else {
         fileExt = "class";
      }
      return fileExt;
   }

   /*-----------------------------------------------------------------------
    - Method:        dump                                                  -
    -                                                                      -
    - Desc:          hex dump of an input file stream                      -
    -----------------------------------------------------------------------*/
   public static void dump(DataInputStream ios, int length) {
      try {
         String s;
         String a = "";
         for (int i = 0, j = 0; i < length; i++) {
            int val = ios.read();
            if (val < 0) System.exit(0);
            if (j == 0) {
               System.out.println("  " + a);
               a = "";
               s = Integer.toHexString(i);
               s = s + "      ".substring(s.length());
               System.out.print(s + "  ");
            }
            if (j == 8) System.out.print("  ");
            s = Integer.toHexString(val);
            if (s.length() < 2) s = "0" + s;
            System.out.print(s + " ");
            if ((val > 32) && (val < 128)) a += (char)val; else a += ' ';
            if (++j == 16) j = 0;
         }
         System.out.println("");
      } catch(IOException e) {
         System.out.println(e);
      }
   }
}

Chris Rathman / Chris.Rathman@gmail.com