/*
* 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 defineClass().
*
* @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 in.
*/
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 defineClass 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 preexisting 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
* inside 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 oldClass to occurrences of
* class newClass. 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 util.ClassFile would be represented as
* util/ClassFile
*/
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 oldPackage to package
* newPackage.
*
* The format for the package name is slash (/) separated so
* the package java.util would be represented as
* java/util
*/
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) {
}
}
}
}