home *** CD-ROM | disk | FTP | other *** search
Java Source | 1996-05-22 | 25.0 KB | 788 lines |
- /*
- * ClassFile.java Chuck McManis
- *
- * Copyright (c) 1996 Chuck McManis, All Rights Reserved.
- *
- * Permission to use, copy, modify, and distribute this software
- * and its documentation for NON-COMMERCIAL purposes and without
- * fee is hereby granted provided that this copyright notice
- * appears in all copies.
- *
- * CHUCK MCMANIS MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
- * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
- * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. CHUCK MCMANIS SHALL NOT BE
- * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING,
- * MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
- */
-
- package util;
-
- import java.util.*;
- import java.io.*;
-
- /**
- * This class is used to manipulate Java class files in strange and
- * mysterious ways.
- *
- * Usage it typically to feed it an array of bytes that are a class
- * file, manipulate the class, then convert the class back into bytes,
- * and feed the final result to <TT>defineClass()</TT>.
- *
- * @version 1.6, 19 Aug 1995
- * @author Chuck McManis
- * @see AttributeInfo
- * @see ConstantPoolInfo
- * @see MethodInfo
- * @see FieldInfo
- */
-
- public class ClassFile {
- int magic;
- short majorVersion;
- short minorVersion;
- ConstantPoolInfo constantPool[];
- short accessFlags;
- ConstantPoolInfo thisClass;
- ConstantPoolInfo superClass;
- ConstantPoolInfo interfaces[];
- FieldInfo fields[];
- MethodInfo methods[];
- AttributeInfo attributes[];
- boolean isValidClass = false;
-
- public static final int ACC_PUBLIC = 0x1;
- public static final int ACC_PRIVATE = 0x2;
- public static final int ACC_PROTECTED = 0x4;
- public static final int ACC_STATIC = 0x8;
- public static final int ACC_FINAL = 0x10;
- public static final int ACC_SYNCHRONIZED = 0x20;
- public static final int ACC_THREADSAFE = 0x40;
- public static final int ACC_TRANSIENT = 0x80;
- public static final int ACC_NATIVE = 0x100;
- public static final int ACC_INTERFACE = 0x200;
- public static final int ACC_ABSTRACT = 0x400;
-
- public boolean debug = false;
- public boolean dumpConstants = false;
- /**
- * Read a class from InputStream <i>in</i>.
- */
- public boolean read(InputStream in)
- throws IOException {
- DataInputStream di = new DataInputStream(in);
- int count;
-
- magic = di.readInt();
- if (magic != (int) 0xCAFEBABE) {
- return (false);
- }
-
- majorVersion = di.readShort();
- minorVersion = di.readShort();
- count = di.readShort();
- constantPool = new ConstantPoolInfo[count];
- if (debug)
- System.out.println("read(): Read header...");
- constantPool[0] = new ConstantPoolInfo();
- for (int i = 1; i < constantPool.length; i++) {
- constantPool[i] = new ConstantPoolInfo();
- if (! constantPool[i].read(di)) {
- return (false);
- }
- // These two types take up "two" spots in the table
- if ((constantPool[i].type == ConstantPoolInfo.LONG) ||
- (constantPool[i].type == ConstantPoolInfo.DOUBLE))
- i++;
- }
-
- /*
- * Update pointers in the constant table. This turns the
- * table into a real datastructure.
- *
- * TODO: Have it verify that the right arguments are present
- */
- for (int i = 1; i < constantPool.length; i++) {
- if (constantPool[i] == null)
- continue;
- if (constantPool[i].index1 > 0)
- constantPool[i].arg1 = constantPool[constantPool[i].index1];
- if (constantPool[i].index2 > 0)
- constantPool[i].arg2 = constantPool[constantPool[i].index2];
- }
-
- if (dumpConstants) {
- for (int i = 1; i < constantPool.length; i++) {
- System.out.println("C"+i+" - "+constantPool[i]);
- }
- }
- accessFlags = di.readShort();
-
- thisClass = constantPool[di.readShort()];
- superClass = constantPool[di.readShort()];
- if (debug)
- System.out.println("read(): Read class info...");
-
- /*
- * Identify all of the interfaces implemented by this class
- */
- count = di.readShort();
- if (count != 0) {
- if (debug)
- System.out.println("Class implements "+count+" interfaces.");
- interfaces = new ConstantPoolInfo[count];
- for (int i = 0; i < count; i++) {
- int iindex = di.readShort();
- if ((iindex < 1) || (iindex > constantPool.length - 1))
- return (false);
- interfaces[i] = constantPool[iindex];
- if (debug)
- System.out.println("I"+i+": "+interfaces[i]);
- }
- }
- if (debug)
- System.out.println("read(): Read interface info...");
-
- /*
- * Identify all fields in this class.
- */
- count = di.readShort();
- if (debug)
- System.out.println("This class has "+count+" fields.");
- if (count != 0) {
- fields = new FieldInfo[count];
- for (int i = 0; i < count; i++) {
- fields[i] = new FieldInfo();
- if (! fields[i].read(di, constantPool)) {
- return (false);
- }
- if (debug)
- System.out.println("F"+i+": "+
- fields[i].toString(constantPool));
- }
- }
- if (debug)
- System.out.println("read(): Read field info...");
-
- /*
- * Identify all the methods in this class.
- */
- count = di.readShort();
- if (count != 0) {
- methods = new MethodInfo[count];
- for (int i = 0; i < count; i++) {
- methods[i] = new MethodInfo();
- if (! methods[i].read(di, constantPool)) {
- return (false);
- }
- if (debug)
- System.out.println("M"+i+": "+methods[i].toString());
- }
- }
- if (debug)
- System.out.println("read(): Read method info...");
-
-
- /*
- * Identify all of the attributes in this class
- */
- count = di.readShort();
- if (count != 0) {
- attributes = new AttributeInfo[count];
- for (int i = 0; i < count; i++) {
- attributes[i] = new AttributeInfo();
- if (! attributes[i].read(di, constantPool)) {
- return (false);
- }
- }
- }
- if (debug) {
- System.out.println("read(): Read attribute info...");
- System.out.println("done.");
- }
- isValidClass = true;
- return(true);
- }
-
- /**
- * Write the class out as a stream of bytes to the output
- * stream.
- *
- * Generally you will read a class file, manipulate
- * it in some way, and then write it out again before passing
- * it to <TT>defineClass</TT> in some class loader.
- */
- public void write(OutputStream out)
- throws IOException, Exception {
- DataOutputStream dos = new DataOutputStream(out);
-
- if (! isValidClass) {
- throw new Exception("ClassFile::write() - Invalid Class");
- }
-
- dos.writeInt(magic);
- dos.writeShort(majorVersion);
- dos.writeShort(minorVersion);
- dos.writeShort(constantPool.length);
- for (int i = 1; i < constantPool.length; i++) {
- if (constantPool[i] != null)
- constantPool[i].write(dos, constantPool);
- }
- dos.writeShort(accessFlags);
- dos.writeShort(ConstantPoolInfo.indexOf(thisClass, constantPool));
- dos.writeShort(ConstantPoolInfo.indexOf(superClass, constantPool));
-
- if (interfaces == null) {
- dos.writeShort(0);
- } else {
- dos.writeShort(interfaces.length);
- for (int i = 0; i < interfaces.length; i++) {
- dos.writeShort(ConstantPoolInfo.indexOf(interfaces[i],
- constantPool));
- }
- }
-
- if (fields == null) {
- dos.writeShort(0);
- } else {
- dos.writeShort(fields.length);
- for (int i = 0; i < fields.length; i++) {
- fields[i].write(dos, constantPool);
- }
- }
-
- if (methods == null) {
- dos.writeShort(0);
- } else {
- dos.writeShort(methods.length);
- for (int i = 0; i < methods.length; i++) {
- methods[i].write(dos, constantPool);
- }
- }
-
- if (attributes == null) {
- dos.writeShort(0);
- } else {
- dos.writeShort(attributes.length);
- for (int i = 0; i < attributes.length; i++) {
- attributes[i].write(dos, constantPool);
- }
- }
- }
-
- /**
- * Returns a string that represents what the access flags
- * are set for. So 0x14 returns "public final "
- */
- public static String accessString(short flags) {
- StringBuffer x = new StringBuffer();
-
- if ((flags & ACC_PUBLIC) != 0) {
- x.append("public ");
- }
-
- if ((flags & ACC_PRIVATE) != 0) {
- x.append("private ");
- }
-
- if ((flags & ACC_PROTECTED) != 0) {
- x.append("protected ");
- }
-
- if ((flags & ACC_STATIC) != 0) {
- x.append("static ");
- }
-
- if ((flags & ACC_FINAL) != 0) {
- x.append("final ");
- }
-
- if ((flags & ACC_SYNCHRONIZED) != 0) {
- x.append("synchronized ");
- }
-
- if ((flags & ACC_THREADSAFE) != 0) {
- x.append("threadsafe ");
- }
-
- if ((flags & ACC_TRANSIENT) != 0) {
- x.append("transient ");
- }
-
- if ((flags & ACC_NATIVE) != 0) {
- x.append("native ");
- }
-
- if ((flags & ACC_INTERFACE) != 0) {
- x.append("interface ");
- }
-
- if ((flags & ACC_ABSTRACT) != 0) {
- x.append("abstract ");
- }
-
- return (x.toString());
- }
-
- /**
- * Takes a type signature and a string representing a variable name
- * and returns a declaration for that variable name.
- *
- * For example, passing this the strings "[B" and "myArray" will
- * return the string "byte myArray[]"
- */
- public static String typeString(String typeString, String varName) {
- int isArray = 0;
- int ndx = 0;
- StringBuffer x = new StringBuffer();
-
- while (typeString.charAt(ndx) == '[') {
- isArray++;
- ndx++;
- }
-
- switch (typeString.charAt(ndx)) {
- case 'B' :
- x.append("byte ");
- break;
- case 'C' :
- x.append("char ");
- break;
- case 'D' :
- x.append("double ");
- break;
- case 'F' :
- x.append("float ");
- break;
- case 'I' :
- x.append("int ");
- break;
- case 'J' :
- x.append("long ");
- break;
- case 'L' :
- for (int i = ndx+1; i < typeString.indexOf(';'); i++) {
- if (typeString.charAt(i) != '/')
- x.append(typeString.charAt(i));
- else
- x.append('.');
- }
- x.append(" ");
- break;
- case 'V':
- x.append("void ");
- break;
- case 'S' :
- x.append("short ");
- break;
- case 'Z' :
- x.append("boolean ");
- break;
- }
- x.append(varName);
- while (isArray > 0) {
- x.append("[]");
- isArray--;
- }
- return (x.toString());
- }
-
- /**
- * Returns the next signature from a string of concatenated signatures.
- * For example if the signature was "[BII", this method would return
- * "II"
- */
- public static String nextSig(String sig) {
- int ndx = 0;
- String x;
-
- while (sig.charAt(ndx) == '[')
- ndx++;
-
- if (sig.charAt(ndx) == 'L') {
- while (sig.charAt(ndx) != ';')
- ndx++;
- }
- ndx++;
- x = (sig.substring(ndx));
- return (x);
- }
-
- /**
- * Print the name of a class in "canonical form"
- */
- private String printClassName(String s) {
- StringBuffer x;
-
- if (s.charAt(0) == '[') {
- return(typeString(s, ""));
- }
-
- x = new StringBuffer();
- for (int j = 0; j < s.length(); j++) {
- if (s.charAt(j) == '/')
- x.append('.');
- else
- x.append(s.charAt(j));
- }
- return (x.toString());
-
- }
-
- public String getClassName() {
- return printClassName(thisClass.arg1.strValue);
- }
-
- /**
- * The boring version of display().
- */
- public String toString() {
- return("Class File (Version "+majorVersion+"."+minorVersion+
- ") for class "+thisClass.arg1);
- }
-
- /**
- * Write out a text version of this class.
- */
- public void display(PrintStream ps)
- throws Exception {
- int i;
- String myClassName;
- String mySuperClassName;
- String packageName = null;
-
- if (! isValidClass) {
- ps.println("Not a valid class");
- }
-
- myClassName = printClassName(thisClass.arg1.strValue);
- mySuperClassName = printClassName(superClass.arg1.strValue);
- if (myClassName.indexOf('.') > 0) {
- packageName =
- myClassName.substring(0, myClassName.lastIndexOf('.'));
- myClassName = myClassName.substring(myClassName.lastIndexOf('.')+1);
- ps.println("package "+packageName+"\n");
- }
-
- for (i = 1; i < constantPool.length; i++) {
- if (constantPool[i] == null)
- continue;
- if ((constantPool[i] == thisClass) ||
- (constantPool[i] == superClass))
- continue;
- if (constantPool[i].type == ConstantPoolInfo.CLASS) {
- String s = constantPool[i].arg1.strValue;
- if (s.charAt(0) == '[')
- continue;
- s = printClassName(constantPool[i].arg1.strValue);
- if ((packageName != null) && (s.startsWith(packageName)))
- continue;
- ps.println("import "+printClassName(s)+";");
- }
- }
- ps.println();
- ps.println("/*");
- DataInputStream dis;
- ConstantPoolInfo cpi;
-
- if (attributes != null) {
- ps.println(" * This class has "+attributes.length+
- " optional class attributes.");
- ps.println(" * These attributes are: ");
- for (i = 0; i < attributes.length; i++) {
- String attrName = attributes[i].name.strValue;
- dis = new DataInputStream(new ByteArrayInputStream(attributes[i].data));
-
- ps.println(" * Attribute "+(i+1)+" is of type "+attributes[i].name);
- if (attrName.compareTo("SourceFile") == 0) {
- cpi = null;
- try {
- cpi = constantPool[dis.readShort()];
- } catch (IOException e) { }
- ps.println(" * SourceFile : "+cpi);
- } else {
- ps.println(" * TYPE ("+attrName+")");
- }
- }
- } else {
- ps.println(" * This class has NO optional class attributes.");
- }
- ps.println(" */\n");
- ps.print(accessString(accessFlags)+"class "+myClassName+" extends "+
- mySuperClassName);
- if (interfaces != null) {
- ps.print(" implements ");
- for (i = 0; i < interfaces.length - 1; i++) {
- ps.print(interfaces[i].arg1.strValue+", ");
- }
- ps.print(interfaces[interfaces.length-1].arg1.strValue);
- }
- ps.println(" {\n");
- if (fields != null) {
- ps.println("/* Instance Variables */");
- for (i = 0; i < fields.length; i++) {
- ps.println(" "+fields[i].toString(constantPool)+";");
- }
- }
-
- if (methods != null) {
- ps.println("\n/* Methods */");
- for (i = 0; i < methods.length; i++) {
- ps.println(" "+methods[i].toString(myClassName));
- }
- }
- ps.println("\n}");
- }
-
- public ConstantPoolInfo getConstantRef(short index) {
- return (constantPool[index]);
- }
-
- /**
- * Add a single constant pool item and return its index.
- * If the item is already in the pool then the index of
- * the <i>preexisting</i> item is returned. Thus you cannot
- * assume that a pointer to your item will be useful.
- */
- public short addConstantPoolItem(ConstantPoolInfo item)
- throws Exception {
- ConstantPoolInfo newConstantPool[];
- ConstantPoolInfo cp;
-
- cp = item.inPool(constantPool);
- if (cp != null)
- return ConstantPoolInfo.indexOf(cp, constantPool);
-
- newConstantPool = new ConstantPoolInfo[constantPool.length+1];
- for (int i = 1; i < constantPool.length; i++) {
- newConstantPool[i] = constantPool[i];
- }
- newConstantPool[constantPool.length] = item;
- constantPool = newConstantPool;
- return ConstantPoolInfo.indexOf(item, constantPool);
- }
-
- /**
- * Add some items to the constant pool. This is used to add new
- * items to the constant pool. The items references in arg1 and
- * arg2 are expected to be valid pointers (if necessary). Pruning
- * is done to prevent adding redundant items to the list and to
- * preserve string space.
- *
- * The algorithm is simple, first identify pool items containing
- * constants in the list of items to be added that are already
- * in the constant pool. If any are found to already exist, change
- * the pointers in the non-constant items to point to the ones in
- * the pool rather than the ones in the list. Next check to see
- * if any of the non-constant items are already in the pool and
- * if so fix up the others in the list to point to the ones in
- * the pool. Finally, add any items (there must be at least one)
- * from the item list that aren't already in the pool, all of
- * the pointers will already be fixed.
- *
- * NOTE: Since constants in the constant pool may be referenced
- * <i>inside</i> the opaque portion of attributes the constant
- * table cannot be re-ordered, only extended.
- */
- public void addConstantPoolItems(ConstantPoolInfo items[]) {
- ConstantPoolInfo newArg;
- ConstantPoolInfo newConstantPool[];
- boolean delete[] = new boolean[items.length];
-
- /* Step one, look for matching constants */
- for (int j = 0; j < items.length; j++) {
- if ( (items[j].type == ConstantPoolInfo.ASCIZ) ||
- (items[j].type == ConstantPoolInfo.UNICODE) ||
- (items[j].type == ConstantPoolInfo.INTEGER) ||
- (items[j].type == ConstantPoolInfo.LONG) ||
- (items[j].type == ConstantPoolInfo.FLOAT) ||
- (items[j].type == ConstantPoolInfo.DOUBLE)) {
-
- // Look for this item in the constant pool
- delete[j] = false;
- newArg = items[j].inPool(constantPool);
- if (newArg != null) {
- // replace the references in our list.
- delete[j] = true; // mark it for deletion
- for (int i = 0; i < items.length; i++) {
- if (items[i].arg1 == items[j])
- items[i].arg1 = newArg;
- if (items[i].arg2 == items[j])
- items[i].arg2 = newArg;
- }
- }
- }
- }
-
- /* Step two : now match everything else */
- for (int j = 0; j < items.length; j++) {
- if ( (items[j].type == ConstantPoolInfo.CLASS) ||
- (items[j].type == ConstantPoolInfo.FIELDREF) ||
- (items[j].type == ConstantPoolInfo.METHODREF) ||
- (items[j].type == ConstantPoolInfo.STRING) ||
- (items[j].type == ConstantPoolInfo.INTERFACE) ||
- (items[j].type == ConstantPoolInfo.NAMEANDTYPE)) {
-
- // Look for this item in the constant pool
- delete[j] = false;
- newArg = items[j].inPool(constantPool);
- if (newArg != null) {
- // replace the references in our list.
- delete[j] = true; // mark it for deletion
- for (int i = 0; i < items.length; i++) {
- if (items[i].arg1 == items[j])
- items[i].arg1 = newArg;
- if (items[i].arg2 == items[j])
- items[i].arg2 = newArg;
- }
- }
- }
- }
-
- /* Step three: Add the surviving items to the pool */
- int count = 0;
- for (int i = 0; i < items.length; i++) {
- if (! delete[i])
- count++;
- }
- // count == # of survivors
- newConstantPool = new ConstantPoolInfo[constantPool.length + count];
- for (int i = 1; i < constantPool.length; i++) {
- newConstantPool[i] = constantPool[i];
- }
- // newConstantPool == existing constantPool
-
- int ndx = 0;
- for (int i = constantPool.length; i < newConstantPool.length; i++) {
- while (delete[ndx])
- ndx++;
- newConstantPool[i] = items[ndx];
- ndx++;
- }
- // newConstantPool == existing + new
- constantPool = newConstantPool;
- // all done.
- }
-
- /**
- * Add a new optional class Attribute.
- *
- * Items is an array of constant pool items that are first added
- * to the constant pool. At a minimum items[0] must be an ASCIZ
- * item with the name of the attribute. If the body of the attribute
- * references constant pool items these should be in the item list
- * as well.
- */
- public void addAttribute(AttributeInfo newAttribute) {
-
- if (attributes == null) {
- attributes = new AttributeInfo[1];
- attributes[0] = newAttribute;
- } else {
- AttributeInfo newAttrList[] = new AttributeInfo[1+attributes.length];
- for (int i = 0; i < attributes.length; i++) {
- newAttrList[i] = attributes[i];
- }
- newAttrList[attributes.length] = newAttribute;
- attributes = newAttrList;
- }
- }
-
- /**
- * Return the attribute named 'name' from the class file.
- */
- public AttributeInfo getAttribute(String name) {
- if (attributes == null)
- return null;
- for (int i = 0; i < attributes.length; i++) {
- if (name.compareTo(attributes[i].name.toString()) == 0)
- return attributes[i];
- }
- return (null);
- }
-
- /**
- * Return a constant pool item from this class. (note does fixup
- * of indexes to facilitate extracting nested or linked items.
- */
- public ConstantPoolInfo getConstantPoolItem(short index)
- throws Exception {
- ConstantPoolInfo cp;
-
- if ((index <= 0) || (index > (constantPool.length - 1)))
- return (null);
- cp = constantPool[index];
- if (cp.arg1 != null)
- cp.index1 = ConstantPoolInfo.indexOf(cp.arg1, constantPool);
- if (cp.arg2 != null)
- cp.index2 = ConstantPoolInfo.indexOf(cp.arg2, constantPool);
- return cp;
- }
-
- /* Examples of mysterious things you can do to a class file before
- * writing it back out. These methods are not currently functional.
- * (that would be too easy :-)
- */
-
- /**
- * Map occurences of class <i>oldClass</i> to occurrences of
- * class <i>newClass</i>. This method is used to retarget
- * accesses to one class, seamlessly to another.
- *
- * The format for the class name is slash (/) separated so
- * the class <tt>util.ClassFile</tt> would be represented as
- * <tt>util/ClassFile</tt>
- */
- public void mapClass(String oldClass, String newClass) {
- if (debug)
- System.out.println("Mapping class name "+oldClass+" ==> "+
- newClass+" for class "+thisClass.arg1);
- for (int i = 0; i < constantPool.length; i++) {
- if (constantPool[i].type == ConstantPoolInfo.CLASS) {
- String cname = constantPool[i].arg1.strValue;
- if (cname.compareTo(oldClass) == 0) {
- if (debug) {
- System.out.println("REPLACING "+cname+" with "+newClass);
- }
- constantPool[i].arg1.strValue = newClass;
- }
- }
- }
- }
-
- /**
- * Map occurences of package <i>oldPackage</i> to package
- * <i>newPackage</i>.
- *
- * The format for the package name is slash (/) separated so
- * the package <tt>java.util</tt> would be represented as
- * <tt>java/util</tt>
- */
- public void mapPackage(String oldPackage, String newPackage) {
- for (int i = 0; i < constantPool.length; i++) {
- if (constantPool[i].type == ConstantPoolInfo.CLASS) {
- String cname = constantPool[i].arg1.strValue;
- if (cname.startsWith(oldPackage)) {
- constantPool[i].arg1.strValue = newPackage +
- cname.substring(cname.lastIndexOf('/'));
- }
- }
- }
- }
-
- /**
- * Delete a named method from this class. This method is used
- * to excise specific methods from the loaded class. The actual
- * method code remains, however the method signature is deleted
- * from the constant pool. If this method is called by a class
- * the exception IncompatibleClassChangeException is generated
- * by the runtime.
- */
- public void deleteMethod(String name, String signature) {
- for (int i = 0; i < constantPool.length; i++) {
- if (constantPool[i].type == ConstantPoolInfo.CLASS) {
- }
- }
- }
- }
-