diff --git a/jode/jode/bytecode/ClassInfo.java.in b/jode/jode/bytecode/ClassInfo.java.in new file mode 100644 index 0000000..81e2fd1 --- /dev/null +++ b/jode/jode/bytecode/ClassInfo.java.in @@ -0,0 +1,793 @@ +/* ClassInfo Copyright (C) 1998-1999 Jochen Hoenicke. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +package jode.bytecode; +import jode.GlobalOptions; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Enumeration; +///#ifdef JDK12 +///import java.lang.ref.WeakReference; +///import java.lang.ref.ReferenceQueue; +///#endif +import @COLLECTIONS@.Map; +import @COLLECTIONS@.HashMap; +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. + * + * @author Jochen Hoenicke + */ +public class ClassInfo extends BinaryInfo { + + private static SearchPath classpath; + private static final Map classes = new HashMap(); +///#ifdef JDK12 +/// private static final ReferenceQueue queue = new ReferenceQueue(); +///#endif + + private int status = 0; + + private boolean modified = false; + + private int modifiers = -1; + 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) { + classpath = new SearchPath(path); +///#ifdef JDK12 +/// java.lang.ref.Reference died; +/// while ((died = queue.poll()) != null) { +/// classes.values().remove(died); +/// } +/// Iterator i = classes.values().iterator(); +/// while (i.hasNext()) { +/// ClassInfo ci = (ClassInfo) ((WeakReference)i.next()).get(); +/// if (ci == null) { +/// i.remove(); +/// continue; +/// } +///#else + Iterator i = classes.values().iterator(); + while (i.hasNext()) { + ClassInfo ci = (ClassInfo) i.next(); +///#endif + 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 enum = + classpath.listFiles(packageName.replace('.','/')); + return new Enumeration() { + public boolean hasMoreElements() { + return enum.hasMoreElements(); + } + public Object nextElement() { + String name = (String) enum.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); + +///#ifdef JDK12 +/// java.lang.ref.Reference died; +/// while ((died = queue.poll()) != null) { +/// classes.values().remove(died); +/// } +/// WeakReference ref = (WeakReference) classes.get(name); +/// ClassInfo clazz = (ref == null) ? null : (ClassInfo) ref.get(); +///#else + ClassInfo clazz = (ClassInfo) classes.get(name); +///#endif + if (clazz == null) { + clazz = new ClassInfo(name); +///#ifdef JDK12 +/// classes.put(name, new WeakReference(clazz, queue)); +///#else + classes.put(name, clazz); +///#endif + } + 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 ((howMuch & ALL_ATTRIBUTES) != 0 && name.equals("SourceFile")) { + if (length != 2) + throw new ClassFormatException("SourceFile attribute" + + " has wrong length"); + sourceFile = cp.getUTF8(input.readUnsignedShort()); + } else if ((howMuch & (OUTERCLASSES | INNERCLASSES)) != 0 + && name.equals("InnerClasses")) { + int count = input.readUnsignedShort(); + 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; + InnerClassInfo ici = new InnerClassInfo + (inner, outer, innername, access); + + if (outer != null && outer.equals(getName()) + && innername != null) + innerClassInfo[innerCount++] = ici; + else + innerClassInfo[count - (++extraCount)] = ici; + } + { + String lastOuterName = getName(); + for (int i = count - extraCount; i < count; i++) { + InnerClassInfo ici = innerClassInfo[i]; + if (ici.inner.equals(lastOuterName)) { + for (int j = i; j > count - extraCount; j--) + innerClassInfo[j] = innerClassInfo[j-1]; + innerClassInfo[count-extraCount] = ici; + extraCount--; + outerCount++; + 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]; + System.arraycopy(innerClassInfo, innerCount, + outerClasses, 0, outerCount); + } else + outerClasses = null; + + if (extraCount > 0) { + extraClasses = new InnerClassInfo[extraCount]; + System.arraycopy(innerClassInfo, innerCount + outerCount, + extraClasses, 0, extraCount); + } else + extraClasses = null; + + if (length != 2 + 8 * count) + throw new ClassFormatException + ("InnerClasses 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 |= FIELDS | METHODS | HIERARCHY | INNERCLASSES | OUTERCLASSES; + howMuch &= ~status; + /* header */ + if (input.readInt() != 0xcafebabe) + throw new ClassFormatException("Wrong magic"); + if (input.readUnsignedShort() > 3) + throw new ClassFormatException("Wrong minor"); + if (input.readUnsignedShort() != 45) + throw new ClassFormatException("Wrong major"); + + /* 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 | ALL_ATTRIBUTES)) != 0) { + int count = input.readUnsignedShort(); + if ((howMuch & FIELDS) != 0) + fields = new FieldInfo[count]; + for (int i=0; i< count; i++) { + if ((howMuch & 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 | ALL_ATTRIBUTES)) != 0) { + int count = input.readUnsignedShort(); + if ((howMuch & METHODS) != 0) + methods = new MethodInfo[count]; + for (int i=0; i< count; i++) { + if ((howMuch & 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); + } + } + 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); + } + } + } + + 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 = null; + 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(classpath.getFile(name.replace('.', '/') + + ".class")); + read(input, howMuch); + + } catch (IOException ex) { + String message = ex.getMessage(); + if ((howMuch & ~(FIELDS|METHODS|HIERARCHY + |INNERCLASSES|OUTERCLASSES)) != 0) { + GlobalOptions.err.println + ("Can't read class " + name + "."); + ex.printStackTrace(GlobalOptions.err); + 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 : "") + ")"); + + 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; + } + } + + 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 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 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; + } +}