/* --- 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); } } }