/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2010, Stefan Hepp (stefan@stefant.org).
*
* 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 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.common;
import com.jopdesign.common.graphutils.ClassHierarchyTraverser;
import com.jopdesign.common.graphutils.ClassVisitor;
import com.jopdesign.common.logger.LogConfig;
import com.jopdesign.common.misc.JavaClassFormatError;
import com.jopdesign.common.misc.Ternary;
import com.jopdesign.common.tools.ConstantPoolRebuilder;
import com.jopdesign.common.tools.ConstantPoolReferenceFinder;
import com.jopdesign.common.type.ClassRef;
import com.jopdesign.common.type.ConstantInfo;
import com.jopdesign.common.type.Descriptor;
import com.jopdesign.common.type.MemberID;
import com.jopdesign.common.type.MethodRef;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.InnerClasses;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.SourceFile;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldGen;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;
import org.apache.log4j.Logger;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.jopdesign.common.misc.MiscUtils.inArray;
/**
*
* @author Stefan Hepp (stefan@stefant.org)
*/
public final class ClassInfo extends MemberInfo {
private final ClassGen classGen;
private ConstantPoolGen cpg;
protected final Set<ClassInfo> subClasses;
protected ClassInfo superClass;
protected Ternary fullyKnown;
private final Map<String, MethodInfo> methods;
private final Map<String, FieldInfo> fields;
private InnerClassesInfo innerClasses;
private static final Logger logger = Logger.getLogger(LogConfig.LOG_STRUCT + ".ClassInfo");
public ClassInfo(ClassGen classGen) {
super(classGen, new MemberID(classGen.getClassName()));
this.classGen = classGen;
cpg = classGen.getConstantPool();
superClass = null;
subClasses = new LinkedHashSet<ClassInfo>();
fullyKnown = Ternary.UNKNOWN;
innerClasses = new InnerClassesInfo(this, classGen);
Method[] cgMethods = classGen.getMethods();
Field[] cgFields = classGen.getFields();
methods = new LinkedHashMap<String, MethodInfo>(cgMethods.length);
fields = new LinkedHashMap<String, FieldInfo>(cgFields.length);
// we create all FieldInfos and MethodInfos now and save a lot of trouble later
for (Method m : cgMethods) {
MethodInfo method = new MethodInfo(this, new MethodGen(m, classGen.getClassName(), cpg));
methods.put(method.getMethodSignature(), method);
}
for (Field f : cgFields) {
FieldInfo field = new FieldInfo(this, new FieldGen(f, cpg));
fields.put(f.getName(), field);
}
}
//////////////////////////////////////////////////////////////////////////////
// Various getter and setter, modify class
//////////////////////////////////////////////////////////////////////////////
@Override
public ClassInfo getClassInfo() {
return this;
}
@Override
public String getShortName() {
String name = getClassName();
return name.substring(name.lastIndexOf('.')+1);
}
@Override
public String getModifierString() {
String s = super.getModifierString();
if ( isInterface() ) {
s += "interface ";
} else {
s += "class ";
}
return s;
}
/**
* Get the fully qualified name of this class.
* @return the FQN.
*/
public String getClassName() {
return classGen.getClassName();
}
public ClassRef getClassRef() {
return new ClassRef(this);
}
public boolean isAbstract() {
return classGen.isAbstract();
}
public void setAbstract(boolean val) {
classGen.isAbstract(val);
}
public boolean isStrictFP() {
return classGen.isStrictfp();
}
public void setStrictFP(boolean val) {
classGen.isStrictfp(val);
}
public int getMajor() {
return classGen.getMajor();
}
public int getMinor() {
return classGen.getMinor();
}
public void setMinor(int minor) {
classGen.setMinor(minor);
}
public void setMajor(int major) {
classGen.setMajor(major);
}
public void setAnnotation(boolean flag) {
classGen.isAnnotation(flag);
}
public boolean isAnnotation() {
return classGen.isAnnotation();
}
public void setEnum(boolean flag) {
classGen.isEnum(flag);
}
public boolean isEnum() {
return classGen.isEnum();
}
/**
* Check if the super flag is set. This defines the behaviour of invokespecial and should
* be set in every class generated by a modern compiler.
* @return true if the ACC_SUPER flag is set.
*/
public boolean hasSuperFlag() {
return (classGen.getAccessFlags() & Constants.ACC_SUPER) != 0;
}
//////////////////////////////////////////////////////////////////////////////
// Attributes access and lookups
//////////////////////////////////////////////////////////////////////////////
@Override
public Attribute[] getAttributes() {
return classGen.getAttributes();
}
@Override
public void addAttribute(Attribute a) {
classGen.addAttribute(a);
}
@Override
public void removeAttribute(Attribute a) {
classGen.removeAttribute(a);
}
public String getSourceFileName() {
for (Attribute a : getAttributes()) {
if ( a instanceof SourceFile ) {
return ((SourceFile)a).getSourceFileName();
}
}
return null;
}
//////////////////////////////////////////////////////////////////////////////
// Inner-class stuff; some delegates to InnerClassesInfo
//////////////////////////////////////////////////////////////////////////////
/**
* Get the InnerClassesInfo which contains infos about the inner classes, the enclosing classes
* and the InnerClasses attribute.
* @return the InnerClasses attribute manager (even if this class is not an inner class).
*/
public InnerClassesInfo getInnerClassesInfo() {
return innerClasses;
}
/**
* @see InnerClassesInfo#isNestedClass()
* @return true if this class is a nested class (a member- or inner class).
*/
public boolean isNestedClass() {
return innerClasses.isNestedClass();
}
/**
* @see InnerClassesInfo#getEnclosingClassInfo()
* @return the ClassInfo which encloses this class, if it is loaded and if this class is a nested class, else null.
*/
public ClassInfo getEnclosingClassInfo() {
return innerClasses.getEnclosingClassInfo();
}
/**
* @see InnerClassesInfo#getEnclosingMethodRef()
* @return the method reference to the method which encloses this class, or null if this is not a local class.
*/
public MethodRef getEnclosingMethodRef() {
return innerClasses.getEnclosingMethodRef();
}
/**
* @see InnerClassesInfo#getDirectNestedClasses()
* @return a set of all classes directly nested in this class.
*/
public Set<ClassInfo> getDirectNestedClasses() {
return innerClasses.getDirectNestedClasses();
}
/**
* @see InnerClassesInfo#getTopLevelClass()
* @return the top level class enclosing this class or this class if this is a top level class.
*/
public ClassInfo getTopLevelClass() {
return innerClasses.getTopLevelClass();
}
/**
* Check if this class is defined within a method.
* @see InnerClassesInfo#isLocalInnerClass()
* @return true if this class is not a member of the class.
*/
public boolean isLocalInnerClass() {
return innerClasses.isLocalInnerClass();
}
//////////////////////////////////////////////////////////////////////////////
// Access to the constantpool, lookups and modification
//////////////////////////////////////////////////////////////////////////////
public ConstantInfo getConstantInfo(int i) {
if ( i < 1 || i >= cpg.getSize() ) {
return null;
}
Constant c = cpg.getConstant(i);
return ConstantInfo.createFromConstant(cpg.getConstantPool(), c);
}
public ConstantInfo getConstantInfo(Constant c) {
return ConstantInfo.createFromConstant(cpg.getConstantPool(), c);
}
public Constant getConstant(int i) {
return cpg.getConstant(i);
}
public ConstantPoolGen getConstantPoolGen() {
return cpg;
}
/**
* Replace a constant with a new constant value in the constant pool.
* Be aware that this does not check for duplicate entries, and may create additional
* new entries in the constant pool.
*
* @see #addConstantInfo(ConstantInfo)
* @param i the index of the constant to replace.
* @param constant the new value.
*/
public void setConstantInfo(int i, ConstantInfo constant) {
Constant c = constant.createConstant(cpg);
cpg.setConstant(i, c);
}
/**
* Add a constant in the constant pool or return the index of an existing entry.
* To add individual constants, use the add-methods from {@link #getConstantPoolGen()}.
*
* @param constant the constant to add
* @return the index of the constant entry in the constant pool.
*/
public int addConstantInfo(ConstantInfo constant) {
return constant.addConstant(cpg);
}
/**
* Lookup the index of a constant in the constant pool.
* To lookup individual constants, use the lookup-methods from {@link #getConstantPoolGen()}.
*
* @param constant the constant to look up
* @return the index in the constant pool, or -1 if not found.
*/
public int lookupConstantInfo(ConstantInfo constant) {
return constant.lookupConstant(cpg);
}
public int getConstantPoolSize() {
return cpg.getSize();
}
//////////////////////////////////////////////////////////////////////////////
// Superclass and Interfaces
//////////////////////////////////////////////////////////////////////////////
/**
* @return true if this class represents java.lang.Object
*/
public boolean isRootClass() {
return "java.lang.Object".equals(getClassName());
}
public String getSuperClassName() {
return classGen.getSuperclassName();
}
/**
* Get the ClassInfo of the superClass if it is known and if this is not java.lang.Object, else return null.
* @return the superclass ClassInfo or null if not loaded or if this class is java.lang.Object.
*/
public ClassInfo getSuperClassInfo() {
return superClass;
}
/**
* check if a given class is a superclass of this class. This check does not require the given
* superclass to exist in AppInfo. If the given classname is the name of this class, this method
* returns false.
*
* @param className the name of the superclass.
* @param checkInterfaces if true, check the interfaces of this class too.
* @return true if a superclass by this name is found, false if all superclasses have been checked
* and no superclass has been found by that name, or unknown if the full superclass hierarchy is
* not known.
*/
public Ternary hasSuperClass(String className, boolean checkInterfaces) {
// some simple special cases, to get them out of the way..
if (getClassName().equals(className)) return Ternary.FALSE;
if (this.isRootClass()) return Ternary.FALSE;
LinkedList<ClassInfo> list = new LinkedList<ClassInfo>();
list.add(this);
boolean unsafe = false;
while (!list.isEmpty()) {
ClassInfo cls = list.removeFirst();
// check if we find a superclass name match
if (cls.getSuperClassName().equals(className)) return Ternary.TRUE;
if (checkInterfaces) {
if ( inArray(cls.getInterfaceNames(), className) ) return Ternary.TRUE;
}
// push all superclasses to the queue, if they exist
// no need to visit Object, if it did not match, we will not find a matching superclass of Object..
if (!"java.lang.Object".equals(cls.getSuperClassName())) {
ClassInfo superClass = cls.getSuperClassInfo();
// cls never is java.lang.Object, since we checked this at the beginning, so
// if superclass is null (and not Object) we might have an unsafe result
if (superClass == null) {
unsafe = true;
} else {
list.add(superClass);
}
}
if (checkInterfaces) {
Set<ClassInfo> ifs = cls.getInterfaces();
if (ifs.size() != cls.getInterfaceNames().length) {
unsafe = true;
}
list.addAll(ifs);
}
}
return unsafe ? Ternary.UNKNOWN : Ternary.FALSE;
}
public boolean isInterface() {
return classGen.isInterface();
}
/**
* Set interface flag of this class.
*
* <p>This does not check if this is a valid operation, i.e. you need to check
* for yourself if all methods are abstract and public and this class is not a superclass of a
* normal class if you make this class an interface, ... This also does not update the
* class hierarchy, you should call {@link AppInfo#reloadClassHierarchy()} later.</p>
*
* @param val the new value of the interface flag.
*/
public void setInterface(boolean val) {
classGen.isInterface(val);
}
public String[] getInterfaceNames() {
return classGen.getInterfaceNames();
}
/**
* Get a set of all (loaded) interfaces this class directly implements.
*
* @return a set of all known directly implemented classInfos.
*/
public Set<ClassInfo> getInterfaces() {
String[] names = classGen.getInterfaceNames();
Set<ClassInfo> interfaces = new LinkedHashSet<ClassInfo>(names.length);
for (String name : names) {
ClassInfo cls = getAppInfo().getClassInfo(name);
if (cls != null) {
interfaces.add(cls);
}
}
return interfaces;
}
@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"})
public void addInterface(ClassInfo classInfo) {
classGen.addInterface(classInfo.getClassName());
classInfo.subClasses.add(this);
if ( fullyKnown == Ternary.TRUE && isInterface() ) {
if ( classInfo.fullyKnown == Ternary.UNKNOWN ) {
classInfo.updateCompleteFlag(false);
}
fullyKnown = classInfo.fullyKnown;
}
}
public void addInterface(ClassRef classRef) {
ClassInfo cls = classRef.getClassInfo();
if (cls != null) {
addInterface(cls);
} else {
// adding unknown interface
classGen.addInterface(classRef.getClassName());
if ( fullyKnown != Ternary.UNKNOWN && isInterface() ) {
fullyKnown = Ternary.FALSE;
}
}
}
@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"})
public void removeInterface(ClassInfo classInfo) {
classGen.removeInterface(classInfo.getClassName());
classInfo.subClasses.remove(this);
}
public void removeInterface(ClassRef classRef) {
ClassInfo cls = classRef.getClassInfo();
if (cls != null) {
removeInterface(cls);
} else {
// removing unknown interface
classGen.removeInterface(classRef.getClassName());
}
}
//////////////////////////////////////////////////////////////////////////////
// Class-hierarchy lookups and helpers
//////////////////////////////////////////////////////////////////////////////
/**
* Get a set of all known direct subclasses of this class if this is a class,
* or if this is an interface, get a collection of all known direct implementations
* and all known direct extensions.
*
* @return a set of all known direct subclasses and implementations of this class or interface.
*/
public Set<ClassInfo> getDirectSubclasses() {
return subClasses;
}
/**
* Check if all superclasses and outer classes of this class or interface are loaded.
* If checkInterfaces is true or if this is class is an interface, also check if all extended/implemented
* interfaces are loaded.
*
* @param checkInterfaces If this is a class, if true check interfaces too, else only the superclasses are checked.
* If this is an interface, the interfaces of this interface are always checked.
* @return true if all superclasses/interfaces are loaded or if this is 'java.lang.Object'.
*/
public boolean isFullyKnown(boolean checkInterfaces) {
if (checkInterfaces || isInterface()) {
Set<ClassInfo> interfaces = getInterfaces();
if (interfaces.size() < classGen.getInterfaces().length ) {
return false;
}
for (ClassInfo i : interfaces) {
if (!i.isFullyKnown(true)) {
return false;
}
}
}
if (fullyKnown == Ternary.TRUE) {
return true;
}
//noinspection RedundantIfStatement
if (fullyKnown == Ternary.UNKNOWN && "java.lang.Object".equals(classGen.getClassName())) {
return true;
}
return false;
}
/**
* Get a set of all superclasses (including this class) and all implemented/extended interfaces.
*
* @return a set of all superclasses and all interfaces of this class.
*/
public Set<ClassInfo> getAncestors() {
Set<ClassInfo> sc = new LinkedHashSet<ClassInfo>();
List<ClassInfo> queue = new LinkedList<ClassInfo>();
sc.add(this);
queue.add(this);
while (!queue.isEmpty()) {
ClassInfo cls = queue.remove(0);
ClassInfo superClass = cls.getSuperClassInfo();
if ( superClass != null && sc.add(superClass) ) {
queue.add(superClass);
}
for (ClassInfo i : cls.getInterfaces()) {
if ( sc.add(i) ) {
queue.add(i);
}
}
}
return sc;
}
/**
* Check if the given class is the same as this class or a subclass of this class.
* This does not check the implemented interfaces. For interfaces this will always return
* false, even if the given class implements the given interface.
*
* @see #isSubclassOf(ClassInfo)
* @see #isExtensionOf(ClassInfo)
* @param classInfo the possible subclass of this class.
* @return true if the given class is this class or a superclass of this class.
*/
public boolean isSuperclassOf(ClassInfo classInfo) {
ClassInfo cls = classInfo;
while ( cls != null ) {
if ( this.equals(cls) ) {
return true;
}
cls = cls.getSuperClassInfo();
}
return false;
}
/**
* Check if this class is an extension of the given class, i.e. if this is a class,
* check if the given class is a superclass, if this is an interface, check if the given
* class is an interface and if this is an extension of the given interface.
*
* @see #isSubclassOf(ClassInfo)
* @see #isImplementationOf(ClassInfo)
* @param classInfo the class to check.
* @return true if the class is an extension of this class.
*/
public boolean isExtensionOf(ClassInfo classInfo) {
// if this is not an interface, can only extend (super-)classes
if ( !isInterface() ) {
// can only be a superclass if it is not an interface
return !classInfo.isInterface() && classInfo.isSuperclassOf(this);
}
// if classInfo is not a superclass, this can only be an extension if this and classInfo are interfaces
if ( !classInfo.isInterface() ) {
return false;
}
// could use visitor+ClassHierarchyTraverser here to speed things up a little
Set<ClassInfo> interfaces = getAncestors();
return interfaces.contains(classInfo);
}
/**
* Check if this class is an implementation of the given class, i.e. if this class
* is a class, the given class is an interface, and this class implements the given class.
* @param classInfo the interface to check.
* @return true if this class implements the interface.
*/
public boolean isImplementationOf(ClassInfo classInfo) {
// can only implement if this is a class and other class is an interface
if (isInterface() || !classInfo.isInterface()) {
return false;
}
// could use visitor+ClassHierarchyTraverser here to speed things up a little
Set<ClassInfo> interfaces = getAncestors();
return interfaces.contains(classInfo);
}
/**
* Check if this class is either an extension or an implementation of the given class or
* interface.
*
* <p>Note that this is slightly different from {@code isExtensionOf() || isImplementationOf()}, because
* an interface is an instance of java.lang.Object, but it neither implements or extends java.lang.Object.</p>
*
* @see #isExtensionOf(ClassInfo)
* @param classInfo the super class to check.
* @return true if this class is is a subtype of the given class.
*/
public boolean isSubclassOf(ClassInfo classInfo) {
// classes can only be superclasses
if ( !classInfo.isInterface() ) {
return classInfo.isSuperclassOf(this);
}
// classInfo is an interface ..
if ( isInterface() ) {
return this.isExtensionOf(classInfo);
} else {
return this.isImplementationOf(classInfo);
}
}
//////////////////////////////////////////////////////////////////////////////
// Various helper, access checks
//////////////////////////////////////////////////////////////////////////////
public String getPackageName() {
String clsName = getClassName();
int index = clsName.lastIndexOf('.');
if ( index > 0 ) {
return clsName.substring(0, index);
} else {
return "";
}
}
public boolean hasSamePackage(ClassInfo classInfo) {
return getPackageName().equals(classInfo.getPackageName());
}
/**
* Check if this class inherits the given nested class.
* @param classInfo the nested class to check.
* @return true if the class is inherited by this class.
*/
public boolean inherits(ClassInfo classInfo) {
ClassInfo superClass = classInfo.getInnerClassesInfo().getEnclosingSuperClassOf(this, true);
if (superClass == null) {
return false;
}
// canAccess checks if all enclosing classes can be accessed too
return canAccess(classInfo);
}
/**
* Check if this class inherits the given class member.
*
* @param member the member to inherit.
* @param checkInstanceOf if true, check if the member is defined in a superclass or interface of this class,
* else assume that this has already been checked.
* @return true if this class inherits it.
*/
public boolean inherits(ClassMemberInfo member, boolean checkInstanceOf) {
ClassInfo cls = member.getClassInfo();
if ( checkInstanceOf && !isSubclassOf(cls) ) {
return false;
}
return canAccess(cls, member.getAccessType());
}
//////////////////////////////////////////////////////////////////////////////
// Access to fields and methods, lookups
//////////////////////////////////////////////////////////////////////////////
public ClassMemberInfo getMemberInfo(MemberID memberID) {
return getMemberInfo(memberID.hasMethodSignature() ? memberID.getMethodSignature() : memberID.getMemberName());
}
/**
* @param memberSignature either a field name or a method signature (short name and descriptor).
* @return the member info for this signature or null if not found.
*/
public ClassMemberInfo getMemberInfo(String memberSignature) {
MethodInfo method = methods.get(memberSignature);
if (method != null) {
return method;
}
return fields.get(memberSignature);
}
public FieldInfo getFieldInfo(MemberID memberID) {
return getFieldInfo(memberID.getMemberName());
}
public FieldInfo getFieldInfo(String name) {
return fields.get(name);
}
public MethodInfo getMethodInfo(MemberID memberID) {
if (memberID.hasMethodSignature()) {
return getMethodInfo(memberID.getMethodSignature());
}
Set<MethodInfo> methods = getMethodByName(memberID.getMemberName());
if (methods.size() == 1) {
return methods.iterator().next();
}
// not found or not unique
return null;
}
public Set<MethodInfo> getMethodInfos(MemberID memberID) {
if (memberID.hasMethodSignature()) {
MethodInfo method = getMethodInfo(memberID.getMethodSignature());
return method != null ? Collections.singleton(method) : Collections.<MethodInfo>emptySet();
}
return getMethodByName(memberID.getMemberName());
}
/**
* Get the method with the given member signature (e.g. {@code "foo(I)V"}).
*
* @param methodSignature the signature of the method without the classname.
* @return the method or null if it does not exist.
*/
public MethodInfo getMethodInfo(String methodSignature) {
return methods.get(methodSignature);
}
public Set<MethodInfo> getMethodByName(String name) {
Set<MethodInfo> mList = new LinkedHashSet<MethodInfo>();
for (MethodInfo m : methods.values()) {
if (m.getShortName().equals(name)) {
mList.add(m);
}
}
return mList;
}
/**
* Return a collection of all fields of this class.
* You really should not modify this list directly.
*
* @return a collection of all fields.
*/
public Collection<FieldInfo> getFields() {
return Collections.unmodifiableCollection(fields.values());
}
/**
* Return a collection of all methods of this class.
* You really should not modify this list directly.
*
* @return a collection of all methods.
*/
public Collection<MethodInfo> getMethods() {
return Collections.unmodifiableCollection(methods.values());
}
public Collection<String> getMethodSignatures() {
return Collections.unmodifiableCollection(methods.keySet());
}
/**
* Find a method of this class or in the superclasses of this class.
* If no such method is found, look in the interfaces too and return the first found method.
* This method therefore always returns an inherited non-abstract method if it exists, even if the method
* is also defined in an implemented interface.
*
* @param memberID the memberID of the method to find. The classname in the memberID is ignored.
* @param checkAccess if false, also return non-accessible or static methods in superclasses.
* @return the MethodInfo with the given memberID in this class or its extended classes, or null if not found.
*/
public MethodInfo getMethodInfoInherited(MemberID memberID, boolean checkAccess) {
return getMethodInfoInherited(memberID.getMethodSignature(), checkAccess);
}
/**
* Find a method of this class or in the superclasses of this class.
* If no such method is found, look in the interfaces too and return the first found method.
* This method therefore always returns an inherited non-abstract method if it exists, even if the method
* is also defined in an implemented interface.
*
* @param methodSignature the signature of the method to find.
* @param checkAccess if false, also return non-accessible or static methods in superclasses.
* @return the MethodInfo with the given signature in this class or its extended classes, or null if not found.
*/
public MethodInfo getMethodInfoInherited(String methodSignature, boolean checkAccess) {
// first, lets look at all superclasses, so that we find the implementation first if the method
// is also defined in an interface
ClassInfo cls = this;
while ( cls != null ) {
MethodInfo m = cls.getMethodInfo(methodSignature);
if ( m != null ) {
if ( !checkAccess || inherits(m, false) ) {
return m;
} else {
// if we find a method but we do not override this method, no need to look in
// superclasses, but we may find something in the interfaces
break;
}
}
cls = cls.getSuperClassInfo();
}
// not very nice, but works: get all ancestors, look in interfaces
for (ClassInfo i : getAncestors()) {
if (!i.isInterface()) continue;
MethodInfo m = i.getMethodInfo(methodSignature);
if ( m != null ) {
// we always inherit from interfaces, no need to check
return m;
}
}
return null;
}
public FieldInfo getFieldInfoInherited(String name, boolean checkAccess) {
ClassInfo cls = this;
while ( cls != null ) {
FieldInfo f = cls.getFieldInfo(name);
if ( f != null ) {
if ( !checkAccess || inherits(f,false) ) {
return f;
} else {
break;
}
}
cls = cls.getSuperClassInfo();
}
// not very nice, but works: get all ancestors, look in interfaces
for (ClassInfo i : getAncestors()) {
FieldInfo f = i.getFieldInfo(name);
if ( f != null ) {
return f;
}
}
return null;
}
/**
* Get the index of the given method in the class method array, or -1 if not found.
*
* @param memberSignature name and descriptor of the method to find.
* @return the index in the methods array or -1 if not found.
*/
public int lookupMethodInfo(String memberSignature) {
Method[] methods = classGen.getMethods();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
String s = MemberID.getMethodSignature(m.getName(), m.getSignature());
if ( s.equals(memberSignature) ) {
return i;
}
}
return -1;
}
/**
* Get the index of the given field in the class field array, or -1 if not found.
*
* @param fieldName the name of the field to found.
* @return the index in the fields array or -1 if not found.
*/
public int lookupFieldInfo(String fieldName) {
Field[] fields = classGen.getFields();
for (int i = 0; i < fields.length; i++) {
Field f = fields[i];
if ( f.getName().equals(fieldName) ) {
return i;
}
}
return -1;
}
//////////////////////////////////////////////////////////////////////////////
// Modify fields and methods (create, copy, rename, remove)
//////////////////////////////////////////////////////////////////////////////
/**
* Create a new non-static, package-visible field with the given name and type.
* If a field by that name exists, the existing field is returned.
*
* @param name the name of the new field
* @param type the type of the field
* @return the new field or an existing field with that name.
*/
public FieldInfo createField(String name, Type type) {
FieldInfo field = fields.get(name);
if ( field != null ) {
return field;
}
field = new FieldInfo(this, new FieldGen(0, type, name, cpg));
fields.put(name,field);
classGen.addField(field.getField());
// TODO call manager eventhandler
return field;
}
/**
* Create a new non-static, abstract, package-visible method with the given name and descriptor.
*
* @param memberID the membername and descriptor of the method (classname is ignored).
* @param argNames the names of the parameters
* @return the new method or an existing method with that memberID.
*/
public MethodInfo createMethod(MemberID memberID, String[] argNames) {
return createMethod(memberID, argNames, null);
}
/**
* Create a new non-static, package-visible method with the given name and descriptor.
*
* @param memberID the membername and descriptor of the method (classname is ignored).
* @param argNames the names of the parameters
* @param code an InstructionList to set to the method as code, or if null, create an abstract method.
* @return the new method or an existing method with that memberID.
*/
public MethodInfo createMethod(MemberID memberID, String[] argNames, InstructionList code) {
MethodInfo method = methods.get(memberID.getMethodSignature());
if ( method != null ) {
method.setAbstract(code == null);
if (code != null) {
method.getCode().setInstructionList(code);
}
return method;
}
Descriptor desc = memberID.getDescriptor();
int flags = (code == null) ? Constants.ACC_ABSTRACT : 0;
method = new MethodInfo(this, new MethodGen(flags, desc.getType(), desc.getArgumentTypes(), argNames,
memberID.getMemberName(), classGen.getClassName(), code, cpg));
methods.put(memberID.getMethodSignature(), method);
classGen.addMethod(method.getMethod(false));
// TODO call manager eventhandler
return method;
}
public MethodInfo copyMethod(String memberSignature, String newName) {
MethodInfo method = methods.get(memberSignature);
if ( method == null ) {
return null;
}
MethodGen methodGen = new MethodGen(method.compile(), getClassName(), cpg);
methodGen.setName(newName);
MethodInfo newMethod = new MethodInfo(this, methodGen);
// TODO copy all the attribute stuff, call manager eventhandler
methods.put(newMethod.getMethodSignature(), newMethod);
classGen.addMethod(newMethod.getMethod(false));
return newMethod;
}
public FieldInfo copyField(String name, String newName) {
FieldInfo field = fields.get(name);
if ( field == null ) {
return null;
}
FieldGen fieldGen = new FieldGen(field.getField(), cpg);
fieldGen.setName(newName);
FieldInfo newField = new FieldInfo(this, fieldGen);
// TODO copy all the attribute stuff, call manager eventhandler
fields.put(newName, newField);
classGen.addField(newField.getField());
return newField;
}
public MethodInfo renameMethod(String memberSignature, String newName) {
MethodInfo method = methods.remove(memberSignature);
if ( method == null ) {
return null;
}
method.getInternalMethodGen().setName(newName);
methods.put(method.getMethodSignature(), method);
int i = lookupMethodInfo(memberSignature);
if ( i == -1 ) {
// This should never happen
throw new JavaClassFormatError("Renaming method "+memberSignature+" in " +getClassName()+ " to " + newName
+", but old method was not found in classGen!");
}
classGen.setMethodAt(method.getMethod(false), i);
// TODO call manager eventhandler
return method;
}
public FieldInfo renameField(String name, String newName) {
FieldInfo field = fields.remove(name);
if ( field == null ) {
return null;
}
field.getInternalFieldGen().setName(newName);
fields.put(newName, field);
int i = lookupFieldInfo(name);
if ( i == -1 ) {
// This should never happen
throw new JavaClassFormatError("Renaming field "+name+" in " +getClassName()+ " to " + newName
+", but old field was not found in classGen!");
}
// Damn, BCEL does not have a setFieldAt method
classGen.removeField(classGen.getFields()[i]);
classGen.addField(field.getField());
// TODO call manager eventhandler
return field;
}
public FieldInfo removeField(String name) {
FieldInfo fieldInfo = fields.remove(name);
if ( fieldInfo == null ) {
return null;
}
int i = lookupFieldInfo(name);
if ( i == -1 ) {
// this should never happen
throw new JavaClassFormatError("Removing field "+name+" in " +getClassName()
+", but field was not found in classGen!");
}
classGen.removeField(classGen.getFields()[i]);
for (AppEventHandler e : getAppInfo().getEventHandlers()) {
e.onRemoveField(fieldInfo);
}
return fieldInfo;
}
public MethodInfo removeMethod(String memberSignature) {
MethodInfo methodInfo = methods.remove(memberSignature);
if ( methodInfo == null ) {
return null;
}
int i = lookupMethodInfo(memberSignature);
if ( i == -1 ) {
// this should never happen
throw new JavaClassFormatError("Removing method "+memberSignature+" in " +getClassName()
+", but method was not found in classGen!");
}
classGen.removeMethod(classGen.getMethodAt(i));
for (AppEventHandler e : getAppInfo().getEventHandlers()) {
e.onRemoveMethod(methodInfo);
}
return methodInfo;
}
//////////////////////////////////////////////////////////////////////////////
// BCEL stuff
//////////////////////////////////////////////////////////////////////////////
/**
* Rebuild the constantpool from a new, empty constantpool.
*
* <p>This updates the indices of all references in the code of all methods of this class,
* therefore do not call this method while modifying the code.</p>
* <p>
* Note that the ConstantPoolRebuilder implements ClassVisitor, so you can also use
* {@link AppInfo#iterate(ClassVisitor)} to apply it to all classes.
* </p>
*
* @param rebuilder the builder to use to rebuild the pool.
*/
public void rebuildConstantPool(ConstantPoolRebuilder rebuilder) {
// this will compile the classInfo
Set<Integer> usedIndices = ConstantPoolReferenceFinder.findPoolReferences(this, true);
cpg = rebuilder.createNewConstantPool(cpg, usedIndices);
rebuilder.updateClassGen(this, classGen);
for (MethodInfo m : methods.values()) {
rebuilder.updateMethodGen(m, m.getInternalMethodGen());
}
for (FieldInfo f : fields.values()) {
rebuilder.updateFieldGen(f, f.getInternalFieldGen());
}
}
/**
* Rebuild the InnerClasses attribute of this class.
* Add or update all references to nested classes found in this class in the InnerClasses attribute.
*/
public void rebuildInnerClasses() {
InnerClasses oldIC = innerClasses.getInnerClassesAttribute();
InnerClasses newIC = innerClasses.buildInnerClassesAttribute();
if (oldIC != null) classGen.removeAttribute(oldIC);
if (newIC != null) classGen.addAttribute(newIC);
}
/**
* Commit all modifications to this ClassInfo and return a BCEL JavaClass for this ClassInfo.
* <p>
* You may want to call {@link #rebuildConstantPool(ConstantPoolRebuilder)} and {@link #rebuildInnerClasses()} first if needed.
* </p>
* @see MethodInfo#compile()
* @see #rebuildInnerClasses()
* @see #rebuildConstantPool(ConstantPoolRebuilder)
* @see #getJavaClass()
* @return a JavaClass representing this ClassInfo.
*/
public JavaClass compile() {
// We could keep a modified flag in both MethodInfo and FieldInfo
// (maybe even ClassInfo), and update only what is needed here
// could make class-writing,.. faster, but makes code more complex
Field[] fList = classGen.getFields();
if (fList.length != fields.size() ) {
// should never happen
throw new JavaClassFormatError("Number of fields in classGen of " + getClassName() +
" differs from number of FieldInfos!");
}
for (Field f : fList) {
classGen.replaceField(f, fields.get(f.getName()).getField());
}
Method[] mList = classGen.getMethods();
if (mList.length != methods.size()) {
// should never happen
throw new JavaClassFormatError("Number of methods in classGen of " + getClassName() +
" differs from number of MethodInfos!");
}
for (int i = 0; i < mList.length; i++) {
MethodInfo method = methods.get(MemberID.getMethodSignature(mList[i].getName(),
mList[i].getSignature()));
classGen.setMethodAt(method.compile(), i);
}
// TODO call manager eventhandler
return classGen.getJavaClass();
}
/**
* Create and return a BCEL JavaClass for this ClassInfo.
*
* <p>The returned JavaClass does not contain any modifications not yet
* commited to the internal BCEL ClassGen (e.g. it does not contain
* modifications to methods/fields/code).</p>
*
* @see #compile()
* @return a JavaClass for this ClassInfo.
*/
public JavaClass getJavaClass() {
return classGen.getJavaClass();
}
//////////////////////////////////////////////////////////////////////////////
// hashCode, equals
//////////////////////////////////////////////////////////////////////////////
@Override
public int hashCode() {
return classGen.getClassName().hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof ClassInfo && ((ClassInfo) o).getClassName().equals(getClassName());
}
//////////////////////////////////////////////////////////////////////////////
// Internal affairs, class hierarchy management; To be used only be AppInfo
//////////////////////////////////////////////////////////////////////////////
protected void resetHierarchyInfos() {
superClass = null;
subClasses.clear();
fullyKnown = Ternary.UNKNOWN;
innerClasses.resetHierarchyInfos();
}
@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"})
protected void updateClassHierarchy() {
AppInfo appInfo = AppInfo.getSingleton();
if ( isRootClass() ) {
superClass = null;
} else {
superClass = appInfo.getClassInfo(classGen.getSuperclassName());
}
if ( superClass != null ) {
superClass.subClasses.add(this);
}
for (ClassInfo i : getInterfaces()) {
i.subClasses.add(this);
}
// set the outer class
innerClasses.updateClassHierarchy();
}
@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"})
protected void updateCompleteFlag(boolean updateSubclasses) {
if ( isInterface() ) {
// need to check all extended interfaces
Set<ClassInfo> interfaces = getInterfaces();
if ( interfaces.size() < classGen.getInterfaceNames().length ) {
// some interfaces are not loaded
fullyKnown = Ternary.FALSE;
} else {
boolean known = true;
// if this interface extends no other interface, 'known' will stay true
// else we check if all extended interfaces are known
for (ClassInfo i : interfaces) {
// update interfaces recursively first
if ( i.fullyKnown == Ternary.UNKNOWN ) {
i.updateCompleteFlag(false);
}
known &= i.fullyKnown == Ternary.TRUE;
}
fullyKnown = Ternary.valueOf(known);
}
} else if ( superClass == null ) {
fullyKnown = Ternary.valueOf(isRootClass());
} else {
// if superclass is unknown, update recursively first
if ( superClass.fullyKnown == Ternary.UNKNOWN ) {
superClass.updateCompleteFlag(false);
}
fullyKnown = superClass.fullyKnown;
}
// We require that all enclosing classes are at least loaded.
// On the other hand, we do not need them to be completely known, therefore
// the enclosing classes do not affect the fullyKnown flag and do not need to be updated.
if ( updateSubclasses ) {
// we need to recurse down here since the flag might depend on other interfaces- and
// outer-classes too
for (ClassInfo c : subClasses) {
c.updateCompleteFlag(true);
}
}
}
@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"})
protected void removeFromClassHierarchy() {
if ( superClass != null ) {
superClass.subClasses.remove(this);
}
// direct subclasses of an interface can be other interfaces, which have java.lang.Object as superclass,
// or implementing classes, which have a class as superclass, so no need to update them if this is an interface
if ( !isInterface() ) {
// direct subclasses of a class are only classes, so
// all their superclasses must be this class, so unset them
for (ClassInfo c : subClasses) {
c.superClass = null;
}
}
innerClasses.removeFromClassHierarchy();
}
protected void finishRemoveFromHierarchy() {
// all extensions and inner classes of this class will now be incomplete
if ( fullyKnown == Ternary.TRUE ) {
ClassVisitor visitor = new ClassVisitor() {
@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"})
public boolean visitClass(ClassInfo classInfo) {
classInfo.fullyKnown = Ternary.FALSE;
return true;
}
public void finishClass(ClassInfo classInfo) {
}
};
ClassHierarchyTraverser traverser = new ClassHierarchyTraverser(visitor);
traverser.setVisitSubclasses(true, false);
// we do not support nested classes without enclosing class, so no need to visit them
// as they are removed too
traverser.setVisitInnerClasses(false);
traverser.traverseDown(this);
}
}
}