/*******************************************************************************
 * 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.jdt.internal.compiler.classfmt;

import java.io.File;
import java.io.IOException;
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.*;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.util.Util;

public class ClassFileReader extends ClassFileStruct implements AttributeNamesConstants, IBinaryType {
	private int constantPoolCount;
	private int[] constantPoolOffsets;
	private long version;
	private int accessFlags;
	private char[] className;
	private char[] superclassName;
	private int interfacesCount;
	private char[][] interfaceNames;
	private int fieldsCount;
	private FieldInfo[] fields;
	private int methodsCount;
	private MethodInfo[] methods;
	private InnerClassInfo[] innerInfos;
	private char[] sourceFileName;
	// initialized in case the .class file is a nested type
	private InnerClassInfo innerInfo;
	private char[] classFileName;
	private int classNameIndex;
	private int innerInfoIndex;
/**
 * @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;
						}
					}
				}
			}
			readOffset += (6 + u4At(readOffset + 2));
		}
		if (fullyInitialize) {
			this.initialize();
		}
	} catch(ClassFormatException e) {
		throw e;
	} catch (Exception e) {
		throw new ClassFormatException(
			ClassFormatException.ErrTruncatedInput, 
			readOffset); 
	}
}

/**
 * @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);
}

/**
 * 	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.jdt.internal.compiler.api.IBinaryField[]
 */
public IBinaryField[] getFields() {
	return this.fields;
}
/**
 * Answer the file name which defines the type.
 * The format is unspecified.
 */
public char[] getFileName() {
	return this.classFileName;
}
/**
 * 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;
}
/**
 * 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;
}
/**
 * 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;
}
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(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);
}
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;
}

/**
 * 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();
}
/**
 * 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 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 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 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;
}
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;

	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;
	}
}
protected void reset() {
	this.constantPoolOffsets = null;
	super.reset();
}

}
