/* * 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.bcel.EnclosingMethod; import com.jopdesign.common.misc.AppInfoError; import com.jopdesign.common.misc.JavaClassFormatError; import com.jopdesign.common.tools.ConstantPoolReferenceFinder; import com.jopdesign.common.type.ClassRef; import com.jopdesign.common.type.ConstantClassInfo; import com.jopdesign.common.type.MethodRef; import org.apache.bcel.classfile.Attribute; import org.apache.bcel.classfile.ConstantUtf8; import org.apache.bcel.classfile.InnerClass; import org.apache.bcel.classfile.InnerClasses; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.ConstantPoolGen; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * @author Stefan Hepp (stefan@stefant.org) */ public class InnerClassesInfo { private final ClassInfo classInfo; private final ClassGen classGen; private ClassInfo enclosingClass; private final Set<ClassInfo> nestedClasses; private boolean aNestedClass; public InnerClassesInfo(ClassInfo classInfo, ClassGen classGen) { this.classInfo = classInfo; this.classGen = classGen; aNestedClass = false; enclosingClass = null; nestedClasses = new HashSet<ClassInfo>(); updateInnerClassFlag(); } public ClassInfo getClassInfo() { return classInfo; } public String getClassName() { return classInfo.getClassName(); } public InnerClasses getInnerClassesAttribute() { // we could keep a reference to the attribute in the class, but this should be sufficiently fast. for (Attribute a : classGen.getAttributes()) { if ( a instanceof InnerClasses ) { return (InnerClasses) a; } } return null; } public InnerClass getInnerClassAttribute(String innerClassName) { InnerClasses ic = getInnerClassesAttribute(); return findInnerClass(ic, innerClassName); } public String getInnerClassName(InnerClass i) { return ((ConstantClassInfo) classInfo.getConstantInfo(i.getInnerClassIndex())).getClassName(); } public String getOuterClassName(InnerClass i) { int index = i.getOuterClassIndex(); if ( index == 0 ) { return null; } return ((ConstantClassInfo) classInfo.getConstantInfo(index)).getClassName(); } /** * Get the simple name of a class if it is a non-anonymous class. * @param i the attribute corresponding to the nested class. * @return the member name of the nested class or null if anonymous. */ public String getInnerName(InnerClass i) { int index = i.getInnerNameIndex(); if ( index == 0 ) { return null; } return ((ConstantUtf8)classInfo.getConstant(index)).getBytes(); } /** * Get the simple inner name of this class if this is an non-anonymous inner class. * @return return the simple inner name of this class or null if this is an anonymous class or not a nested class. */ public String getInnerName() { InnerClass attribute = getInnerClassAttribute(classInfo.getClassName()); if ( attribute == null ) { return null; } return getInnerName(attribute); } ////////////////////////////////////////////////////////////////////////////// // Inner-class access and analyze stuff ////////////////////////////////////////////////////////////////////////////// /** * Check if this class is a nested class. * @return true if this class is a nested class (member or inner class, anonymous or not). */ public boolean isNestedClass() { return aNestedClass; } /** * An inner class is a non-static nested class. * @return true if this a non-static nested class. */ public boolean isInnerClass() { return aNestedClass && !classInfo.isStatic(); } /** * Check if this class is an anonymous inner class (i.e. a class defined within a method without a class name). * @return true if this class has no name. */ public boolean isAnonymousInnerClass() { if (!aNestedClass) return false; InnerClass i = getInnerClassAttribute(getClassName()); return i != null && i.getInnerNameIndex() == 0; } /** * Check if this is a local class (i.e a class defined within a method, not a member of the outer class). * @return true if this class is not a member of a class. */ public boolean isLocalInnerClass() { if (!aNestedClass) return false; InnerClass i = getInnerClassAttribute(getClassName()); return i != null && i.getOuterClassIndex() == 0; } /** * Check if the given classRef is a nested class (not necessarily of this class!), * using the InnerClasses attribute of this class if no ClassInfo is available for the reference. * * @param classRef the class to check using the InnerClasses attribute of this class. * @return true if the referenced class is a nested class or if this class knows the class as nested class. */ public boolean isNestedClass(ClassRef classRef) { ClassInfo classInfo = classRef.getClassInfo(); if ( classInfo != null ) { return classInfo.isNestedClass(); } InnerClass ic = getInnerClassAttribute(classRef.getClassName()); return ic != null; } /** * Check if the given class is an enclosing class of this class. * This requires the class hierarchy to be up-to-date and all outer classes must be loaded. * * @param enclosing the potential outer class. * @param membersOnly only return true if this class and all enclosing classes up to enclosing are member classes. * @return true if this class is the same as or a nested class of the given class. */ public boolean isNestedClassOf(ClassInfo enclosing, boolean membersOnly) { return isNestedClassOf(enclosing.getClassName(), membersOnly); } /** * Check if the given class is an outer class of this class. * This requires the class hierarchy to be up-to-date and all outer classes must be loaded. * * @param enclosingClassName the fully qualified name of the potential outer class. * @param membersOnly only return true if this class and all enclosing classes up to outer are member classes. * @return true if this class is the same as or a nested class of the given class. */ public boolean isNestedClassOf(String enclosingClassName, boolean membersOnly) { if ( !aNestedClass) { return false; } // We could even handle some cases (when membersOnly is true) even without knowing the // enclosing ClassInfos using the InnerClasses attribute, but to make life easier we simply // require that all enclosing classes are loaded and the class hierarchy is up-to-date. ClassInfo outer = classInfo; while (outer != null) { if (outer.getClassName().equals(enclosingClassName)) { return true; } outer = outer.getEnclosingClassInfo(); if (membersOnly && outer.isLocalInnerClass()) { return false; } } return false; } /** * Get the name of the immediatly enclosing class of this class, if this is a nested class * (member or local), or null if this is not a nested class. * * @see #getOuterClassName() * @return the immediatly enclosing class of this class, or null if this is a top-level class. */ public String getEnclosingClassName() { if ( !aNestedClass ) { return null; } if (enclosingClass != null) { return enclosingClass.getClassName(); } String name = getOuterClassName(); if ( name == null ) { EnclosingMethod m = getEnclosingMethod(); if (m != null) { return m.getClassName(); } else { throw new JavaClassFormatError("Could not find enclosing class name for nested class " +getClassName()); } } else { return name; } } /** * Get the immediatly enclosing class of this class. * * @return the immediatly enclosing class of this class, or null if this is a top-level class. */ public ClassInfo getEnclosingClassInfo() { return enclosingClass; } /** * Get a collection of all known nested classes of this class. * This may return a subset of {@link #getDirectInnerClassNames()} if not all * directly nested classes are loaded. * * @return a set of all known nested classes (member and local classes). */ public Set<ClassInfo> getDirectNestedClasses() { return nestedClasses; } /** * Get the top level enclosing class, or this class itself if this class is a top level class. * * @return the top level class for this class. */ public ClassInfo getTopLevelClass() { ClassInfo top = classInfo; while (top.isNestedClass()) { top = top.getEnclosingClassInfo(); } return top; } /** * Get the name of the outer class of this class if this is a nested member class. * * @see #getEnclosingClassName() * @return the name of the outer class as defined in the InnerClasses attribute if this is a member * nested class, else null. */ public String getOuterClassName() { if ( !aNestedClass ) { return null; } InnerClass i = getInnerClassAttribute(getClassName()); if (i == null) { throw new JavaClassFormatError("Someone removed the InnerClasses attribute of this nested class!"); } return getOuterClassName(i); } /** * Get a list of fully qualified classnames of all direct inner classes of this class, including local classes, * using the InnerClasses attribute. * * @return a collection of fully qualified classnames of the inner classes or an empty collection if this class * has no inner classes. */ public Collection<String> getDirectInnerClassNames() { InnerClasses ic = getInnerClassesAttribute(); if ( ic == null ) { return new LinkedList<String>(); } List<String> inner = new LinkedList<String>(); for (InnerClass i : ic.getInnerClasses()) { String outerName = getOuterClassName(i); if (getInnerClassName(i).equals(getClassName())) { continue; } // if outer is null, inner is a local inner class of this class. if (outerName == null || getClassName().equals(outerName)) { inner.add(getInnerClassName(i)); } } return inner; } /** * Find the class enclosing this class which is the same as or a superclass or an interface of * the given class. If the given class is a subclass of this class, this returns null. * * @param classInfo the (sub)class containing this class. * @param membersOnly if true, only check outer classes of member inner classes. * @return the found enclosing class or null if none found. */ public ClassInfo getEnclosingSuperClassOf(ClassInfo classInfo, boolean membersOnly) { if ( membersOnly && isLocalInnerClass() ) { return null; } ClassInfo outer = enclosingClass; while (outer != null) { if (classInfo.isSubclassOf(outer)) { return outer; } if ( membersOnly && outer.isLocalInnerClass() ) { return null; } else { outer = outer.getEnclosingClassInfo(); } } return null; } public EnclosingMethod getEnclosingMethod() { for (Attribute a : classInfo.getAttributes()) { if ( a instanceof EnclosingMethod) { return ((EnclosingMethod)a); } } return null; } public MethodRef getEnclosingMethodRef() { EnclosingMethod m = getEnclosingMethod(); return m == null ? null : m.getMethodRef(); } /* * Make this class a member nested class of the given class or move it to top-level * * @param enclosingClass the new outer class of this class, or null to make it a toplevel class. * @param innerName the simple name of this member class. */ /* public void setEnclosingClass(ClassInfo enclosingClass, String innerName) throws AppInfoException { // implement setOuterClass(), if needed. But this is not a simple task. // need to // - remove references to old outerClass from InnerClasses of this and all old outer classes // - create InnerClasses attribute if none exists // - add new entries to InnerClasses of this and all new outer classes. // - update class hierarchy infos and fullyKnown flags // - handle setEnclosingClass(null) } */ /* * Make this class a local nested class of the given method. * @param methodInfo the enclosing method of this class. */ /* public void setEnclosingMethod(MethodInfo methodInfo) { } */ /* * Add or replace InnerClass entries of this class with the info of the given classes. * If no InnerClasses attribute exists, it is created. Enclosing classes are added if needed. * * @param nestedClasses a list of nested classes to add to the InnerClasses attribute. */ /* public void addInnerClassRefs(Collection<ClassRef> nestedClasses) { if ( nestedClasses == null || nestedClasses.isEmpty() ) { return; } Map<String, InnerClass> classes = buildInnerClasses(nestedClasses); // create an InnerClasses attribute if it does not exist // add all InnerClasses to the existing InnerClasses (replace entries if existing) } */ /** * Build a new InnerClasses Attribute containing entries for all inner classes referenced by the current * constantpool, using the current ClassInfos or the old InnerClasses attribute to build the table. * * @return a new InnerClasses attribute or null if this class does not reference any inner classes. */ public InnerClasses buildInnerClassesAttribute() { ConstantPoolGen cpg = classGen.getConstantPool(); // check+update InnerClasses attribute (and add referenced nested classes) List<ClassRef> referencedClasses = new LinkedList<ClassRef>(); for (String name : ConstantPoolReferenceFinder.findReferencedClasses(classInfo)) { referencedClasses.add(classInfo.getAppInfo().getClassRef(name)); } // find all referenced classes recursively, build InnerClass list from ClassInfo or old InnerClasses Collection<InnerClass> classes = buildInnerClasses(referencedClasses); if (classes.isEmpty()) { return null; } InnerClass[] ics = null; ics = classes.toArray(ics); int length = ics.length * 8 + 2; return new InnerClasses(cpg.addUtf8("InnerClasses"), length, ics, cpg.getConstantPool()); } ////////////////////////////////////////////////////////////////////////////// // private methods and maintenance code ////////////////////////////////////////////////////////////////////////////// private InnerClass findInnerClass(InnerClasses ic, String innerClassName) { if ( ic != null ) { for (InnerClass i : ic.getInnerClasses()) { if (getInnerClassName(i).equals(innerClassName)) { return i; } } } return null; } /** * Build a map of InnerClasses containing all entries for all inner classes in the 'classes' * parameter and all their enclosing inner classes. * * @param classes a collection of classes the result should contain. * @return the map of className->InnerClass containing all inner classes in the 'classes' parameter. */ private Collection<InnerClass> buildInnerClasses(Collection<ClassRef> classes) { List<ClassRef> queue = new LinkedList<ClassRef>(classes); Set<String> visited = new HashSet<String>(); List<InnerClass> inner = new LinkedList<InnerClass>(); ConstantPoolGen cpg = classGen.getConstantPool(); InnerClasses ic = getInnerClassesAttribute(); while (!queue.isEmpty()) { ClassRef ref = queue.remove(0); ClassInfo cls = ref.getClassInfo(); int innerClassIdx; int outerClassIdx = 0; int innerNameIdx = 0; int flags = 0; String enclosingClass = null; if ( cls != null ) { InnerClassesInfo info = cls.getInnerClassesInfo(); // only nested classes have an entry if ( !info.isNestedClass() ) { continue; } enclosingClass = info.getEnclosingClassName(); innerClassIdx = cpg.addClass(ref.getClassName()); if ( !info.isLocalInnerClass() ) { // class is a member, add outer class reference outerClassIdx = cpg.addClass(enclosingClass); } if ( !info.isAnonymousInnerClass() ) { // class has a simple name innerNameIdx = cpg.addUtf8(info.getInnerName()); } flags = cls.getAccessFlags(); } else { // unknown class, need to check the existing InnerClass array, if it exists InnerClass i = findInnerClass(ic, ref.getClassName()); if ( i == null ) { // class is not an innerclass according to our old InnerClasses table continue; } innerClassIdx = i.getInnerClassIndex(); outerClassIdx = i.getOuterClassIndex(); innerNameIdx = i.getInnerNameIndex(); flags = i.getInnerAccessFlags(); } // add enclosing class to queue // Note: if this is a (known) local class, enclosingClass is set, but outerClassIdx is 0, // but we add the enclosing class anyway to the queue, because the EnclosingMethod attribute // will add a reference to the enclosing class anyway if ( enclosingClass != null && !visited.contains(enclosingClass) ) { queue.add( classInfo.getAppInfo().getClassRef(enclosingClass) ); } visited.add(ref.getClassName()); inner.add(new InnerClass(innerClassIdx, outerClassIdx, innerNameIdx, flags)); } return inner; } private void updateInnerClassFlag() { // check if this class appears as inner class (iff this is an inner class, it must // appear in the attribute by definition) aNestedClass = getInnerClassAttribute(classInfo.getClassName()) != null; } protected void resetHierarchyInfos() { enclosingClass = null; nestedClasses.clear(); } @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"}) protected void updateClassHierarchy() { if ( !aNestedClass ) { return; } enclosingClass = classInfo.getAppInfo().getClassInfo(getEnclosingClassName()); if ( enclosingClass != null ) { enclosingClass.getInnerClassesInfo().nestedClasses.add(classInfo); } else { throw new AppInfoError("Enclosing class "+getEnclosingClassName()+" of class "+classInfo.getClassName() +" is not loaded, but unknown outer classes are not supported."); } } @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"}) protected void removeFromClassHierarchy() { if ( enclosingClass != null ) { enclosingClass.getInnerClassesInfo().nestedClasses.remove(classInfo); } for (ClassInfo c : nestedClasses) { c.getInnerClassesInfo().enclosingClass = null; } } }