/* ClassInfo Copyright (C) 1998-2002 Jochen Hoenicke. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; see the file COPYING.LESSER. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package jode.bytecode; import jode.GlobalOptions; import jode.util.UnifyHash; import java.io.DataInputStream; import java.io.BufferedInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.Enumeration; import @COLLECTIONS@.Iterator; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** * This class does represent a class similar to java.lang.Class. You * can get the super class and the interfaces.
* * The main difference to java.lang.Class is, that the objects are builded * from a stream containing the .class file, and that it uses the * Type to represent types instead of Class itself.
* *

The InnerClasses attribute

* * The InnerClasses attribute is transformed in a special way by this * class so we want to taker a closer look. According to the inner * class specification there must be an InnerClass attribute for * every non top-level class that is referenced somewhere in the * bytecode. This implies that if this is an inner class, it must * contain a inner class attribute for itself. Before a class is * referenced as outer class in an InnerClass attribute, it must be * described by another InnerClass attribute.
* * Since every class references itself, there must be informations * about the outer class for each class scoped class. If that outer * class is an outer class again, there must be information about it, * too. This particular chain of InnerClassInfos is returned by the * getOuterClasses() method; for convenience in reverse order, i.e. * current class first, then the outer classes from innermost to * outermost.
* * A valid bytecode must also contain InnerClass infos for each inner * classes it declares. These information are returned by the * getInnerClasses() method. The order of these classes is the same * as in the bytecode attribute. * * All remaining attributes are returned by getExtraClasses() in the * same order as in the bytecode attribute. * * @author Jochen Hoenicke */ public class ClassInfo extends BinaryInfo { private static SearchPath classpath; private static final UnifyHash classes = new UnifyHash(); private int status = 0; private boolean modified = false; private int modifiers = -1; private boolean deprecatedFlag; private String name; private ClassInfo superclass; private ClassInfo[] interfaces; private FieldInfo[] fields; private MethodInfo[] methods; private InnerClassInfo[] outerClasses; private InnerClassInfo[] innerClasses; private InnerClassInfo[] extraClasses; private String sourceFile; public final static ClassInfo javaLangObject = forName("java.lang.Object"); public static void setClassPath(String path) { setClassPath(new SearchPath(path)); } public static void setClassPath(SearchPath path) { if (classpath != path) { classpath = path; Iterator i = classes.iterator(); while (i.hasNext()) { ClassInfo ci = (ClassInfo) i.next(); ci.status = 0; ci.superclass = null; ci.fields = null; ci.interfaces = null; ci.methods = null; ci.removeAllAttributes(); } } } public static boolean exists(String name) { return classpath.exists(name.replace('.', '/') + ".class"); } public static boolean isPackage(String name) { return classpath.isDirectory(name.replace('.', '/')); } public static Enumeration getClassesAndPackages(final String packageName) { final Enumeration enumeration = classpath.listFiles(packageName.replace('.','/')); return new Enumeration() { public boolean hasMoreElements() { return enumeration.hasMoreElements(); } public Object nextElement() { String name = (String) enumeration.nextElement(); if (!name.endsWith(".class")) // This is a package return name; return name.substring(0, name.length()-6); } }; } public static ClassInfo forName(String name) { if (name == null || name.indexOf(';') != -1 || name.indexOf('[') != -1 || name.indexOf('/') != -1) throw new IllegalArgumentException("Illegal class name: "+name); int hash = name.hashCode(); Iterator iter = classes.iterateHashCode(hash); while (iter.hasNext()) { ClassInfo clazz = (ClassInfo) iter.next(); if (clazz.name.equals(name)) return clazz; } ClassInfo clazz = new ClassInfo(name); classes.put(hash, clazz); return clazz; } private ClassInfo(String name) { this.name = name; } protected void readAttribute(String name, int length, ConstantPool cp, DataInputStream input, int howMuch) throws IOException { if (name.equals("SourceFile")) { if ((howMuch & KNOWNATTRIBS) != 0) { if (length != 2) throw new ClassFormatException("SourceFile attribute" + " has wrong length"); sourceFile = cp.getUTF8(input.readUnsignedShort()); } else input.readFully(new byte[length]); } else if (name.equals("InnerClasses")) { if ((howMuch & (OUTERCLASSES | INNERCLASSES)) != 0) { int count = input.readUnsignedShort(); if (length != 2 + 8 * count) throw new ClassFormatException ("InnerClasses attribute has wrong length"); int innerCount = 0, outerCount = 0, extraCount = 0; InnerClassInfo[] innerClassInfo = new InnerClassInfo[count]; for (int i=0; i< count; i++) { int innerIndex = input.readUnsignedShort(); int outerIndex = input.readUnsignedShort(); int nameIndex = input.readUnsignedShort(); String inner = cp.getClassName(innerIndex); String outer = outerIndex != 0 ? cp.getClassName(outerIndex) : null; String innername = nameIndex != 0 ? cp.getUTF8(nameIndex) : null; int access = input.readUnsignedShort(); if (innername != null && innername.length() == 0) innername = null; /* Some compilers give method scope classes a valid * outer field, but we mustn't handle them as inner * classes. The best way to distinguish this case * is by the class name. */ if (outer != null && innername != null && inner.length() > outer.length() + 2 + innername.length() && inner.startsWith(outer+"$") && inner.endsWith("$"+innername) && Character.isDigit(inner.charAt(outer.length() + 1))) outer = null; InnerClassInfo ici = new InnerClassInfo (inner, outer, innername, access); if (outer != null && outer.equals(getName()) && innername != null) innerClassInfo[innerCount++] = ici; else innerClassInfo[count - (++extraCount)] = ici; } /* Now innerClasses are at the front of innerClassInfo array * in correct order. The other InnerClassInfos are in reverse * order in the rest of the innerClassInfo array. */ /* We now count the outerClasses. The reverse order is the * right thing for us. */ { String lastOuterName = getName(); for (int i = count - extraCount; i < count && lastOuterName != null; i++) { InnerClassInfo ici = innerClassInfo[i]; if (ici.inner.equals(lastOuterName)) { outerCount++; extraCount--; lastOuterName = ici.outer; } } } if (innerCount > 0) { innerClasses = new InnerClassInfo[innerCount]; System.arraycopy(innerClassInfo, 0, innerClasses, 0, innerCount); } else innerClasses = null; if (outerCount > 0) { outerClasses = new InnerClassInfo[outerCount]; } else outerClasses = null; if (extraCount > 0) { extraClasses = new InnerClassInfo[extraCount]; } else extraClasses = null; /* The last part: We split between outer and extra classes. * In this step we will also revert the order of the extra * classes. */ { int outerPtr = 0; String lastOuterName = getName(); for (int i = count - extraCount - outerCount; i < count; i++) { InnerClassInfo ici = innerClassInfo[i]; /* If we counted correctly there is no NullPointer * or ArrayIndexOutOfBoundsException here */ if (ici.inner.equals(lastOuterName)) { outerClasses[outerPtr++] = ici; lastOuterName = ici.outer; } else extraClasses[--extraCount] = ici; } } } else input.readFully(new byte[length]); } else if (name.equals("Deprecated")) { deprecatedFlag = true; if (length != 0) throw new ClassFormatException ("Deprecated attribute has wrong length"); } else super.readAttribute(name, length, cp, input, howMuch); } public void read(DataInputStream input, int howMuch) throws IOException { /* Since we have to read the whole class anyway, we load all * info, that we may need later and that does not take much memory. */ howMuch |= HIERARCHY | INNERCLASSES | OUTERCLASSES; howMuch &= ~status; /* header */ if (input.readInt() != 0xcafebabe) throw new ClassFormatException("Wrong magic"); int version = input.readUnsignedShort(); version |= input.readUnsignedShort() << 16; if (version < (45 << 16 | 0)) throw new ClassFormatException("Wrong class version"); /* constant pool */ ConstantPool cpool = new ConstantPool(); cpool.read(input); /* always read modifiers, name, super, ifaces */ { modifiers = input.readUnsignedShort(); String className = cpool.getClassName(input.readUnsignedShort()); if (!name.equals(className)) throw new ClassFormatException("wrong name " + className); String superName = cpool.getClassName(input.readUnsignedShort()); superclass = superName != null ? ClassInfo.forName(superName) : null; int count = input.readUnsignedShort(); interfaces = new ClassInfo[count]; for (int i=0; i< count; i++) { interfaces[i] = ClassInfo.forName (cpool.getClassName(input.readUnsignedShort())); } status |= HIERARCHY; } /* fields */ if ((howMuch & (FIELDS | KNOWNATTRIBS | UNKNOWNATTRIBS)) != 0) { int count = input.readUnsignedShort(); if ((status & FIELDS) == 0) fields = new FieldInfo[count]; for (int i=0; i< count; i++) { if ((status & FIELDS) == 0) fields[i] = new FieldInfo(this); fields[i].read(cpool, input, howMuch); } } else { byte[] skipBuf = new byte[6]; int count = input.readUnsignedShort(); for (int i=0; i< count; i++) { input.readFully(skipBuf); // modifier, name, type skipAttributes(input); } } /* methods */ if ((howMuch & (METHODS | KNOWNATTRIBS | UNKNOWNATTRIBS)) != 0) { int count = input.readUnsignedShort(); if ((status & METHODS) == 0) methods = new MethodInfo[count]; for (int i=0; i< count; i++) { if ((status & METHODS) == 0) methods[i] = new MethodInfo(this); methods[i].read(cpool, input, howMuch); } } else { byte[] skipBuf = new byte[6]; int count = input.readUnsignedShort(); for (int i=0; i< count; i++) { input.readFully(skipBuf); // modifier, name, type skipAttributes(input); } } /* attributes */ readAttributes(cpool, input, howMuch); status |= howMuch; } public void reserveSmallConstants(GrowableConstantPool gcp) { for (int i=0; i < fields.length; i++) fields[i].reserveSmallConstants(gcp); for (int i=0; i < methods.length; i++) methods[i].reserveSmallConstants(gcp); } public void prepareWriting(GrowableConstantPool gcp) { gcp.putClassName(name); gcp.putClassName(superclass.getName()); for (int i=0; i < interfaces.length; i++) gcp.putClassName(interfaces[i].getName()); for (int i=0; i < fields.length; i++) fields[i].prepareWriting(gcp); for (int i=0; i < methods.length; i++) methods[i].prepareWriting(gcp); if (sourceFile != null) { gcp.putUTF8("SourceFile"); gcp.putUTF8(sourceFile); } if (outerClasses != null || innerClasses != null || extraClasses != null) { gcp.putUTF8("InnerClasses"); int outerCount = outerClasses != null ? outerClasses.length : 0; for (int i=outerCount; i-- > 0;) { gcp.putClassName(outerClasses[i].inner); if (outerClasses[i].outer != null) gcp.putClassName(outerClasses[i].outer); if (outerClasses[i].name != null) gcp.putUTF8(outerClasses[i].name); } int innerCount = innerClasses != null ? innerClasses.length : 0; for (int i=0; i< innerCount; i++) { gcp.putClassName(innerClasses[i].inner); if (innerClasses[i].outer != null) gcp.putClassName(innerClasses[i].outer); if (innerClasses[i].name != null) gcp.putUTF8(innerClasses[i].name); } int extraCount = extraClasses != null ? extraClasses.length : 0; for (int i=0; i< extraCount; i++) { gcp.putClassName(extraClasses[i].inner); if (extraClasses[i].outer != null) gcp.putClassName(extraClasses[i].outer); if (extraClasses[i].name != null) gcp.putUTF8(extraClasses[i].name); } } if (deprecatedFlag) gcp.putUTF8("Deprecated"); prepareAttributes(gcp); } protected int getKnownAttributeCount() { int count = 0; if (sourceFile != null) count++; if (innerClasses != null || outerClasses != null || extraClasses != null) count++; return count; } public void writeKnownAttributes(GrowableConstantPool gcp, DataOutputStream output) throws IOException { if (sourceFile != null) { output.writeShort(gcp.putUTF8("SourceFile")); output.writeInt(2); output.writeShort(gcp.putUTF8(sourceFile)); } if (outerClasses != null || innerClasses != null || extraClasses != null) { output.writeShort(gcp.putUTF8("InnerClasses")); int outerCount = (outerClasses != null) ? outerClasses.length : 0; int innerCount = (innerClasses != null) ? innerClasses.length : 0; int extraCount = (extraClasses != null) ? extraClasses.length : 0; int count = outerCount + innerCount + extraCount; output.writeInt(2 + count * 8); output.writeShort(count); for (int i=outerCount; i-- > 0; ) { output.writeShort(gcp.putClassName(outerClasses[i].inner)); output.writeShort(outerClasses[i].outer != null ? gcp.putClassName(outerClasses[i].outer) : 0); output.writeShort(outerClasses[i].name != null ? gcp.putUTF8(outerClasses[i].name) : 0); output.writeShort(outerClasses[i].modifiers); } for (int i=0; i< innerCount; i++) { output.writeShort(gcp.putClassName(innerClasses[i].inner)); output.writeShort(innerClasses[i].outer != null ? gcp.putClassName(innerClasses[i].outer) : 0); output.writeShort(innerClasses[i].name != null ? gcp.putUTF8(innerClasses[i].name) : 0); output.writeShort(innerClasses[i].modifiers); } for (int i=0; i< extraCount; i++) { output.writeShort(gcp.putClassName(extraClasses[i].inner)); output.writeShort(extraClasses[i].outer != null ? gcp.putClassName(extraClasses[i].outer) : 0); output.writeShort(extraClasses[i].name != null ? gcp.putUTF8(extraClasses[i].name) : 0); output.writeShort(extraClasses[i].modifiers); } } if (deprecatedFlag) { output.writeShort(gcp.putUTF8("Deprecated")); output.writeInt(0); } } public void write(DataOutputStream out) throws IOException { GrowableConstantPool gcp = new GrowableConstantPool(); reserveSmallConstants(gcp); prepareWriting(gcp); out.writeInt(0xcafebabe); out.writeShort(3); out.writeShort(45); gcp.write(out); out.writeShort(modifiers); out.writeShort(gcp.putClassName(name)); out.writeShort(gcp.putClassName(superclass.getName())); out.writeShort(interfaces.length); for (int i=0; i < interfaces.length; i++) out.writeShort(gcp.putClassName(interfaces[i].getName())); out.writeShort(fields.length); for (int i=0; i < fields.length; i++) fields[i].write(gcp, out); out.writeShort(methods.length); for (int i=0; i < methods.length; i++) methods[i].write(gcp, out); writeAttributes(gcp, out); } public void loadInfoReflection(Class clazz, int howMuch) throws SecurityException { if ((howMuch & HIERARCHY) != 0) { modifiers = clazz.getModifiers(); if (clazz.getSuperclass() == null) superclass = clazz == Object.class ? null : javaLangObject; else superclass = ClassInfo.forName (clazz.getSuperclass().getName()); Class[] ifaces = clazz.getInterfaces(); interfaces = new ClassInfo[ifaces.length]; for (int i=0; i= 0; ) { String type = TypeSignature.getSignature(fs[i].getType()); fields[i] = new FieldInfo (this, fs[i].getName(), type, fs[i].getModifiers()); } } if ((howMuch & METHODS) != 0 && methods == null) { Constructor[] cs; Method[] ms; try { cs = clazz.getDeclaredConstructors(); ms = clazz.getDeclaredMethods(); } catch (SecurityException ex) { cs = clazz.getConstructors(); ms = clazz.getMethods(); GlobalOptions.err.println ("Could only get public methods of class " + name + "."); } methods = new MethodInfo[cs.length + ms.length]; for (int i = cs.length; --i >= 0; ) { String type = TypeSignature.getSignature (cs[i].getParameterTypes(), void.class); methods[i] = new MethodInfo (this, "", type, cs[i].getModifiers()); } for (int i = ms.length; --i >= 0; ) { String type = TypeSignature.getSignature (ms[i].getParameterTypes(), ms[i].getReturnType()); methods[cs.length+i] = new MethodInfo (this, ms[i].getName(), type, ms[i].getModifiers()); } } if ((howMuch & INNERCLASSES) != 0 && innerClasses == null) { Class[] is; try { is = clazz.getDeclaredClasses(); } catch (SecurityException ex) { is = clazz.getClasses(); GlobalOptions.err.println ("Could only get public inner classes of class " + name + "."); } if (is.length > 0) { innerClasses = new InnerClassInfo[is.length]; for (int i = is.length; --i >= 0; ) { String inner = is[i].getName(); int dollar = inner.lastIndexOf('$'); String name = inner.substring(dollar+1); innerClasses[i] = new InnerClassInfo (inner, getName(), name, is[i].getModifiers()); } } } if ((howMuch & OUTERCLASSES) != 0 && outerClasses == null) { int count = 0; Class declarer = clazz.getDeclaringClass(); while (declarer != null) { count++; declarer = declarer.getDeclaringClass(); } if (count > 0) { outerClasses = new InnerClassInfo[count]; Class current = clazz; for (int i = 0; i < count; i++) { declarer = current.getDeclaringClass(); String name = current.getName(); int dollar = name.lastIndexOf('$'); outerClasses[i] = new InnerClassInfo (name, declarer.getName(), name.substring(dollar+1), current.getModifiers()); current = declarer; } } } status |= howMuch; } public void loadInfo(int howMuch) { if ((status & howMuch) == howMuch) return; if (modified) { System.err.println("Allocating info 0x" + Integer.toHexString(howMuch) + " (status 0x" + Integer.toHexString(status) + ") in class " + this); Thread.dumpStack(); return; } try { DataInputStream input = new DataInputStream (new BufferedInputStream (classpath.getFile(name.replace('.', '/') + ".class"))); read(input, howMuch); } catch (IOException ex) { String message = ex.getMessage(); if ((howMuch & ~(FIELDS|METHODS|HIERARCHY |INNERCLASSES|OUTERCLASSES)) != 0) { throw new NoClassDefFoundError(name); } // Try getting the info through the reflection interface // instead. Class clazz = null; try { clazz = Class.forName(name); } catch (ClassNotFoundException ex2) { } catch (NoClassDefFoundError ex2) { } try { if (clazz != null) { loadInfoReflection(clazz, howMuch); return; } } catch (SecurityException ex2) { GlobalOptions.err.println (ex2+" while collecting info about class " + name + "."); } // Give a warning and ``guess'' the hierarchie, methods etc. GlobalOptions.err.println ("Can't read class " + name + ", types may be incorrect. (" + ex.getClass().getName() + (message != null ? ": " + message : "") + ")"); ex.printStackTrace(GlobalOptions.err); if ((howMuch & HIERARCHY) != 0) { modifiers = Modifier.PUBLIC; if (name.equals("java.lang.Object")) superclass = null; else superclass = javaLangObject; interfaces = new ClassInfo[0]; } if ((howMuch & METHODS) != 0) methods = new MethodInfo[0]; if ((howMuch & FIELDS) != 0) fields = new FieldInfo[0]; status |= howMuch; } } /** * This is the counter part to loadInfo. It will drop all info specified * in howMuch and clean up the memory. * @param howMuch tells how much info we should drop */ public void dropInfo(int howMuch) { if ((status & howMuch) == 0) return; if (modified) { System.err.println("Dropping info 0x" + Integer.toHexString(howMuch) + " (status 0x" + Integer.toHexString(status) + ") in class " + this); Thread.dumpStack(); return; } howMuch &= status; if ((howMuch & FIELDS) != 0) { fields = null; } else if ((status & FIELDS) != 0 && (howMuch & (KNOWNATTRIBS | UNKNOWNATTRIBS)) != 0) { for (int i=0; i < fields.length; i++) fields[i].dropInfo(howMuch); } if ((howMuch & METHODS) != 0) { methods = null; } else if ((status & METHODS) != 0 && (howMuch & (KNOWNATTRIBS | UNKNOWNATTRIBS)) != 0) { for (int i=0; i < methods.length; i++) methods[i].dropInfo(howMuch); } if ((howMuch & KNOWNATTRIBS) != 0) sourceFile = null; if ((howMuch & OUTERCLASSES) != 0) outerClasses = null; if ((howMuch & INNERCLASSES) != 0) { innerClasses = null; extraClasses = null; } super.dropInfo(howMuch); status &= ~howMuch; } public String getName() { return name; } public String getJavaName() { /* Don't load attributes for class names not containing a * dollar sign. */ if (name.indexOf('$') == -1) return getName(); if (getOuterClasses() != null) { int last = outerClasses.length-1; StringBuffer sb = new StringBuffer(outerClasses[last].outer != null ? outerClasses[last].outer : "METHOD"); for (int i=last; i >= 0; i--) sb.append(".").append(outerClasses[i].name != null ? outerClasses[i].name : "ANONYMOUS"); return sb.toString(); } return getName(); } public ClassInfo getSuperclass() { if ((status & HIERARCHY) == 0) loadInfo(HIERARCHY); return superclass; } public ClassInfo[] getInterfaces() { if ((status & HIERARCHY) == 0) loadInfo(HIERARCHY); return interfaces; } public int getModifiers() { if ((status & HIERARCHY) == 0) loadInfo(HIERARCHY); return modifiers; } public boolean isInterface() { return Modifier.isInterface(getModifiers()); } public boolean isDeprecated() { return deprecatedFlag; } public FieldInfo findField(String name, String typeSig) { if ((status & FIELDS) == 0) loadInfo(FIELDS); for (int i=0; i< fields.length; i++) if (fields[i].getName().equals(name) && fields[i].getType().equals(typeSig)) return fields[i]; return null; } public MethodInfo findMethod(String name, String typeSig) { if ((status & METHODS) == 0) loadInfo(METHODS); for (int i=0; i< methods.length; i++) if (methods[i].getName().equals(name) && methods[i].getType().equals(typeSig)) return methods[i]; return null; } public MethodInfo[] getMethods() { if ((status & METHODS) == 0) loadInfo(METHODS); return methods; } public FieldInfo[] getFields() { if ((status & FIELDS) == 0) loadInfo(FIELDS); return fields; } public InnerClassInfo[] getOuterClasses() { if ((status & OUTERCLASSES) == 0) loadInfo(OUTERCLASSES); return outerClasses; } public InnerClassInfo[] getInnerClasses() { if ((status & INNERCLASSES) == 0) loadInfo(INNERCLASSES); return innerClasses; } public InnerClassInfo[] getExtraClasses() { if ((status & INNERCLASSES) == 0) loadInfo(INNERCLASSES); return extraClasses; } public String getSourceFile() { return sourceFile; } public void setName(String newName) { name = newName; modified = true; } public void setSuperclass(ClassInfo newSuper) { superclass = newSuper; modified = true; } public void setInterfaces(ClassInfo[] newIfaces) { interfaces = newIfaces; modified = true; } public void setModifiers(int newModifiers) { modifiers = newModifiers; modified = true; } public void setDeprecated(boolean flag) { deprecatedFlag = flag; } public void setMethods(MethodInfo[] mi) { methods = mi; modified = true; } public void setFields(FieldInfo[] fi) { fields = fi; modified = true; } public void setOuterClasses(InnerClassInfo[] oc) { outerClasses = oc; modified = true; } public void setInnerClasses(InnerClassInfo[] ic) { innerClasses = ic; modified = true; } public void setExtraClasses(InnerClassInfo[] ec) { extraClasses = ec; modified = true; } public void setSourceFile(String newSource) { sourceFile = newSource; modified = true; } public boolean superClassOf(ClassInfo son) { while (son != this && son != null) { son = son.getSuperclass(); } return son == this; } public boolean implementedBy(ClassInfo clazz) { while (clazz != this && clazz != null) { ClassInfo[] ifaces = clazz.getInterfaces(); for (int i=0; i< ifaces.length; i++) { if (implementedBy(ifaces[i])) return true; } clazz = clazz.getSuperclass(); } return clazz == this; } public String toString() { return name; } }