blob: 9bfcd5a0b218988c2acd36a61690de45e7f9becc [file] [log] [blame]
/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
*
* This program is distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
package org.pantsbuild.jmake;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* A reflection of a class, in the form that allows fast checks and information obtaining.
*
* @author Misha Dmitriev
* 5 April 2004
*/
@SuppressWarnings("serial")
public class ClassInfo implements Serializable {
public static final int VER_OLD = 0; // Old version
public static final int VER_NEW = 1; // New version
public static final int NO_VERSIONS = 2; // Non-project class, no change tracking
private transient PCDManager pcdm;
transient int verCode; // Version code for this ClassInfo - one of the above.
String name = null;
transient String packageName; // Package name; restored when database is reloaded
int javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_OLDEST; // Can have values from Utils.JAVAC_TARGET_RELEASE_xxx
String cpoolRefsToClasses[]; // Directly referenced class names trimmed of array and 'L' prefixes and ';' suffixes
boolean isRefClassArray[]; // Indicates if a directly referenced class is actually an array class
// In all signatures we replace the 'L' and ';' symbols that enclose non-primitive type names with '@' and '#' respectively,
// so that class names inside signatures can be located fast and unambiguously.
String cpoolRefsToFieldClasses[]; // Defining classes of referenced fields, trimmed of enclosing 'L' and ';' symbols
String cpoolRefsToFieldNames[]; // Names of referenced fields
String cpoolRefsToFieldSignatures[]; // Signatures of referenced fields
String cpoolRefsToMethodClasses[]; // Defining classes of referenced methods, trimmed of enclosing 'L' and ';' symbols
String cpoolRefsToMethodNames[]; // Names of referenced methods
String cpoolRefsToMethodSignatures[]; // Signatures of referenced methods
char accessFlags; // isInterface flag included
boolean isNonMemberNestedClass = false; // True if this is a non-member nested class
String superName;
String interfaces[];
String fieldNames[];
String fieldSignatures[];
char fieldAccessFlags[];
Object primitiveConstantInitValues[];
String methodNames[];
String methodSignatures[];
char methodAccessFlags[];
String checkedExceptions[][];
transient ClassInfo directSubclasses[]; // Direct subclasses. Created lazily and not preserved on disk.
transient String directlyEnclosingClass; // Directly enclosing class name; restored when database is reloaded
transient String topLevelEnclosingClass; // Top-level enclosing class name; restored when database is reloaded
String nestedClasses[]; // Names of all nested classes. Don't make transient - it's used to check
// if nested classes for this class were added/deleted in new version
transient char nestedClassAccessFlags[]; // No need to store this information permanently
transient boolean nestedClassNonMember[]; // Ditto
/** Creates new ClassInfo out of a class file. The last parameter is needed only to produce sensible error reports.*/
public ClassInfo(byte[] classFileBytes, int verCode, PCDManager pcdm, String classFileFullPath) {
this.pcdm = pcdm;
this.verCode = verCode;
pcdm.getClassFileReader().readClassFile(classFileBytes, this, classFileFullPath, true);
packageName = Utils.getPackageName(name);
directlyEnclosingClass =
Utils.getDirectlyEnclosingClass(name, this.javacTargetRelease);
topLevelEnclosingClass = Utils.getTopLevelEnclosingClass(name);
}
/**
* Create a "lightweight" ClassInfo, that contains just the class name, super name, interfaces, flags and verCode.
* Used for non-project classes, that don't change themselves, for which we are only interested in type hierarchy structure.
*/
public ClassInfo(byte[] classFileBytes, PCDManager pcdm, String classFileFullPath) {
this.pcdm = pcdm;
this.verCode = NO_VERSIONS;
pcdm.getClassFileReader().readClassFile(classFileBytes, this, classFileFullPath, false);
packageName = Utils.getPackageName(name);
directlyEnclosingClass =
Utils.getDirectlyEnclosingClass(name, this.javacTargetRelease);
topLevelEnclosingClass = Utils.getTopLevelEnclosingClass(name);
}
/** Even more lightweight variant - created for a deleted non-project class, to enable minimum possible checks. */
public ClassInfo(String name, PCDManager pcdm) {
this.pcdm = pcdm;
this.verCode = NO_VERSIONS;
this.name = name;
packageName = Utils.getPackageName(name);
directlyEnclosingClass = Utils.getDirectlyEnclosingClass(name, 0);
topLevelEnclosingClass = Utils.getTopLevelEnclosingClass(name);
}
public ClassInfo() {
}
/** Initialize transient data that can be initialized immediately after this ClassInfo is read from the project database */
public void initializeImmediateTransientFields() {
verCode = VER_OLD;
packageName = Utils.getPackageName(name);
directlyEnclosingClass =
Utils.getDirectlyEnclosingClass(name, this.javacTargetRelease);
topLevelEnclosingClass = Utils.getTopLevelEnclosingClass(name);
}
/**
* Called to restore the pointer to the current PCDManager after this ClassInfo is brought back
* from the store.
*/
public void restorePCDM(PCDManager pcdm) {
this.pcdm = pcdm;
}
public boolean isInterface() {
return Modifier.isInterface(accessFlags);
}
public boolean isAbstract() {
return Modifier.isAbstract(accessFlags);
}
public boolean isPublic() {
return Modifier.isPublic(accessFlags);
}
/**
* Returns the names of the superclasses of the given class (transitively), that belong
* to the same project, plus those of the superclasses that can be found on the class path
* supplied to jmake, and on the boot class path.
*/
public List<String> getAllSuperclassNames() {
List<String> res = new ArrayList<String>();
String superName = this.superName;
while (superName != null && !"java/lang/Object".equals(superName)) {
res.add(superName);
ClassInfo classInfo = pcdm.getClassInfoForName(verCode, superName);
if (classInfo == null) { // Class not in project (or deleted?). Try to find it and further superclasses in non-project classes
ClassPath.getSuperclasses(superName, res, pcdm);
break;
}
superName = classInfo.superName;
}
return res;
}
/**
* Returns the set of names of the interfaces transitively implemented by the given
* class, that belong to the same project.
*/
public Set<String> getAllImplementedIntfNames() {
Set<String> res = new LinkedHashSet<String>();
addImplementedInterfaceNames(false, res);
return res;
}
/** Add to the given set the names of direct/all interfaces implemented by the given class. */
private void addImplementedInterfaceNames(boolean directOnly,
Set<String> intfSet) {
if (interfaces != null) {
for (int i = 0; i < interfaces.length; i++) {
String superIntfName = interfaces[i];
intfSet.add(superIntfName);
if (directOnly) {
continue;
}
ClassInfo superIntfInfo =
pcdm.getClassInfoForName(verCode, superIntfName);
if (superIntfInfo == null) { // Class not in project
ClassPath.addAllImplementedInterfaceNames(superIntfName, intfSet, pcdm);
} else {
superIntfInfo.addImplementedInterfaceNames(false, intfSet);
}
}
}
if (directOnly || superName == null ||
"java/lang/Object".equals(superName)) {
return;
}
ClassInfo superInfo = pcdm.getClassInfoForName(verCode, superName);
if (superInfo == null) { // Class not in project
ClassPath.addAllImplementedInterfaceNames(superName, intfSet, pcdm);
} else {
superInfo.addImplementedInterfaceNames(false, intfSet);
}
}
/** Returns the array of all direct subclasses of this class (array of zero length if there are none). */
public ClassInfo[] getDirectSubclasses() {
if (directSubclasses != null) {
return directSubclasses;
}
List<ClassInfo> listRes = new ArrayList<ClassInfo>();
for (PCDEntry entry : pcdm.entries()) {
ClassInfo classInfo = pcdm.getClassInfoForPCDEntry(verCode, entry);
if (classInfo == null) {
continue; // New or deleted class, depending on verCode
}
if (classInfo.superName.equals(name)) {
listRes.add(classInfo);
}
}
directSubclasses = listRes.toArray(new ClassInfo[listRes.size()]);
return directSubclasses;
}
/** Check if the initial values for the given primitive constatnts in two classes are the same. */
public static boolean constFieldInitValuesEqual(ClassInfo oldClassInfo, int oldFieldNo,
ClassInfo newClassInfo, int newFieldNo) {
Object oldInitValue = oldClassInfo.primitiveConstantInitValues == null ? null
: oldClassInfo.primitiveConstantInitValues[oldFieldNo];
Object newInitValue = newClassInfo.primitiveConstantInitValues == null ? null
: newClassInfo.primitiveConstantInitValues[newFieldNo];
if (oldInitValue == newInitValue) {
return true;
}
if (oldInitValue == null || newInitValue == null) {
return false;
}
if (oldInitValue instanceof Integer) {
if (((Integer) oldInitValue).intValue() == ((Integer) newInitValue).intValue()) {
return true;
} else {
return false;
}
} else if (oldInitValue instanceof String) {
if ( ((String) oldInitValue).equals((String) newInitValue) ) {
return true;
} else {
return false;
}
} else if (oldInitValue instanceof Long) {
if (((Long) oldInitValue).longValue() == ((Long) newInitValue).longValue()) {
return true;
} else {
return false;
}
} else if (oldInitValue instanceof Float) {
if (((Float) oldInitValue).floatValue() == ((Float) newInitValue).floatValue()) {
return true;
} else {
return false;
}
} else if (oldInitValue instanceof Double) {
if (((Double) oldInitValue).doubleValue() == ((Double) newInitValue).doubleValue()) {
return true;
} else {
return false;
}
}
return true;
}
public boolean implementsInterfaceDirectly(String intfName) {
if (interfaces == null) {
return false;
}
for (int i = 0; i < interfaces.length; i++) {
if (intfName.equals(interfaces[i])) {
return true;
}
}
return false;
}
/** Check if this class implements interface I or any subinterface of I directly */
public boolean implementsIntfOrSubintfDirectly(String intfName) {
if (interfaces == null) {
return false;
}
for (int i = 0; i < interfaces.length; i++) {
if (intfName.equals(interfaces[i])) {
return true;
}
// An interface can have multiple superinterfaces, all of which are listed in its "interfaces" array
// (although in the .java source it "extends" them all).
ClassInfo superIntfInfo =
pcdm.getClassInfoForName(verCode, interfaces[i]);
if (superIntfInfo == null) {
continue; // Class not in project
}
if (superIntfInfo.implementsIntfOrSubintfDirectly(intfName)) {
return true;
}
}
return false;
}
/**
* Class C implements interface I indirectly, if C or some superclass of C directly implements I
* or some subinterface of I.
*/
public boolean implementsInterfaceDirectlyOrIndirectly(String intfName) {
if (interfaces == null) {
return false;
}
if (implementsIntfOrSubintfDirectly(intfName)) {
return true;
}
if (superName != null) {
ClassInfo superInfo = pcdm.getClassInfoForName(verCode, superName);
if (superInfo == null) {
return false; // Class not in project
}
return superInfo.implementsInterfaceDirectlyOrIndirectly(intfName);
}
return false;
}
/**
* Returns true if this class declares a field with the same name and type as
* the field number fieldNo in class classInfo.
*/
public boolean declaresField(ClassInfo classInfo, int fieldNo) {
if (fieldNames == null) {
return false;
}
String fieldName = classInfo.fieldNames[fieldNo];
String fieldSignature = classInfo.fieldSignatures[fieldNo];
for (int i = 0; i < fieldNames.length; i++) {
if (fieldName.equals(fieldNames[i]) &&
fieldSignature.equals(fieldSignatures[i])) {
return true;
}
}
return false;
}
/** Returns true if this class declares a field with the given name, signature and access */
public boolean declaresField(String name, String signature, boolean isStatic) {
if (fieldNames == null) {
return false;
}
signature = ("@" + signature + "#").intern();
for (int i = 0; i < fieldNames.length; i++) {
if (name.equals(fieldNames[i]) &&
signature.equals(fieldSignatures[i]) &&
Modifier.isStatic(fieldAccessFlags[i]) == isStatic) {
return true;
}
}
return false;
}
/**
* Returns true if this class declares a method with the same name and signature as
* the method number methodNo in class classInfo.
*/
public boolean declaresMethod(ClassInfo classInfo, int methodNo) {
if (methodNames == null) {
return false;
}
String methodName = classInfo.methodNames[methodNo];
String methodSignature = classInfo.methodSignatures[methodNo];
for (int i = 0; i < methodNames.length; i++) {
if (methodName.equals(methodNames[i]) &&
methodSignature.equals(methodSignatures[i])) {
return true;
}
}
return false;
}
/**
* If this class declares a method with the same name and signature as the given method,
* return its position. Otherwise, return -1.
*/
public int getDeclaredMethodPos(ClassInfo classInfo, int methodNo) {
if (methodNames == null) {
return -1;
}
String methodName = classInfo.methodNames[methodNo];
String methodSignature = classInfo.methodSignatures[methodNo];
for (int i = 0; i < methodNames.length; i++) {
if (methodName.equals(methodNames[i]) &&
methodSignature.equals(methodSignatures[i])) {
return i;
}
}
return -1;
}
/**
* Returns a nonnegative number (position in the method array) if this class declares a method with the
* name methodName, and -1 otherwise.
*/
public int declaresSameNameMethod(String methodName) {
if (methodNames == null) {
return -1;
}
for (int j = 0; j < methodNames.length; j++) {
if (methodName.equals(methodNames[j])) {
return j;
}
}
return -1;
}
/**
* Check if this class references the given class in different ways, depending on thorDegree parameter.
* thorDegree = 0: the given class (but not its array class) directly from the constantpool.
*
* thorDegree = 1: the given class or its array class directly from the constantpool, as a
* type of a data field, as a type in a method signature or a thrown exception, as a directly
* implemented interface or a direct superclass
*
* thorDegree = 2: the given class or its array class directly or indirectly from the
* constantpool, as a type of a data field, as a type in a method signature or a thrown exception,
* as a directly/indirectly implemented interface or a direct/indirect superclass.
*
* isRefTypeInterface indicates whether className is an interface.
*/
public boolean referencesClass(String className, boolean isRefTypeInterface, int thorDegree) {
int i;
if (thorDegree == 0) {
if (cpoolRefsToClasses == null) {
return false;
}
for (i = 0; i < cpoolRefsToClasses.length; i++) {
if (!isRefClassArray[i] &&
className.equals(cpoolRefsToClasses[i])) {
return true;
}
}
} else {
if (isSubclassOf(className, (thorDegree == 1))) {
return true;
}
if (isRefTypeInterface) {
if (thorDegree == 1) {
if (implementsInterfaceDirectly(className)) {
return true;
}
} else {
// Check for indirectly implemented interfaces
if (implementsInterfaceDirectlyOrIndirectly(className)) {
return true;
}
}
}
if (cpoolRefsToClasses != null) {
for (i = 0; i < cpoolRefsToClasses.length; i++) {
if (className.equals(cpoolRefsToClasses[i])) {
return true;
}
}
}
if (thorDegree == 2) {
// Check for indirect references from the constantpool
if (cpoolRefsToFieldSignatures != null) {
for (i = 0; i < cpoolRefsToFieldSignatures.length; i++) {
if (signatureIncludesClassName(cpoolRefsToFieldSignatures[i], className)) {
return true;
}
}
}
if (cpoolRefsToMethodNames != null) {
for (i = 0; i < cpoolRefsToMethodSignatures.length; i++) {
if (signatureIncludesClassName(cpoolRefsToMethodSignatures[i], className)) {
return true;
}
}
}
}
if (fieldSignatures != null) {
for (i = 0; i < fieldSignatures.length; i++) {
if (signatureIncludesClassName(fieldSignatures[i], className)) {
return true;
}
}
}
if (methodSignatures != null) {
for (i = 0; i < methodSignatures.length; i++) {
if (signatureIncludesClassName(methodSignatures[i], className)) {
return true;
}
}
}
if (checkedExceptions != null) {
for (i = 0; i < checkedExceptions.length; i++) {
if (checkedExceptions[i] != null) {
String excArray[] = checkedExceptions[i];
for (int j = 0; j < excArray.length; j++) {
if (className.equals(excArray[j])) {
return true;
}
}
}
}
}
}
return false;
}
private static boolean signatureIncludesClassName(String signature, String className) {
int stIndex = signature.indexOf(className);
if (stIndex == -1) {
return false;
}
return ((stIndex != 0 && signature.charAt(stIndex - 1) == '@' && signature.charAt(stIndex + className.length()) == '#') ||
(stIndex == 0 && signature.length() == className.length()));
}
public boolean isSubclassOf(String className, boolean directOnly) {
if (className.equals(superName)) {
return true;
}
if (directOnly) {
return false;
}
String superName = this.superName;
while (superName != null) {
if (className.equals(superName)) {
return true;
}
ClassInfo classInfo = pcdm.getClassInfoForName(verCode, superName);
if (classInfo == null) {
break; // Class not in project
}
superName = classInfo.superName;
}
return false;
}
/**
* Check if this class references field number fieldNo of class fieldDefClassInfo. Let us call
* this field C.f. Actual reference contained in the constant pool may be not to C.f itself,
* but to Csub.f, where Csub is some subclass of C such that neither Csub nor any other class
* located between C and Csub in the class hierarchy redeclares f. We look up both "real"
* references C.f and "fake" references such as Csub.f.
*/
public boolean referencesField(ClassInfo fieldDefClassInfo, int fieldNo) {
if (cpoolRefsToFieldNames == null) {
return false;
}
String fieldDefClassName = fieldDefClassInfo.name;
String fieldName = fieldDefClassInfo.fieldNames[fieldNo];
String fieldSig = fieldDefClassInfo.fieldSignatures[fieldNo];
for (int i = 0; i < cpoolRefsToFieldNames.length; i++) {
if (fieldName.equals(cpoolRefsToFieldNames[i]) &&
fieldSig.equals(cpoolRefsToFieldSignatures[i]) ) {
if (fieldDefClassName.equals(cpoolRefsToFieldClasses[i]) ) {
return true; // "real" reference
} else { // Check if this is a "fake" reference that resolves to the above "real" reference
ClassInfo classInThisCpool =
pcdm.getClassInfoForName(verCode, cpoolRefsToFieldClasses[i]);
if (classInThisCpool == null) {
continue; // Class not in project
}
if (!classInThisCpool.isSubclassOf(fieldDefClassInfo.name, false)) {
continue;
}
// Ok, now check that this field is not actually redeclared in fieldDefClassInfo or
// somewhere in between it and classInThisCpool
boolean redeclared = false;
ClassInfo curClass = classInThisCpool;
do {
if (curClass.declaresField(fieldDefClassInfo, fieldNo)) {
redeclared = true;
break;
}
String superName = curClass.superName;
curClass = pcdm.getClassInfoForName(verCode, superName);
if (curClass == null) {
break;
}
} while (curClass != fieldDefClassInfo);
if (!redeclared) {
return true;
}
}
}
}
return false;
}
/**
* Check if this class references method number methodNo of class methodDefClassInfo. Let us
* call this method C.m. Actual reference contained in the constant pool may be not to C.m
* itself, but to Csub.m, where Csub is some subclass of C such that neither Csub nor any
* other class located between C and Csub in the class hierarchy redeclares m. We look up
* both "real" references C.m and "fake" references such as Csub.m.
*/
public boolean referencesMethod(ClassInfo methodDefClassInfo, int methodNo) {
if (cpoolRefsToMethodNames == null) {
return false;
}
String methodDefClassName = methodDefClassInfo.name;
String methodName = methodDefClassInfo.methodNames[methodNo];
String methodSig = methodDefClassInfo.methodSignatures[methodNo];
for (int i = 0; i < cpoolRefsToMethodNames.length; i++) {
if (methodName.equals(cpoolRefsToMethodNames[i]) &&
methodSig.equals(cpoolRefsToMethodSignatures[i])) {
if (methodDefClassName.equals(cpoolRefsToMethodClasses[i])) {
return true; // "real" reference
} else { // Check if this is a "fake" reference that resolves to the above "real" reference
// Be careful - class in the cpool may be not a project class (e.g. a core class).
ClassInfo classInThisCpool =
pcdm.getClassInfoForName(verCode, cpoolRefsToMethodClasses[i]);
if (classInThisCpool == null) {
continue; // Class not in project
}
if (classInThisCpool.isSubclassOf(methodDefClassInfo.name, false)) {
// Ok, now check that this method is not actually redeclared in classInThisCpool (which is
// lower in the hierarchy) or somewhere in between it and classInThisCpool
boolean redeclared = false;
ClassInfo curClass = classInThisCpool;
do {
if (curClass.declaresMethod(methodDefClassInfo, methodNo)) {
redeclared = true;
break;
}
String superName = curClass.superName;
curClass =
pcdm.getClassInfoForName(verCode, superName);
if (curClass == null) {
break;
}
} while (curClass != methodDefClassInfo);
if (!redeclared) {
return true;
}
} else if (methodDefClassInfo.isInterface() && classInThisCpool.implementsIntfOrSubintfDirectly(methodDefClassName)) {
return true;
}
}
}
}
return false;
}
/**
* If this class has a method that throws the given exception, return its index. Otherwise return -1.
* The search starts from method with index startMethodIdx.
*/
public int hasMethodThrowingException(ClassInfo excClassInfo, int startMethodIdx) {
if (checkedExceptions == null) {
return -1;
}
if (startMethodIdx >= checkedExceptions.length) {
return -1;
}
String excName = excClassInfo.name;
for (int i = startMethodIdx; i < checkedExceptions.length; i++) {
if (checkedExceptions[i] == null) {
continue;
}
String[] exc = checkedExceptions[i];
for (int j = 0; j < exc.length; j++) {
if (exc[j].equals(excName)) {
return i;
}
}
}
return -1;
}
public static abstract class MethodHandler {
abstract void handleMethod(ClassInfo ci, int methodIdx);
}
/**
* Check this class and all its superclasses (if includeSuperclasses == true) and superinterfaces (if includeInterfaces == true)
* for a method with the given name. If such a method is found, call h.handleMethod(classInfo, methodIdx).
*/
public void findExistingSameNameMethods(String methodName, boolean includeSuperclasses, boolean includeInterfaces, MethodHandler h) {
String className = name;
ClassInfo classInfo;
while (className != null) {
classInfo = pcdm.getClassInfoForName(verCode, className);
if (classInfo == null) {
break; // Class not in project
}
String mNames[] = classInfo.methodNames;
int mNamesLen = mNames != null ? mNames.length : 0;
for (int i = 0; i < mNamesLen; i++) {
if (methodName.equals(mNames[i])) {
h.handleMethod(classInfo, i);
}
}
if (includeInterfaces && classInfo.interfaces != null) {
String intfNames[] = classInfo.interfaces;
for (int i = 0; i < intfNames.length; i++) {
ClassInfo superIntfInfo =
pcdm.getClassInfoForName(verCode, intfNames[i]);
if (superIntfInfo == null) {
continue; // Class not in project
}
superIntfInfo.findExistingSameNameMethods(methodName, true, includeInterfaces, h);
}
}
if (includeSuperclasses) {
className = classInfo.superName;
} else {
return;
}
}
}
public static boolean isPrimitiveFieldSig(String fieldSig) {
return fieldSig.indexOf('@') == -1;
}
/**
* Check if the given signature is of a class type, and that class does not belong to the project.
* It used to be a check for just a core type name, but sometimes people use JDK sources as e.g. a test
* case - so better perform a universal (and entirely correct, unlike just a core type name) test here.
*/
public boolean isNonProjectClassTypeFieldSig(String fieldSig) {
int stPos = fieldSig.indexOf('@');
if (stPos == -1) {
return false;
}
int endPos = fieldSig.indexOf('#');
String className = fieldSig.substring(stPos + 1, endPos);
return (!pcdm.isProjectClass(verCode, className));
}
/** For debugging. */
public String toString() {
return name + (verCode == VER_OLD ? " OLD" : " NEW");
}
}