/*******************************************************************************
* Copyright (c) 2000, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.classfmt;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.codegen.AttributeNamesConstants;
import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
import org.eclipse.jdt.internal.compiler.env.IBinaryField;
import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
import org.eclipse.jdt.internal.compiler.env.IBinaryNestedType;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.util.Util;
public class ClassFileReader extends ClassFileStruct implements IBinaryType {
private int accessFlags;
private char[] classFileName;
private char[] className;
private int classNameIndex;
private int constantPoolCount;
private AnnotationInfo[] annotations;
private FieldInfo[] fields;
private int fieldsCount;
// initialized in case the .class file is a nested type
private InnerClassInfo innerInfo;
private int innerInfoIndex;
private InnerClassInfo[] innerInfos;
private char[][] interfaceNames;
private int interfacesCount;
private MethodInfo[] methods;
private int methodsCount;
private char[] signature;
private char[] sourceName;
private char[] sourceFileName;
private char[] superclassName;
private long tagBits;
private long version;
private char[] enclosingTypeName;
private char[][][] missingTypeNames;
private int enclosingNameAndTypeIndex;
private char[] enclosingMethod;
private static String printTypeModifiers(int modifiers) {
java.io.ByteArrayOutputStream out= new java.io.ByteArrayOutputStream();
java.io.PrintWriter print= new java.io.PrintWriter(out);
if ((modifiers & ClassFileConstants.AccPublic) != 0)
print.print("public "); //$NON-NLS-1$
if ((modifiers & ClassFileConstants.AccPrivate) != 0)
print.print("private "); //$NON-NLS-1$
if ((modifiers & ClassFileConstants.AccFinal) != 0)
print.print("final "); //$NON-NLS-1$
if ((modifiers & ClassFileConstants.AccSuper) != 0)
print.print("super "); //$NON-NLS-1$
if ((modifiers & ClassFileConstants.AccInterface) != 0)
print.print("interface "); //$NON-NLS-1$
if ((modifiers & ClassFileConstants.AccAbstract) != 0)
print.print("abstract "); //$NON-NLS-1$
print.flush();
return out.toString();
}
public static ClassFileReader read(File file) throws ClassFormatException, IOException {
return read(file, false);
}
public static ClassFileReader read(File file, boolean fullyInitialize) throws ClassFormatException, IOException {
byte classFileBytes[]= Util.getFileByteContent(file);
ClassFileReader classFileReader= new ClassFileReader(classFileBytes, file.getAbsolutePath().toCharArray());
if (fullyInitialize) {
classFileReader.initialize();
}
return classFileReader;
}
public static ClassFileReader read(InputStream stream, String fileName) throws ClassFormatException, IOException {
return read(stream, fileName, false);
}
public static ClassFileReader read(InputStream stream, String fileName, boolean fullyInitialize) throws ClassFormatException, IOException {
byte classFileBytes[]= Util.getInputStreamAsByteArray(stream, -1);
ClassFileReader classFileReader= new ClassFileReader(classFileBytes, fileName.toCharArray());
if (fullyInitialize) {
classFileReader.initialize();
}
return classFileReader;
}
public static ClassFileReader read(
java.util.zip.ZipFile zip,
String filename)
throws ClassFormatException, java.io.IOException {
return read(zip, filename, false);
}
public static ClassFileReader read(
java.util.zip.ZipFile zip,
String filename,
boolean fullyInitialize)
throws ClassFormatException, java.io.IOException {
java.util.zip.ZipEntry ze= zip.getEntry(filename);
if (ze == null)
return null;
byte classFileBytes[]= Util.getZipEntryByteContent(ze, zip);
ClassFileReader classFileReader= new ClassFileReader(classFileBytes, filename.toCharArray());
if (fullyInitialize) {
classFileReader.initialize();
}
return classFileReader;
}
public static ClassFileReader read(String fileName) throws ClassFormatException, java.io.IOException {
return read(fileName, false);
}
public static ClassFileReader read(String fileName, boolean fullyInitialize) throws ClassFormatException, java.io.IOException {
return read(new File(fileName), fullyInitialize);
}
/**
* @param classFileBytes Actual bytes of a .class file
* @param fileName Actual name of the file that contains the bytes, can be null
*
* @exception ClassFormatException
*/
public ClassFileReader(byte classFileBytes[], char[] fileName) throws ClassFormatException {
this(classFileBytes, fileName, false);
}
/**
* @param classFileBytes byte[] Actual bytes of a .class file
*
* @param fileName char[] Actual name of the file that contains the bytes, can be null
*
* @param fullyInitialize boolean Flag to fully initialize the new object
* @exception ClassFormatException
*/
public ClassFileReader(byte[] classFileBytes, char[] fileName, boolean fullyInitialize) throws ClassFormatException {
// This method looks ugly but is actually quite simple, the constantPool is constructed
// in 3 passes. All non-primitive constant pool members that usually refer to other members
// by index are tweaked to have their value in inst vars, this minor cost at read-time makes
// all subsequent uses of the constant pool element faster.
super(classFileBytes, null, 0);
this.classFileName= fileName;
int readOffset= 10;
try {
this.version= ((long)u2At(6) << 16) + u2At(4); // major<<16 + minor
this.constantPoolCount= u2At(8);
// Pass #1 - Fill in all primitive constants
this.constantPoolOffsets= new int[this.constantPoolCount];
for (int i= 1; i < this.constantPoolCount; i++) {
int tag= u1At(readOffset);
switch (tag) {
case ClassFileConstants.Utf8Tag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= u2At(readOffset + 1);
readOffset+= ClassFileConstants.ConstantUtf8FixedSize;
break;
case ClassFileConstants.IntegerTag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= ClassFileConstants.ConstantIntegerFixedSize;
break;
case ClassFileConstants.FloatTag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= ClassFileConstants.ConstantFloatFixedSize;
break;
case ClassFileConstants.LongTag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= ClassFileConstants.ConstantLongFixedSize;
i++;
break;
case ClassFileConstants.DoubleTag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= ClassFileConstants.ConstantDoubleFixedSize;
i++;
break;
case ClassFileConstants.ClassTag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= ClassFileConstants.ConstantClassFixedSize;
break;
case ClassFileConstants.StringTag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= ClassFileConstants.ConstantStringFixedSize;
break;
case ClassFileConstants.FieldRefTag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= ClassFileConstants.ConstantFieldRefFixedSize;
break;
case ClassFileConstants.MethodRefTag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= ClassFileConstants.ConstantMethodRefFixedSize;
break;
case ClassFileConstants.InterfaceMethodRefTag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= ClassFileConstants.ConstantInterfaceMethodRefFixedSize;
break;
case ClassFileConstants.NameAndTypeTag:
this.constantPoolOffsets[i]= readOffset;
readOffset+= ClassFileConstants.ConstantNameAndTypeFixedSize;
}
}
// Read and validate access flags
this.accessFlags= u2At(readOffset);
readOffset+= 2;
// Read the classname, use exception handlers to catch bad format
this.classNameIndex= u2At(readOffset);
this.className= getConstantClassNameAt(this.classNameIndex);
readOffset+= 2;
// Read the superclass name, can be null for java.lang.Object
int superclassNameIndex= u2At(readOffset);
readOffset+= 2;
// if superclassNameIndex is equals to 0 there is no need to set a value for the
// field this.superclassName. null is fine.
if (superclassNameIndex != 0) {
this.superclassName= getConstantClassNameAt(superclassNameIndex);
}
// Read the interfaces, use exception handlers to catch bad format
this.interfacesCount= u2At(readOffset);
readOffset+= 2;
if (this.interfacesCount != 0) {
this.interfaceNames= new char[this.interfacesCount][];
for (int i= 0; i < this.interfacesCount; i++) {
this.interfaceNames[i]= getConstantClassNameAt(u2At(readOffset));
readOffset+= 2;
}
}
// Read the fields, use exception handlers to catch bad format
this.fieldsCount= u2At(readOffset);
readOffset+= 2;
if (this.fieldsCount != 0) {
FieldInfo field;
this.fields= new FieldInfo[this.fieldsCount];
for (int i= 0; i < this.fieldsCount; i++) {
field= FieldInfo.createField(this.reference, this.constantPoolOffsets, readOffset);
this.fields[i]= field;
readOffset+= field.sizeInBytes();
}
}
// Read the methods
this.methodsCount= u2At(readOffset);
readOffset+= 2;
if (this.methodsCount != 0) {
this.methods= new MethodInfo[this.methodsCount];
boolean isAnnotationType= (this.accessFlags & ClassFileConstants.AccAnnotation) != 0;
for (int i= 0; i < this.methodsCount; i++) {
this.methods[i]= isAnnotationType
? AnnotationMethodInfo.createAnnotationMethod(this.reference, this.constantPoolOffsets, readOffset)
: MethodInfo.createMethod(this.reference, this.constantPoolOffsets, readOffset);
readOffset+= this.methods[i].sizeInBytes();
}
}
// Read the attributes
int attributesCount= u2At(readOffset);
readOffset+= 2;
for (int i= 0; i < attributesCount; i++) {
int utf8Offset= this.constantPoolOffsets[u2At(readOffset)];
char[] attributeName= utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
if (attributeName.length == 0) {
readOffset+= (6 + u4At(readOffset + 2));
continue;
}
switch (attributeName[0]) {
case 'E':
if (CharOperation.equals(attributeName, AttributeNamesConstants.EnclosingMethodName)) {
utf8Offset=
this.constantPoolOffsets[u2At(this.constantPoolOffsets[u2At(readOffset + 6)] + 1)];
this.enclosingTypeName= utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
this.enclosingNameAndTypeIndex= u2At(readOffset + 8);
}
break;
case 'D':
if (CharOperation.equals(attributeName, AttributeNamesConstants.DeprecatedName)) {
this.accessFlags|= ClassFileConstants.AccDeprecated;
}
break;
case 'I':
if (CharOperation.equals(attributeName, AttributeNamesConstants.InnerClassName)) {
int innerOffset= readOffset + 6;
int number_of_classes= u2At(innerOffset);
if (number_of_classes != 0) {
innerOffset+= 2;
this.innerInfos= new InnerClassInfo[number_of_classes];
for (int j= 0; j < number_of_classes; j++) {
this.innerInfos[j]=
new InnerClassInfo(this.reference, this.constantPoolOffsets, innerOffset);
if (this.classNameIndex == this.innerInfos[j].innerClassNameIndex) {
this.innerInfo= this.innerInfos[j];
this.innerInfoIndex= j;
}
innerOffset+= 8;
}
if (this.innerInfo != null) {
char[] enclosingType= this.innerInfo.getEnclosingTypeName();
if (enclosingType != null) {
this.enclosingTypeName= enclosingType;
}
}
}
} else if (CharOperation.equals(attributeName, AttributeNamesConstants.InconsistentHierarchy)) {
this.tagBits|= TagBits.HierarchyHasProblems;
}
break;
case 'S':
if (attributeName.length > 2) {
switch (attributeName[1]) {
case 'o':
if (CharOperation.equals(attributeName, AttributeNamesConstants.SourceName)) {
utf8Offset= this.constantPoolOffsets[u2At(readOffset + 6)];
this.sourceFileName= utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
}
break;
case 'y':
if (CharOperation.equals(attributeName, AttributeNamesConstants.SyntheticName)) {
this.accessFlags|= ClassFileConstants.AccSynthetic;
}
break;
case 'i':
if (CharOperation.equals(attributeName, AttributeNamesConstants.SignatureName)) {
utf8Offset= this.constantPoolOffsets[u2At(readOffset + 6)];
this.signature= utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
}
}
}
break;
case 'R':
if (CharOperation.equals(attributeName, AttributeNamesConstants.RuntimeVisibleAnnotationsName)) {
decodeAnnotations(readOffset, true);
} else if (CharOperation.equals(attributeName, AttributeNamesConstants.RuntimeInvisibleAnnotationsName)) {
decodeAnnotations(readOffset, false);
}
break;
case 'M':
if (CharOperation.equals(attributeName, AttributeNamesConstants.MissingTypesName)) {
// decode the missing types
int missingTypeOffset= readOffset + 6;
int numberOfMissingTypes= u2At(missingTypeOffset);
if (numberOfMissingTypes != 0) {
this.missingTypeNames= new char[numberOfMissingTypes][][];
missingTypeOffset+= 2;
for (int j= 0; j < numberOfMissingTypes; j++) {
utf8Offset= this.constantPoolOffsets[u2At(this.constantPoolOffsets[u2At(missingTypeOffset)] + 1)];
char[] missingTypeConstantPoolName= utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
this.missingTypeNames[j]= CharOperation.splitOn('/', missingTypeConstantPoolName);
missingTypeOffset+= 2;
}
}
}
}
readOffset+= (6 + u4At(readOffset + 2));
}
if (fullyInitialize) {
initialize();
}
} catch (ClassFormatException e) {
throw e;
} catch (Exception e) {
throw new ClassFormatException(
ClassFormatException.ErrTruncatedInput,
readOffset);
}
}
/**
* Answer the receiver's access flags. The value of the access_flags item is a mask of modifiers
* used with class and interface declarations.
*
* @return int
*/
public int accessFlags() {
return this.accessFlags;
}
private void decodeAnnotations(int offset, boolean runtimeVisible) {
int numberOfAnnotations= u2At(offset + 6);
if (numberOfAnnotations > 0) {
int readOffset= offset + 8;
AnnotationInfo[] newInfos= null;
int newInfoCount= 0;
for (int i= 0; i < numberOfAnnotations; i++) {
// With the last parameter being 'false', the data structure will not be flushed out
AnnotationInfo newInfo= new AnnotationInfo(this.reference, this.constantPoolOffsets, readOffset, runtimeVisible, false);
readOffset+= newInfo.readOffset;
long standardTagBits= newInfo.standardAnnotationTagBits;
if (standardTagBits != 0) {
this.tagBits|= standardTagBits;
} else {
if (newInfos == null)
newInfos= new AnnotationInfo[numberOfAnnotations - i];
newInfos[newInfoCount++]= newInfo;
}
}
if (newInfos == null)
return; // nothing to record in this.annotations
if (this.annotations == null) {
if (newInfoCount != newInfos.length)
System.arraycopy(newInfos, 0, newInfos= new AnnotationInfo[newInfoCount], 0, newInfoCount);
this.annotations= newInfos;
} else {
int length= this.annotations.length;
AnnotationInfo[] temp= new AnnotationInfo[length + newInfoCount];
System.arraycopy(this.annotations, 0, temp, 0, length);
System.arraycopy(newInfos, 0, temp, length, newInfoCount);
this.annotations= temp;
}
}
}
/**
* @return the annotations or null if there is none.
*/
public IBinaryAnnotation[] getAnnotations() {
return this.annotations;
}
/**
* Answer the char array that corresponds to the class name of the constant class.
* constantPoolIndex is the index in the constant pool that is a constant class entry.
*
* @param constantPoolIndex int
* @return char[]
*/
private char[] getConstantClassNameAt(int constantPoolIndex) {
int utf8Offset= this.constantPoolOffsets[u2At(this.constantPoolOffsets[constantPoolIndex] + 1)];
return utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
}
/**
* Answer the int array that corresponds to all the offsets of each entry in the constant pool
*
* @return int[]
*/
public int[] getConstantPoolOffsets() {
return this.constantPoolOffsets;
}
public char[] getEnclosingMethod() {
if (this.enclosingNameAndTypeIndex <= 0) {
return null;
}
if (this.enclosingMethod == null) {
// read the name
StringBuffer buffer= new StringBuffer();
int nameAndTypeOffset= this.constantPoolOffsets[this.enclosingNameAndTypeIndex];
int utf8Offset= this.constantPoolOffsets[u2At(nameAndTypeOffset + 1)];
buffer.append(utf8At(utf8Offset + 3, u2At(utf8Offset + 1)));
utf8Offset= this.constantPoolOffsets[u2At(nameAndTypeOffset + 3)];
buffer.append(utf8At(utf8Offset + 3, u2At(utf8Offset + 1)));
this.enclosingMethod= String.valueOf(buffer).toCharArray();
}
return this.enclosingMethod;
}
/*
* Answer the resolved compoundName of the enclosing type
* or null if the receiver is a top level type.
*/
public char[] getEnclosingTypeName() {
return this.enclosingTypeName;
}
/**
* Answer the receiver's this.fields or null if the array is empty.
*
* @return org.eclipse.jdt.internal.compiler.api.IBinaryField[]
*/
public IBinaryField[] getFields() {
return this.fields;
}
/**
* @see org.eclipse.jdt.internal.compiler.env.IDependent#getFileName()
*/
public char[] getFileName() {
return this.classFileName;
}
public char[] getGenericSignature() {
return this.signature;
}
/**
* Answer the source name if the receiver is a inner type. Return null if it is an anonymous
* class or if the receiver is a top-level class. e.g. public class A { public class B { }
* public void foo() { class C {} } public Runnable bar() { return new Runnable() { public void
* run() {} }; } } It returns {'B'} for the member A$B It returns null for A It returns {'C'}
* for the local class A$1$C It returns null for the anonymous A$1
*
* @return char[]
*/
public char[] getInnerSourceName() {
if (this.innerInfo != null)
return this.innerInfo.getSourceName();
return null;
}
/**
* Answer the resolved names of the receiver's interfaces in the class file format as specified
* in section 4.2 of the Java 2 VM spec or null if the array is empty.
*
* For example, java.lang.String is java/lang/String.
*
* @return char[][]
*/
public char[][] getInterfaceNames() {
return this.interfaceNames;
}
/**
* Answer the receiver's nested types or null if the array is empty.
*
* This nested type info is extracted from the inner class attributes. Ask the name environment
* to find a member type using its compound name
*
* @return org.eclipse.jdt.internal.compiler.api.IBinaryNestedType[]
*/
public IBinaryNestedType[] getMemberTypes() {
// we might have some member types of the current type
if (this.innerInfos == null)
return null;
int length= this.innerInfos.length;
int startingIndex= this.innerInfo != null ? this.innerInfoIndex + 1 : 0;
if (length != startingIndex) {
IBinaryNestedType[] memberTypes=
new IBinaryNestedType[length - this.innerInfoIndex];
int memberTypeIndex= 0;
for (int i= startingIndex; i < length; i++) {
InnerClassInfo currentInnerInfo= this.innerInfos[i];
int outerClassNameIdx= currentInnerInfo.outerClassNameIndex;
int innerNameIndex= currentInnerInfo.innerNameIndex;
/*
* Checking that outerClassNameIDx is different from 0 should be enough to determine if an inner class
* attribute entry is a member class, but due to the bug:
* http://dev.eclipse.org/bugs/show_bug.cgi?id=14592
* we needed to add an extra check. So we check that innerNameIndex is different from 0 as well.
*
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=49879
* From JavaMail 1.2, the class javax.mail.Folder contains an anonymous class in the
* terminateQueue() method for which the inner attribute is boggus.
* outerClassNameIdx is not 0, innerNameIndex is not 0, but the sourceName length is 0.
* So I added this extra check to filter out this anonymous class from the
* member types.
*/
if (outerClassNameIdx != 0
&& innerNameIndex != 0
&& outerClassNameIdx == this.classNameIndex
&& currentInnerInfo.getSourceName().length != 0) {
memberTypes[memberTypeIndex++]= currentInnerInfo;
}
}
if (memberTypeIndex == 0)
return null;
if (memberTypeIndex != memberTypes.length) {
// we need to resize the memberTypes array. Some local or anonymous classes
// are present in the current class.
System.arraycopy(
memberTypes,
0,
(memberTypes= new IBinaryNestedType[memberTypeIndex]),
0,
memberTypeIndex);
}
return memberTypes;
}
return null;
}
/**
* Answer the receiver's this.methods or null if the array is empty.
*
* @return org.eclipse.jdt.internal.compiler.api.env.IBinaryMethod[]
*/
public IBinaryMethod[] getMethods() {
return this.methods;
}
/*
public static void main(String[] args) throws ClassFormatException, IOException {
if (args == null || args.length != 1) {
System.err.println("ClassFileReader <filename>"); //$NON-NLS-1$
System.exit(1);
}
File file = new File(args[0]);
ClassFileReader reader = read(file, true);
if (reader.annotations != null) {
System.err.println();
for (int i = 0; i < reader.annotations.length; i++)
System.err.println(reader.annotations[i]);
}
System.err.print("class "); //$NON-NLS-1$
System.err.print(reader.getName());
char[] superclass = reader.getSuperclassName();
if (superclass != null) {
System.err.print(" extends "); //$NON-NLS-1$
System.err.print(superclass);
}
System.err.println();
char[][] interfaces = reader.getInterfaceNames();
if (interfaces != null && interfaces.length > 0) {
System.err.print(" implements "); //$NON-NLS-1$
for (int i = 0; i < interfaces.length; i++) {
if (i != 0) System.err.print(", "); //$NON-NLS-1$
System.err.println(interfaces[i]);
}
}
System.err.println();
System.err.println('{');
if (reader.fields != null) {
for (int i = 0; i < reader.fields.length; i++) {
System.err.println(reader.fields[i]);
System.err.println();
}
}
if (reader.methods != null) {
for (int i = 0; i < reader.methods.length; i++) {
System.err.println(reader.methods[i]);
System.err.println();
}
}
System.err.println();
System.err.println('}');
}
*/
public char[][][] getMissingTypeNames() {
return this.missingTypeNames;
}
/**
* Answer an int whose bits are set according the access constants defined by the VM spec. Set
* the AccDeprecated and AccSynthetic bits if necessary
*
* @return int
*/
public int getModifiers() {
int modifiers;
if (this.innerInfo != null) {
modifiers= this.innerInfo.getModifiers()
| (this.accessFlags & ClassFileConstants.AccDeprecated)
| (this.accessFlags & ClassFileConstants.AccSynthetic);
} else {
modifiers= this.accessFlags;
}
return modifiers;
}
/**
* Answer the resolved name of the type in the class file format as specified in section 4.2 of
* the Java 2 VM spec.
*
* For example, java.lang.String is java/lang/String.
*
* @return char[]
*/
public char[] getName() {
return this.className;
}
public char[] getSourceName() {
if (this.sourceName != null)
return this.sourceName;
char[] name= getInnerSourceName(); // member or local scenario
if (name == null) {
name= getName(); // extract from full name
int start;
if (isAnonymous()) {
start= CharOperation.indexOf('$', name, CharOperation.lastIndexOf('/', name) + 1) + 1;
} else {
start= CharOperation.lastIndexOf('/', name) + 1;
}
if (start > 0) {
char[] newName= new char[name.length - start];
System.arraycopy(name, start, newName, 0, newName.length);
name= newName;
}
}
return this.sourceName= name;
}
/**
* Answer the resolved name of the receiver's superclass in the class file format as specified
* in section 4.2 of the Java 2 VM spec or null if it does not have one.
*
* For example, java.lang.String is java/lang/String.
*
* @return char[]
*/
public char[] getSuperclassName() {
return this.superclassName;
}
public long getTagBits() {
return this.tagBits;
}
/**
* Answer the major/minor version defined in this class file according to the VM spec. as a
* long: (major<<16)+minor
*
* @return the major/minor version found
*/
public long getVersion() {
return this.version;
}
private boolean hasNonSyntheticFieldChanges(FieldInfo[] currentFieldInfos, FieldInfo[] otherFieldInfos) {
int length1= currentFieldInfos == null ? 0 : currentFieldInfos.length;
int length2= otherFieldInfos == null ? 0 : otherFieldInfos.length;
int index1= 0;
int index2= 0;
end: while (index1 < length1 && index2 < length2) {
while (currentFieldInfos[index1].isSynthetic()) {
if (++index1 >= length1)
break end;
}
while (otherFieldInfos[index2].isSynthetic()) {
if (++index2 >= length2)
break end;
}
if (hasStructuralFieldChanges(currentFieldInfos[index1++], otherFieldInfos[index2++]))
return true;
}
while (index1 < length1) {
if (!currentFieldInfos[index1++].isSynthetic())
return true;
}
while (index2 < length2) {
if (!otherFieldInfos[index2++].isSynthetic())
return true;
}
return false;
}
private boolean hasNonSyntheticMethodChanges(MethodInfo[] currentMethodInfos, MethodInfo[] otherMethodInfos) {
int length1= currentMethodInfos == null ? 0 : currentMethodInfos.length;
int length2= otherMethodInfos == null ? 0 : otherMethodInfos.length;
int index1= 0;
int index2= 0;
MethodInfo m;
end: while (index1 < length1 && index2 < length2) {
while ((m= currentMethodInfos[index1]).isSynthetic() || m.isClinit()) {
if (++index1 >= length1)
break end;
}
while ((m= otherMethodInfos[index2]).isSynthetic() || m.isClinit()) {
if (++index2 >= length2)
break end;
}
if (hasStructuralMethodChanges(currentMethodInfos[index1++], otherMethodInfos[index2++]))
return true;
}
while (index1 < length1) {
if (!((m= currentMethodInfos[index1++]).isSynthetic() || m.isClinit()))
return true;
}
while (index2 < length2) {
if (!((m= otherMethodInfos[index2++]).isSynthetic() || m.isClinit()))
return true;
}
return false;
}
/**
* Check if the receiver has structural changes compare to the byte array in argument.
* Structural changes are: - modifiers changes for the class, the this.fields or the
* this.methods - signature changes for this.fields or this.methods. - changes in the number of
* this.fields or this.methods - changes for field constants - changes for thrown exceptions -
* change for the super class or any super interfaces. - changes for member types name or
* modifiers If any of these changes occurs, the method returns true. false otherwise. The
* synthetic fields are included and the members are not required to be sorted.
*
* @param newBytes the bytes of the .class file we want to compare the receiver to
* @return boolean Returns true is there is a structural change between the two .class files,
* false otherwise
*/
public boolean hasStructuralChanges(byte[] newBytes) {
return hasStructuralChanges(newBytes, true, true);
}
/**
* Check if the receiver has structural changes compare to the byte array in argument.
* Structural changes are: - modifiers changes for the class, the this.fields or the
* this.methods - signature changes for this.fields or this.methods. - changes in the number of
* this.fields or this.methods - changes for field constants - changes for thrown exceptions -
* change for the super class or any super interfaces. - changes for member types name or
* modifiers If any of these changes occurs, the method returns true. false otherwise.
*
* @param newBytes the bytes of the .class file we want to compare the receiver to
* @param orderRequired a boolean indicating whether the members should be sorted or not
* @param excludesSynthetic a boolean indicating whether the synthetic members should be used in
* the comparison
* @return boolean Returns true is there is a structural change between the two .class files,
* false otherwise
*/
public boolean hasStructuralChanges(byte[] newBytes, boolean orderRequired, boolean excludesSynthetic) {
try {
ClassFileReader newClassFile=
new ClassFileReader(newBytes, this.classFileName);
// type level comparison
// modifiers
if (getModifiers() != newClassFile.getModifiers())
return true;
// only consider a portion of the tagbits which indicate a structural change for dependents
// e.g. @Override change has no influence outside
long OnlyStructuralTagBits= TagBits.AnnotationTargetMASK // different @Target status ?
| TagBits.AnnotationDeprecated // different @Deprecated status ?
| TagBits.AnnotationRetentionMASK // different @Retention status ?
| TagBits.HierarchyHasProblems; // different hierarchy status ?
// meta-annotations
if ((getTagBits() & OnlyStructuralTagBits) != (newClassFile.getTagBits() & OnlyStructuralTagBits))
return true;
// annotations
if (hasStructuralAnnotationChanges(getAnnotations(), newClassFile.getAnnotations()))
return true;
// generic signature
if (!CharOperation.equals(getGenericSignature(), newClassFile.getGenericSignature()))
return true;
// superclass
if (!CharOperation.equals(getSuperclassName(), newClassFile.getSuperclassName()))
return true;
// interfaces
char[][] newInterfacesNames= newClassFile.getInterfaceNames();
if (this.interfaceNames != newInterfacesNames) { // TypeConstants.NoSuperInterfaces
int newInterfacesLength= newInterfacesNames == null ? 0 : newInterfacesNames.length;
if (newInterfacesLength != this.interfacesCount)
return true;
for (int i= 0, max= this.interfacesCount; i < max; i++)
if (!CharOperation.equals(this.interfaceNames[i], newInterfacesNames[i]))
return true;
}
// member types
IBinaryNestedType[] currentMemberTypes= getMemberTypes();
IBinaryNestedType[] otherMemberTypes= newClassFile.getMemberTypes();
if (currentMemberTypes != otherMemberTypes) { // TypeConstants.NoMemberTypes
int currentMemberTypeLength= currentMemberTypes == null ? 0 : currentMemberTypes.length;
int otherMemberTypeLength= otherMemberTypes == null ? 0 : otherMemberTypes.length;
if (currentMemberTypeLength != otherMemberTypeLength)
return true;
for (int i= 0; i < currentMemberTypeLength; i++)
if (!CharOperation.equals(currentMemberTypes[i].getName(), otherMemberTypes[i].getName())
|| currentMemberTypes[i].getModifiers() != otherMemberTypes[i].getModifiers())
return true;
}
// fields
FieldInfo[] otherFieldInfos= (FieldInfo[])newClassFile.getFields();
int otherFieldInfosLength= otherFieldInfos == null ? 0 : otherFieldInfos.length;
boolean compareFields= true;
if (this.fieldsCount == otherFieldInfosLength) {
int i= 0;
for (; i < this.fieldsCount; i++)
if (hasStructuralFieldChanges(this.fields[i], otherFieldInfos[i]))
break;
if ((compareFields= i != this.fieldsCount) && !orderRequired && !excludesSynthetic)
return true;
}
if (compareFields) {
if (this.fieldsCount != otherFieldInfosLength && !excludesSynthetic)
return true;
if (orderRequired) {
if (this.fieldsCount != 0)
Arrays.sort(this.fields);
if (otherFieldInfosLength != 0)
Arrays.sort(otherFieldInfos);
}
if (excludesSynthetic) {
if (hasNonSyntheticFieldChanges(this.fields, otherFieldInfos))
return true;
} else {
for (int i= 0; i < this.fieldsCount; i++)
if (hasStructuralFieldChanges(this.fields[i], otherFieldInfos[i]))
return true;
}
}
// methods
MethodInfo[] otherMethodInfos= (MethodInfo[])newClassFile.getMethods();
int otherMethodInfosLength= otherMethodInfos == null ? 0 : otherMethodInfos.length;
boolean compareMethods= true;
if (this.methodsCount == otherMethodInfosLength) {
int i= 0;
for (; i < this.methodsCount; i++)
if (hasStructuralMethodChanges(this.methods[i], otherMethodInfos[i]))
break;
if ((compareMethods= i != this.methodsCount) && !orderRequired && !excludesSynthetic)
return true;
}
if (compareMethods) {
if (this.methodsCount != otherMethodInfosLength && !excludesSynthetic)
return true;
if (orderRequired) {
if (this.methodsCount != 0)
Arrays.sort(this.methods);
if (otherMethodInfosLength != 0)
Arrays.sort(otherMethodInfos);
}
if (excludesSynthetic) {
if (hasNonSyntheticMethodChanges(this.methods, otherMethodInfos))
return true;
} else {
for (int i= 0; i < this.methodsCount; i++)
if (hasStructuralMethodChanges(this.methods[i], otherMethodInfos[i]))
return true;
}
}
// missing types
char[][][] missingTypes= getMissingTypeNames();
char[][][] newMissingTypes= newClassFile.getMissingTypeNames();
if (missingTypes != null) {
if (newMissingTypes == null) {
return true;
}
int length= missingTypes.length;
if (length != newMissingTypes.length) {
return true;
}
for (int i= 0; i < length; i++) {
if (!CharOperation.equals(missingTypes[i], newMissingTypes[i])) {
return true;
}
}
} else if (newMissingTypes != null) {
return true;
}
return false;
} catch (ClassFormatException e) {
return true;
}
}
private boolean hasStructuralAnnotationChanges(IBinaryAnnotation[] currentAnnotations, IBinaryAnnotation[] otherAnnotations) {
if (currentAnnotations == otherAnnotations)
return false;
int currentAnnotationsLength= currentAnnotations == null ? 0 : currentAnnotations.length;
int otherAnnotationsLength= otherAnnotations == null ? 0 : otherAnnotations.length;
if (currentAnnotationsLength != otherAnnotationsLength)
return true;
for (int i= 0; i < currentAnnotationsLength; i++) {
if (!CharOperation.equals(currentAnnotations[i].getTypeName(), otherAnnotations[i].getTypeName()))
return true;
IBinaryElementValuePair[] currentPairs= currentAnnotations[i].getElementValuePairs();
IBinaryElementValuePair[] otherPairs= otherAnnotations[i].getElementValuePairs();
int currentPairsLength= currentPairs == null ? 0 : currentPairs.length;
int otherPairsLength= otherPairs == null ? 0 : otherPairs.length;
if (currentPairsLength != otherPairsLength)
return true;
for (int j= 0; j < currentPairsLength; j++) {
if (!CharOperation.equals(currentPairs[j].getName(), otherPairs[j].getName()))
return true;
if (!currentPairs[j].getValue().equals(otherPairs[j].getValue()))
return true;
}
}
return false;
}
private boolean hasStructuralFieldChanges(FieldInfo currentFieldInfo, FieldInfo otherFieldInfo) {
// generic signature
if (!CharOperation.equals(currentFieldInfo.getGenericSignature(), otherFieldInfo.getGenericSignature()))
return true;
if (currentFieldInfo.getModifiers() != otherFieldInfo.getModifiers())
return true;
if ((currentFieldInfo.getTagBits() & TagBits.AnnotationDeprecated) != (otherFieldInfo.getTagBits() & TagBits.AnnotationDeprecated))
return true;
if (hasStructuralAnnotationChanges(currentFieldInfo.getAnnotations(), otherFieldInfo.getAnnotations()))
return true;
if (!CharOperation.equals(currentFieldInfo.getName(), otherFieldInfo.getName()))
return true;
if (!CharOperation.equals(currentFieldInfo.getTypeName(), otherFieldInfo.getTypeName()))
return true;
if (currentFieldInfo.hasConstant() != otherFieldInfo.hasConstant())
return true;
if (currentFieldInfo.hasConstant()) {
Constant currentConstant= currentFieldInfo.getConstant();
Constant otherConstant= otherFieldInfo.getConstant();
if (currentConstant.typeID() != otherConstant.typeID())
return true;
if (!currentConstant.getClass().equals(otherConstant.getClass()))
return true;
switch (currentConstant.typeID()) {
case TypeIds.T_int:
return currentConstant.intValue() != otherConstant.intValue();
case TypeIds.T_byte:
return currentConstant.byteValue() != otherConstant.byteValue();
case TypeIds.T_short:
return currentConstant.shortValue() != otherConstant.shortValue();
case TypeIds.T_char:
return currentConstant.charValue() != otherConstant.charValue();
case TypeIds.T_long:
return currentConstant.longValue() != otherConstant.longValue();
case TypeIds.T_float:
return currentConstant.floatValue() != otherConstant.floatValue();
case TypeIds.T_double:
return currentConstant.doubleValue() != otherConstant.doubleValue();
case TypeIds.T_boolean:
return currentConstant.booleanValue() != otherConstant.booleanValue();
case TypeIds.T_JavaLangString:
return !currentConstant.stringValue().equals(otherConstant.stringValue());
}
}
return false;
}
private boolean hasStructuralMethodChanges(MethodInfo currentMethodInfo, MethodInfo otherMethodInfo) {
// generic signature
if (!CharOperation.equals(currentMethodInfo.getGenericSignature(), otherMethodInfo.getGenericSignature()))
return true;
if (currentMethodInfo.getModifiers() != otherMethodInfo.getModifiers())
return true;
if ((currentMethodInfo.getTagBits() & TagBits.AnnotationDeprecated) != (otherMethodInfo.getTagBits() & TagBits.AnnotationDeprecated))
return true;
if (hasStructuralAnnotationChanges(currentMethodInfo.getAnnotations(), otherMethodInfo.getAnnotations()))
return true;
if (!CharOperation.equals(currentMethodInfo.getSelector(), otherMethodInfo.getSelector()))
return true;
if (!CharOperation.equals(currentMethodInfo.getMethodDescriptor(), otherMethodInfo.getMethodDescriptor()))
return true;
if (!CharOperation.equals(currentMethodInfo.getGenericSignature(), otherMethodInfo.getGenericSignature()))
return true;
char[][] currentThrownExceptions= currentMethodInfo.getExceptionTypeNames();
char[][] otherThrownExceptions= otherMethodInfo.getExceptionTypeNames();
if (currentThrownExceptions != otherThrownExceptions) { // TypeConstants.NoExceptions
int currentThrownExceptionsLength= currentThrownExceptions == null ? 0 : currentThrownExceptions.length;
int otherThrownExceptionsLength= otherThrownExceptions == null ? 0 : otherThrownExceptions.length;
if (currentThrownExceptionsLength != otherThrownExceptionsLength)
return true;
for (int k= 0; k < currentThrownExceptionsLength; k++)
if (!CharOperation.equals(currentThrownExceptions[k], otherThrownExceptions[k]))
return true;
}
return false;
}
/**
* This method is used to fully initialize the contents of the receiver. All methodinfos, fields
* infos will be therefore fully initialized and we can get rid of the bytes.
*/
private void initialize() throws ClassFormatException {
try {
for (int i= 0, max= this.fieldsCount; i < max; i++) {
this.fields[i].initialize();
}
for (int i= 0, max= this.methodsCount; i < max; i++) {
this.methods[i].initialize();
}
if (this.innerInfos != null) {
for (int i= 0, max= this.innerInfos.length; i < max; i++) {
this.innerInfos[i].initialize();
}
}
if (this.annotations != null) {
for (int i= 0, max= this.annotations.length; i < max; i++) {
this.annotations[i].initialize();
}
}
this.getEnclosingMethod();
reset();
} catch (RuntimeException e) {
ClassFormatException exception= new ClassFormatException(e, this.classFileName);
throw exception;
}
}
/**
* Answer true if the receiver is an anonymous type, false otherwise
*
* @return <CODE>boolean</CODE>
*/
public boolean isAnonymous() {
if (this.innerInfo == null)
return false;
char[] innerSourceName= this.innerInfo.getSourceName();
return (innerSourceName == null || innerSourceName.length == 0);
}
/**
* Answer whether the receiver contains the resolved binary form or the unresolved source form
* of the type.
*
* @return boolean
*/
public boolean isBinaryType() {
return true;
}
/**
* Answer true if the receiver is a local type, false otherwise
*
* @return <CODE>boolean</CODE>
*/
public boolean isLocal() {
if (this.innerInfo == null)
return false;
if (this.innerInfo.getEnclosingTypeName() != null)
return false;
char[] innerSourceName= this.innerInfo.getSourceName();
return (innerSourceName != null && innerSourceName.length > 0);
}
/**
* Answer true if the receiver is a member type, false otherwise
*
* @return <CODE>boolean</CODE>
*/
public boolean isMember() {
if (this.innerInfo == null)
return false;
if (this.innerInfo.getEnclosingTypeName() == null)
return false;
char[] innerSourceName= this.innerInfo.getSourceName();
return (innerSourceName != null && innerSourceName.length > 0); // protection against ill-formed attributes (67600)
}
/**
* Answer true if the receiver is a nested type, false otherwise
*
* @return <CODE>boolean</CODE>
*/
public boolean isNestedType() {
return this.innerInfo != null;
}
/**
* Answer the source file name attribute. Return null if there is no source file attribute for
* the receiver.
*
* @return char[]
*/
public char[] sourceFileName() {
return this.sourceFileName;
}
public String toString() {
java.io.ByteArrayOutputStream out= new java.io.ByteArrayOutputStream();
java.io.PrintWriter print= new java.io.PrintWriter(out);
print.println(getClass().getName() + "{"); //$NON-NLS-1$
print.println(" this.className: " + new String(getName())); //$NON-NLS-1$
print.println(" this.superclassName: " + (getSuperclassName() == null ? "null" : new String(getSuperclassName()))); //$NON-NLS-2$ //$NON-NLS-1$
print.println(" access_flags: " + printTypeModifiers(accessFlags()) + "(" + accessFlags() + ")"); //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-2$
print.flush();
return out.toString();
}
}