/******************************************************************************* | |
* Copyright (c) 2000, 2004 IBM Corporation and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Common Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/cpl-v10.html | |
* | |
* Contributors: | |
* IBM Corporation - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.wst.jsdt.internal.compiler.classfmt; | |
import java.io.File; | |
import java.io.IOException; | |
import java.util.Arrays; | |
import org.eclipse.wst.jsdt.core.compiler.CharOperation; | |
import org.eclipse.wst.jsdt.internal.compiler.codegen.AttributeNamesConstants; | |
import org.eclipse.wst.jsdt.internal.compiler.env.*; | |
import org.eclipse.wst.jsdt.internal.compiler.impl.Constant; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeIds; | |
import org.eclipse.wst.jsdt.internal.compiler.util.Util; | |
public class ClassFileReader extends ClassFileStruct implements AttributeNamesConstants, IBinaryType { | |
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( | |
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); | |
} | |
private int accessFlags; | |
private char[] classFileName; | |
private char[] className; | |
private int classNameIndex; | |
private int constantPoolCount; | |
private int[] constantPoolOffsets; | |
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[] sourceFileName; | |
private char[] superclassName; | |
private long version; | |
/** | |
* @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, 0); | |
this.classFileName = fileName; | |
int readOffset = 10; | |
try { | |
this.version = ((long)this.u2At(6) << 16) + this.u2At(4); // major<<16 + minor | |
constantPoolCount = this.u2At(8); | |
// Pass #1 - Fill in all primitive constants | |
this.constantPoolOffsets = new int[constantPoolCount]; | |
for (int i = 1; i < constantPoolCount; i++) { | |
int tag = this.u1At(readOffset); | |
switch (tag) { | |
case Utf8Tag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += u2At(readOffset + 1); | |
readOffset += ConstantUtf8FixedSize; | |
break; | |
case IntegerTag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += ConstantIntegerFixedSize; | |
break; | |
case FloatTag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += ConstantFloatFixedSize; | |
break; | |
case LongTag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += ConstantLongFixedSize; | |
i++; | |
break; | |
case DoubleTag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += ConstantDoubleFixedSize; | |
i++; | |
break; | |
case ClassTag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += ConstantClassFixedSize; | |
break; | |
case StringTag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += ConstantStringFixedSize; | |
break; | |
case FieldRefTag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += ConstantFieldRefFixedSize; | |
break; | |
case MethodRefTag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += ConstantMethodRefFixedSize; | |
break; | |
case InterfaceMethodRefTag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += ConstantInterfaceMethodRefFixedSize; | |
break; | |
case NameAndTypeTag : | |
this.constantPoolOffsets[i] = readOffset; | |
readOffset += 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 this.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 = new FieldInfo(reference, this.constantPoolOffsets, readOffset); | |
this.fields[i] = field; | |
readOffset += field.sizeInBytes(); | |
} | |
} | |
// Read the this.methods | |
this.methodsCount = u2At(readOffset); | |
readOffset += 2; | |
if (this.methodsCount != 0) { | |
this.methods = new MethodInfo[this.methodsCount]; | |
MethodInfo method; | |
for (int i = 0; i < this.methodsCount; i++) { | |
method = new MethodInfo(reference, this.constantPoolOffsets, readOffset); | |
this.methods[i] = method; | |
readOffset += method.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 (CharOperation.equals(attributeName, DeprecatedName)) { | |
this.accessFlags |= AccDeprecated; | |
} else if (CharOperation.equals(attributeName, 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(reference, this.constantPoolOffsets, innerOffset); | |
if (this.classNameIndex == this.innerInfos[j].innerClassNameIndex) { | |
this.innerInfo = this.innerInfos[j]; | |
this.innerInfoIndex = j; | |
} | |
innerOffset += 8; | |
} | |
} | |
} else if (CharOperation.equals(attributeName, SourceName)) { | |
utf8Offset = this.constantPoolOffsets[u2At(readOffset + 6)]; | |
this.sourceFileName = utf8At(utf8Offset + 3, u2At(utf8Offset + 1)); | |
} else if (CharOperation.equals(attributeName, SyntheticName)) { | |
this.accessFlags |= AccSynthetic; | |
} else if (CharOperation.equals(attributeName, SignatureName)) { | |
utf8Offset = this.constantPoolOffsets[u2At(readOffset + 6)]; | |
this.signature = utf8At(utf8Offset + 3, u2At(utf8Offset + 1)); | |
} | |
readOffset += (6 + u4At(readOffset + 2)); | |
} | |
if (fullyInitialize) { | |
this.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; | |
} | |
/** | |
* 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; | |
} | |
/* | |
* Answer the resolved compoundName of the enclosing type | |
* or null if the receiver is a top level type. | |
*/ | |
public char[] getEnclosingTypeName() { | |
if (this.innerInfo != null && !this.isAnonymous()) { | |
return this.innerInfo.getEnclosingTypeName(); | |
} | |
return null; | |
} | |
/** | |
* Answer the receiver's this.fields or null if the array is empty. | |
* @return org.eclipse.wst.jsdt.internal.compiler.api.IBinaryField[] | |
*/ | |
public IBinaryField[] getFields() { | |
return this.fields; | |
} | |
/** | |
* @see org.eclipse.wst.jsdt.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.wst.jsdt.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.wst.jsdt.internal.compiler.api.env.IBinaryMethod[] | |
*/ | |
public IBinaryMethod[] getMethods() { | |
return this.methods; | |
} | |
/** | |
* 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() { | |
if (this.innerInfo != null) { | |
if ((this.accessFlags & AccDeprecated) != 0) { | |
return this.innerInfo.getModifiers() | AccDeprecated; | |
} else { | |
return this.innerInfo.getModifiers(); | |
} | |
} | |
return this.accessFlags; | |
} | |
/** | |
* 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; | |
} | |
/** | |
* 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; | |
} | |
/** | |
* 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 (this.getModifiers() != newClassFile.getModifiers()) | |
return true; | |
// superclass | |
if (!CharOperation.equals(this.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 = this.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; | |
} | |
} | |
return false; | |
} catch (ClassFormatException e) { | |
return true; | |
} | |
} | |
private boolean hasStructuralFieldChanges(FieldInfo currentFieldInfo, FieldInfo otherFieldInfo) { | |
if (currentFieldInfo.getModifiers() != otherFieldInfo.getModifiers()) | |
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_String : | |
return !currentConstant.stringValue().equals(otherConstant.stringValue()); | |
} | |
} | |
return false; | |
} | |
private boolean hasStructuralMethodChanges(MethodInfo currentMethodInfo, MethodInfo otherMethodInfo) { | |
if (currentMethodInfo.getModifiers() != otherMethodInfo.getModifiers()) | |
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 = fieldsCount; i < max; i++) { | |
fields[i].initialize(); | |
} | |
for (int i = 0, max = methodsCount; i < max; i++) { | |
methods[i].initialize(); | |
} | |
if (innerInfos != null) { | |
for (int i = 0, max = innerInfos.length; i < max; i++) { | |
innerInfos[i].initialize(); | |
} | |
} | |
this.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[] sourceName = this.innerInfo.getSourceName(); | |
return (sourceName == null || sourceName.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 class. False otherwise. | |
* @return boolean | |
*/ | |
public boolean isClass() { | |
return (getModifiers() & AccInterface) == 0; | |
} | |
/** | |
* Answer true if the receiver is an interface. False otherwise. | |
* @return boolean | |
*/ | |
public boolean isInterface() { | |
return (getModifiers() & AccInterface) != 0; | |
} | |
/** | |
* 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[] sourceName = this.innerInfo.getSourceName(); | |
return (sourceName != null && sourceName.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[] sourceName = this.innerInfo.getSourceName(); | |
return (sourceName != null && sourceName.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; | |
} | |
protected void reset() { | |
this.constantPoolOffsets = null; | |
super.reset(); | |
} | |
/** | |
* 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(this.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: " + ClassFileStruct.printTypeModifiers(this.accessFlags()) + "(" + this.accessFlags() + ")"); //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-2$ | |
print.flush(); | |
return out.toString(); | |
} | |
} |