Config editor through XML file
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / compiler / classfmt / ClassFileReader.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2001, 2002 International Business Machines Corp. and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v0.5 
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v05.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  ******************************************************************************/
11 package net.sourceforge.phpdt.internal.compiler.classfmt;
12
13 import net.sourceforge.phpdt.internal.compiler.codegen.*;
14 import net.sourceforge.phpdt.internal.compiler.env.*;
15 import net.sourceforge.phpdt.internal.compiler.impl.Constant;
16 import net.sourceforge.phpdt.internal.compiler.impl.NullConstant;
17 import net.sourceforge.phpdt.internal.compiler.lookup.TypeIds;
18 import net.sourceforge.phpdt.internal.compiler.util.*;
19
20 import java.io.*;
21 import java.util.Arrays;
22
23 public class ClassFileReader extends ClassFileStruct implements AttributeNamesConstants, IBinaryType {
24         private int constantPoolCount;
25         private int[] constantPoolOffsets;
26         private int accessFlags;
27         private char[] className;
28         private char[] superclassName;
29         private int interfacesCount;
30         private char[][] interfaceNames;
31         private int fieldsCount;
32         private FieldInfo[] fields;
33         private int methodsCount;
34         private MethodInfo[] methods;
35         private InnerClassInfo[] innerInfos;
36         private char[] sourceFileName;
37         // initialized in case the .class file is a nested type
38         private InnerClassInfo innerInfo;
39         private char[] classFileName;
40         private int classNameIndex;
41         private int innerInfoIndex;
42 /**
43  * @param classFileBytes byte[]
44  *              Actual bytes of a .class file
45  * 
46  * @param fileName char[]
47  *              Actual name of the file that contains the bytes, can be null
48  * 
49  * @param fullyInitialize boolean
50  *              Flag to fully initialize the new object
51  * @exception ClassFormatException
52  */
53 public ClassFileReader(byte[] classFileBytes, char[] fileName, boolean fullyInitialize) throws ClassFormatException {
54         // This method looks ugly but is actually quite simple, the constantPool is constructed
55         // in 3 passes.  All non-primitive constant pool members that usually refer to other members
56         // by index are tweaked to have their value in inst vars, this minor cost at read-time makes
57         // all subsequent uses of the constant pool element faster.
58         super(classFileBytes, 0);
59         this.classFileName = fileName;
60         int readOffset = 10;
61         try {
62                 constantPoolCount = this.u2At(8);
63                 // Pass #1 - Fill in all primitive constants
64                 this.constantPoolOffsets = new int[constantPoolCount];
65                 for (int i = 1; i < constantPoolCount; i++) {
66                         int tag = this.u1At(readOffset);
67                         switch (tag) {
68                                 case Utf8Tag :
69                                         this.constantPoolOffsets[i] = readOffset;
70                                         readOffset += u2At(readOffset + 1);
71                                         readOffset += ConstantUtf8FixedSize;
72                                         break;
73                                 case IntegerTag :
74                                         this.constantPoolOffsets[i] = readOffset;
75                                         readOffset += ConstantIntegerFixedSize;
76                                         break;
77                                 case FloatTag :
78                                         this.constantPoolOffsets[i] = readOffset;
79                                         readOffset += ConstantFloatFixedSize;
80                                         break;
81                                 case LongTag :
82                                         this.constantPoolOffsets[i] = readOffset;
83                                         readOffset += ConstantLongFixedSize;
84                                         i++;
85                                         break;
86                                 case DoubleTag :
87                                         this.constantPoolOffsets[i] = readOffset;
88                                         readOffset += ConstantDoubleFixedSize;
89                                         i++;
90                                         break;
91                                 case ClassTag :
92                                         this.constantPoolOffsets[i] = readOffset;
93                                         readOffset += ConstantClassFixedSize;
94                                         break;
95                                 case StringTag :
96                                         this.constantPoolOffsets[i] = readOffset;
97                                         readOffset += ConstantStringFixedSize;
98                                         break;
99                                 case FieldRefTag :
100                                         this.constantPoolOffsets[i] = readOffset;
101                                         readOffset += ConstantFieldRefFixedSize;
102                                         break;
103                                 case MethodRefTag :
104                                         this.constantPoolOffsets[i] = readOffset;
105                                         readOffset += ConstantMethodRefFixedSize;
106                                         break;
107                                 case InterfaceMethodRefTag :
108                                         this.constantPoolOffsets[i] = readOffset;
109                                         readOffset += ConstantInterfaceMethodRefFixedSize;
110                                         break;
111                                 case NameAndTypeTag :
112                                         this.constantPoolOffsets[i] = readOffset;
113                                         readOffset += ConstantNameAndTypeFixedSize;
114                         }
115                 }
116                 // Read and validate access flags
117                 this.accessFlags = u2At(readOffset);
118                 readOffset += 2;
119
120                 // Read the classname, use exception handlers to catch bad format
121                 this.classNameIndex = u2At(readOffset);
122                 this.className = getConstantClassNameAt(this.classNameIndex);
123                 readOffset += 2;
124
125                 // Read the superclass name, can be null for java.lang.Object
126                 int superclassNameIndex = u2At(readOffset);
127                 readOffset += 2;
128                 // if superclassNameIndex is equals to 0 there is no need to set a value for the 
129                 // field this.superclassName. null is fine.
130                 if (superclassNameIndex != 0) {
131                         this.superclassName = getConstantClassNameAt(superclassNameIndex);
132                 }
133
134                 // Read the interfaces, use exception handlers to catch bad format
135                 this.interfacesCount = u2At(readOffset);
136                 readOffset += 2;
137                 if (this.interfacesCount != 0) {
138                         this.interfaceNames = new char[this.interfacesCount][];
139                         for (int i = 0; i < this.interfacesCount; i++) {
140                                 this.interfaceNames[i] = getConstantClassNameAt(u2At(readOffset));
141                                 readOffset += 2;
142                         }
143                 }
144                 // Read the this.fields, use exception handlers to catch bad format
145                 this.fieldsCount = u2At(readOffset);
146                 readOffset += 2;
147                 if (this.fieldsCount != 0) {
148                         FieldInfo field;
149                         this.fields = new FieldInfo[this.fieldsCount];
150                         for (int i = 0; i < this.fieldsCount; i++) {
151                                 field = new FieldInfo(reference, this.constantPoolOffsets, readOffset);
152                                 this.fields[i] = field;
153                                 readOffset += field.sizeInBytes();
154                         }
155                 }
156                 // Read the this.methods
157                 this.methodsCount = u2At(readOffset);
158                 readOffset += 2;
159                 if (this.methodsCount != 0) {
160                         this.methods = new MethodInfo[this.methodsCount];
161                         MethodInfo method;
162                         for (int i = 0; i < this.methodsCount; i++) {
163                                 method = new MethodInfo(reference, this.constantPoolOffsets, readOffset);
164                                 this.methods[i] = method;
165                                 readOffset += method.sizeInBytes();
166                         }
167                 }
168
169                 // Read the attributes
170                 int attributesCount = u2At(readOffset);
171                 readOffset += 2;
172
173                 for (int i = 0; i < attributesCount; i++) {
174                         int utf8Offset = this.constantPoolOffsets[u2At(readOffset)];
175                         char[] attributeName = utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
176                         if (CharOperation.equals(attributeName, DeprecatedName)) {
177                                 this.accessFlags |= AccDeprecated;
178                         } else {
179                                 if (CharOperation.equals(attributeName, InnerClassName)) {
180                                         int innerOffset = readOffset + 6;
181                                         int number_of_classes = u2At(innerOffset);
182                                         if (number_of_classes != 0) {
183                                                 this.innerInfos = new InnerClassInfo[number_of_classes];
184                                                 for (int j = 0; j < number_of_classes; j++) {
185                                                         this.innerInfos[j] = 
186                                                                 new InnerClassInfo(reference, this.constantPoolOffsets, innerOffset + 2); 
187                                                         if (this.classNameIndex == this.innerInfos[j].innerClassNameIndex) {
188                                                                 this.innerInfo = this.innerInfos[j];
189                                                                 this.innerInfoIndex = j;
190                                                         }
191                                                         innerOffset += 8;
192                                                 }
193                                         }
194                                 } else {
195                                         if (CharOperation.equals(attributeName, SourceName)) {
196                                                 utf8Offset = this.constantPoolOffsets[u2At(readOffset + 6)];
197                                                 this.sourceFileName = utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
198                                         } else {
199                                                 if (CharOperation.equals(attributeName, SyntheticName)) {
200                                                         this.accessFlags |= AccSynthetic;
201                                                 }
202                                         }
203                                 }
204                         }
205                         readOffset += (6 + u4At(readOffset + 2));
206                 }
207                 if (fullyInitialize) {
208                         this.initialize();
209                 }
210         } catch (Exception e) {
211                 throw new ClassFormatException(
212                         ClassFormatException.ErrTruncatedInput, 
213                         readOffset); 
214         }
215 }
216
217 /**
218  * @param classFileBytes Actual bytes of a .class file
219  * @param fileName      Actual name of the file that contains the bytes, can be null
220  * 
221  * @exception ClassFormatException
222  */
223 public ClassFileReader(byte classFileBytes[], char[] fileName) throws ClassFormatException {
224         this(classFileBytes, fileName, false);
225 }
226
227 /**
228  *      Answer the receiver's access flags.  The value of the access_flags
229  *      item is a mask of modifiers used with class and interface declarations.
230  *  @return int 
231  */
232 public int accessFlags() {
233         return this.accessFlags;
234 }
235 /**
236  * Answer the char array that corresponds to the class name of the constant class.
237  * constantPoolIndex is the index in the constant pool that is a constant class entry.
238  *
239  * @param int constantPoolIndex
240  * @return char[]
241  */
242 private char[] getConstantClassNameAt(int constantPoolIndex) {
243         int utf8Offset = this.constantPoolOffsets[u2At(this.constantPoolOffsets[constantPoolIndex] + 1)];
244         return utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
245 }
246 /**
247  * Answer the int array that corresponds to all the offsets of each entry in the constant pool
248  *
249  * @return int[]
250  */
251 public int[] getConstantPoolOffsets() {
252         return this.constantPoolOffsets;
253 }
254 /*
255  * Answer the resolved compoundName of the enclosing type
256  * or null if the receiver is a top level type.
257  */
258 public char[] getEnclosingTypeName() {
259         if (this.innerInfo != null && !this.isAnonymous()) {
260                 return this.innerInfo.getEnclosingTypeName();
261         }
262         return null;
263 }
264 /**
265  * Answer the receiver's this.fields or null if the array is empty.
266  * @return org.eclipse.jdt.internal.compiler.api.IBinaryField[]
267  */
268 public IBinaryField[] getFields() {
269         return this.fields;
270 }
271 /**
272  * Answer the file name which defines the type.
273  * The format is unspecified.
274  */
275 public char[] getFileName() {
276         return this.classFileName;
277 }
278 /**
279  * 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.
280  * e.g.
281  * public class A {
282  *      public class B {
283  *      }
284  *      public void foo() {
285  *              class C {}
286  *      }
287  *      public Runnable bar() {
288  *              return new Runnable() {
289  *                      public void run() {}
290  *              };
291  *      }
292  * }
293  * It returns {'B'} for the member A$B
294  * It returns null for A
295  * It returns {'C'} for the local class A$1$C
296  * It returns null for the anonymous A$1
297  * @return char[]
298  */
299 public char[] getInnerSourceName() {
300         if (this.innerInfo != null)
301                 return this.innerInfo.getSourceName();
302         return null;
303 }
304 /**
305  * Answer the resolved names of the receiver's interfaces in the
306  * class file format as specified in section 4.2 of the Java 2 VM spec
307  * or null if the array is empty.
308  *
309  * For example, java.lang.String is java/lang/String.
310  * @return char[][]
311  */
312 public char[][] getInterfaceNames() {
313         return this.interfaceNames;
314 }
315 /**
316  * Answer the receiver's nested types or null if the array is empty.
317  *
318  * This nested type info is extracted from the inner class attributes.
319  * Ask the name environment to find a member type using its compound name
320  * @return org.eclipse.jdt.internal.compiler.api.IBinaryNestedType[]
321  */
322 public IBinaryNestedType[] getMemberTypes() {
323         // we might have some member types of the current type
324         if (this.innerInfos == null) return null;
325
326         int length = this.innerInfos.length;
327         int startingIndex = this.innerInfo != null ? this.innerInfoIndex + 1 : 0;
328         if (length != startingIndex) {
329                 IBinaryNestedType[] memberTypes = 
330                         new IBinaryNestedType[length - this.innerInfoIndex]; 
331                 int memberTypeIndex = 0;
332                 for (int i = startingIndex; i < length; i++) {
333                         InnerClassInfo currentInnerInfo = this.innerInfos[i];
334                         int outerClassNameIdx = currentInnerInfo.outerClassNameIndex;
335                         int innerNameIndex = currentInnerInfo.innerNameIndex;
336                         /*
337                          * Checking that outerClassNameIDx is different from 0 should be enough to determine if an inner class
338                          * attribute entry is a member class, but due to the bug:
339                          * http://dev.eclipse.org/bugs/show_bug.cgi?id=14592
340                          * we needed to add an extra check. So we check that innerNameIndex is different from 0 as well.
341                          */
342                         if (outerClassNameIdx != 0 && innerNameIndex != 0 && outerClassNameIdx == this.classNameIndex) {
343                                 memberTypes[memberTypeIndex++] = currentInnerInfo;
344                         }
345                 }
346                 if (memberTypeIndex == 0) return null;
347                 if (memberTypeIndex != memberTypes.length) {
348                         // we need to resize the memberTypes array. Some local or anonymous classes
349                         // are present in the current class.
350                         System.arraycopy(
351                                 memberTypes, 
352                                 0, 
353                                 (memberTypes = new IBinaryNestedType[memberTypeIndex]), 
354                                 0, 
355                                 memberTypeIndex); 
356                 }
357                 return memberTypes;
358         }
359         return null;
360 }
361 /**
362  * Answer the receiver's this.methods or null if the array is empty.
363  * @return org.eclipse.jdt.internal.compiler.api.env.IBinaryMethod[]
364  */
365 public IBinaryMethod[] getMethods() {
366         return this.methods;
367 }
368 /**
369  * Answer an int whose bits are set according the access constants
370  * defined by the VM spec.
371  * Set the AccDeprecated and AccSynthetic bits if necessary
372  * @return int
373  */
374 public int getModifiers() {
375         if (this.innerInfo != null) {
376                 return this.innerInfo.getModifiers();
377         }
378         return this.accessFlags;
379 }
380 /**
381  * Answer the resolved name of the type in the
382  * class file format as specified in section 4.2 of the Java 2 VM spec.
383  *
384  * For example, java.lang.String is java/lang/String.
385  * @return char[]
386  */
387 public char[] getName() {
388         return this.className;
389 }
390 /**
391  * Answer the resolved name of the receiver's superclass in the
392  * class file format as specified in section 4.2 of the Java 2 VM spec
393  * or null if it does not have one.
394  *
395  * For example, java.lang.String is java/lang/String.
396  * @return char[]
397  */
398 public char[] getSuperclassName() {
399         return this.superclassName;
400 }
401 /**
402  * Answer true if the receiver is an anonymous type, false otherwise
403  *
404  * @return <CODE>boolean</CODE>
405  */
406 public boolean isAnonymous() {
407         if (this.innerInfo == null) return false;
408         char[] sourceName = this.innerInfo.getSourceName();
409         return (sourceName == null || sourceName.length == 0);
410 }
411 /**
412  * Answer whether the receiver contains the resolved binary form
413  * or the unresolved source form of the type.
414  * @return boolean
415  */
416 public boolean isBinaryType() {
417         return true;
418 }
419 /**
420  * Answer true if the receiver is a class. False otherwise.
421  * @return boolean
422  */
423 public boolean isClass() {
424         return (getModifiers() & AccInterface) == 0;
425 }
426 /**
427  * Answer true if the receiver is an interface. False otherwise.
428  * @return boolean
429  */
430 public boolean isInterface() {
431         return (getModifiers() & AccInterface) != 0;
432 }
433 /**
434  * Answer true if the receiver is a local type, false otherwise
435  *
436  * @return <CODE>boolean</CODE>
437  */
438 public boolean isLocal() {
439         return 
440                 this.innerInfo != null 
441                 && this.innerInfo.getEnclosingTypeName() == null 
442                 && this.innerInfo.getSourceName() != null;
443 }
444 /**
445  * Answer true if the receiver is a member type, false otherwise
446  *
447  * @return <CODE>boolean</CODE>
448  */
449 public boolean isMember() {
450         return this.innerInfo != null && this.innerInfo.getEnclosingTypeName() != null;
451 }
452 /**
453  * Answer true if the receiver is a nested type, false otherwise
454  *
455  * @return <CODE>boolean</CODE>
456  */
457 public boolean isNestedType() {
458         return this.innerInfo != null;
459 }
460 public static ClassFileReader read(File file) throws ClassFormatException, IOException {
461         return read(file, false);
462 }
463 public static ClassFileReader read(File file, boolean fullyInitialize) throws ClassFormatException, IOException {
464         byte classFileBytes[] = Util.getFileByteContent(file);
465         ClassFileReader classFileReader = new ClassFileReader(classFileBytes, file.getAbsolutePath().toCharArray());
466         if (fullyInitialize) {
467                 classFileReader.initialize();
468         }
469         return classFileReader;
470 }
471 public static ClassFileReader read(String fileName) throws ClassFormatException, java.io.IOException {
472         return read(fileName, false);
473 }
474 public static ClassFileReader read(String fileName, boolean fullyInitialize) throws ClassFormatException, java.io.IOException {
475         return read(new File(fileName), fullyInitialize);
476 }
477 public static ClassFileReader read(
478         java.util.zip.ZipFile zip, 
479         String filename)
480         throws ClassFormatException, java.io.IOException {
481                 return read(zip, filename, false);
482 }
483 public static ClassFileReader read(
484         java.util.zip.ZipFile zip, 
485         String filename,
486         boolean fullyInitialize)
487         throws ClassFormatException, java.io.IOException {
488         java.util.zip.ZipEntry ze = zip.getEntry(filename);
489         if (ze == null)
490                 return null;
491         byte classFileBytes[] = Util.getZipEntryByteContent(ze, zip);
492         ClassFileReader classFileReader = new ClassFileReader(classFileBytes, filename.toCharArray());
493         if (fullyInitialize) {
494                 classFileReader.initialize();
495         }
496         return classFileReader;
497 }
498
499 /**
500  * Answer the source file name attribute. Return null if there is no source file attribute for the receiver.
501  * 
502  * @return char[]
503  */
504 public char[] sourceFileName() {
505         return this.sourceFileName;
506 }
507 public String toString() {
508         java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
509         java.io.PrintWriter print = new java.io.PrintWriter(out);
510         
511         print.println(this.getClass().getName() + "{"); //$NON-NLS-1$
512         print.println(" this.className: " + new String(getName())); //$NON-NLS-1$
513         print.println(" this.superclassName: " + (getSuperclassName() == null ? "null" : new String(getSuperclassName()))); //$NON-NLS-2$ //$NON-NLS-1$
514         print.println(" access_flags: " + ClassFileStruct.printTypeModifiers(this.accessFlags()) + "(" + this.accessFlags() + ")"); //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-2$
515
516         print.flush();
517         return out.toString();
518 }
519 /**
520  * Check if the receiver has structural changes compare to the byte array in argument.
521  * Structural changes are:
522  * - modifiers changes for the class, the this.fields or the this.methods
523  * - signature changes for this.fields or this.methods.
524  * - changes in the number of this.fields or this.methods
525  * - changes for field constants
526  * - changes for thrown exceptions
527  * - change for the super class or any super interfaces.
528  * - changes for member types name or modifiers
529  * If any of these changes occurs, the method returns true. false otherwise. 
530  * The synthetic fields are included and the members are not required to be sorted.
531  * @param newBytes the bytes of the .class file we want to compare the receiver to
532  * @return boolean Returns true is there is a structural change between the two .class files, false otherwise
533  */
534 public boolean hasStructuralChanges(byte[] newBytes) {
535         return hasStructuralChanges(newBytes, true, true);
536 }
537 /**
538  * Check if the receiver has structural changes compare to the byte array in argument.
539  * Structural changes are:
540  * - modifiers changes for the class, the this.fields or the this.methods
541  * - signature changes for this.fields or this.methods.
542  * - changes in the number of this.fields or this.methods
543  * - changes for field constants
544  * - changes for thrown exceptions
545  * - change for the super class or any super interfaces.
546  * - changes for member types name or modifiers
547  * If any of these changes occurs, the method returns true. false otherwise.
548  * @param newBytes the bytes of the .class file we want to compare the receiver to
549  * @param orderRequired a boolean indicating whether the members should be sorted or not
550  * @param excludesSynthetics a boolean indicating whether the synthetic members should be used in the comparison
551  * @return boolean Returns true is there is a structural change between the two .class files, false otherwise
552  */
553 public boolean hasStructuralChanges(byte[] newBytes, boolean orderRequired, boolean excludesSynthetic) {
554         try {
555                 ClassFileReader newClassFile =
556                         new ClassFileReader(newBytes, this.classFileName);
557                 // type level comparison
558                 // modifiers
559                 if (this.getModifiers() != newClassFile.getModifiers())
560                         return true;
561                 // superclass
562                 if (!CharOperation.equals(this.getSuperclassName(), newClassFile.getSuperclassName()))
563                         return true;
564                 // interfaces
565                 char[][] newInterfacesNames = newClassFile.getInterfaceNames();
566                 if (this.interfaceNames != newInterfacesNames) { // TypeConstants.NoSuperInterfaces
567                         int newInterfacesLength = newInterfacesNames == null ? 0 : newInterfacesNames.length;
568                         if (newInterfacesLength != this.interfacesCount)
569                                 return true;
570                         for (int i = 0, max = this.interfacesCount; i < max; i++)
571                                 if (!CharOperation.equals(this.interfaceNames[i], newInterfacesNames[i]))
572                                         return true;
573                 }
574
575                 // member types
576                 IBinaryNestedType[] currentMemberTypes = (IBinaryNestedType[]) this.getMemberTypes();
577                 IBinaryNestedType[] otherMemberTypes = (IBinaryNestedType[]) newClassFile.getMemberTypes();
578                 if (currentMemberTypes != otherMemberTypes) { // TypeConstants.NoMemberTypes
579                         int currentMemberTypeLength = currentMemberTypes == null ? 0 : currentMemberTypes.length;
580                         int otherMemberTypeLength = otherMemberTypes == null ? 0 : otherMemberTypes.length;
581                         if (currentMemberTypeLength != otherMemberTypeLength)
582                                 return true;
583                         for (int i = 0; i < currentMemberTypeLength; i++)
584                                 if (!CharOperation.equals(currentMemberTypes[i].getName(), otherMemberTypes[i].getName())
585                                         || currentMemberTypes[i].getModifiers() != otherMemberTypes[i].getModifiers())
586                                                 return true;
587                 }
588
589                 // fields
590                 FieldInfo[] otherFieldInfos = (FieldInfo[]) newClassFile.getFields();
591                 int otherFieldInfosLength = otherFieldInfos == null ? 0 : otherFieldInfos.length;
592                 boolean compareFields = true;
593                 if (this.fieldsCount == otherFieldInfosLength) {
594                         int i = 0;
595                         for (; i < this.fieldsCount; i++)
596                                 if (hasStructuralFieldChanges(this.fields[i], otherFieldInfos[i])) break;
597                         if ((compareFields = i != this.fieldsCount) && !orderRequired && !excludesSynthetic)
598                                 return true;
599                 }
600                 if (compareFields) {
601                         if (this.fieldsCount != otherFieldInfosLength && !excludesSynthetic)
602                                 return true;
603                         if (orderRequired) {
604                                 if (this.fieldsCount != 0)
605                                         Arrays.sort(this.fields);
606                                 if (otherFieldInfosLength != 0)
607                                         Arrays.sort(otherFieldInfos);
608                         }
609                         if (excludesSynthetic) {
610                                 if (hasNonSyntheticFieldChanges(this.fields, otherFieldInfos))
611                                         return true;
612                         } else {
613                                 for (int i = 0; i < this.fieldsCount; i++)
614                                         if (hasStructuralFieldChanges(this.fields[i], otherFieldInfos[i]))
615                                                 return true;
616                         }
617                 }
618                 
619                 // methods
620                 MethodInfo[] otherMethodInfos = (MethodInfo[]) newClassFile.getMethods();
621                 int otherMethodInfosLength = otherMethodInfos == null ? 0 : otherMethodInfos.length;
622                 boolean compareMethods = true;
623                 if (this.methodsCount == otherMethodInfosLength) {
624                         int i = 0;
625                         for (; i < this.methodsCount; i++)
626                                 if (hasStructuralMethodChanges(this.methods[i], otherMethodInfos[i])) break;
627                         if ((compareMethods = i != this.methodsCount) && !orderRequired && !excludesSynthetic)
628                                 return true;
629                 }
630                 if (compareMethods) {
631                         if (this.methodsCount != otherMethodInfosLength && !excludesSynthetic)
632                                 return true;
633                         if (orderRequired) {
634                                 if (this.methodsCount != 0)
635                                         Arrays.sort(this.methods);
636                                 if (otherMethodInfosLength != 0)
637                                         Arrays.sort(otherMethodInfos);  
638                         }
639                         if (excludesSynthetic) {
640                                 if (hasNonSyntheticMethodChanges(this.methods, otherMethodInfos))
641                                         return true;
642                         } else {
643                                 for (int i = 0; i < this.methodsCount; i++)
644                                         if (hasStructuralMethodChanges(this.methods[i], otherMethodInfos[i]))
645                                                 return true;
646                         }
647                 }
648
649                 return false;
650         } catch (ClassFormatException e) {
651                 return true;
652         }
653 }
654 private boolean hasNonSyntheticFieldChanges(FieldInfo[] currentFieldInfos, FieldInfo[] otherFieldInfos) {
655         int length1 = currentFieldInfos == null ? 0 : currentFieldInfos.length;
656         int length2 = otherFieldInfos == null ? 0 : otherFieldInfos.length;
657         int index1 = 0;
658         int index2 = 0;
659
660         end : while (index1 < length1 && index2 < length2) {
661                 while (currentFieldInfos[index1].isSynthetic()) {
662                         if (++index1 >= length1) break end;
663                 }
664                 while (otherFieldInfos[index2].isSynthetic()) {
665                         if (++index2 >= length2) break end;
666                 }
667                 if (hasStructuralFieldChanges(currentFieldInfos[index1++], otherFieldInfos[index2++]))
668                         return true;
669         }
670
671         while (index1 < length1) {
672                 if (!currentFieldInfos[index1++].isSynthetic()) return true;
673         }
674         while (index2 < length2) {
675                 if (!otherFieldInfos[index2++].isSynthetic()) return true;
676         }
677         return false;
678 }
679 private boolean hasStructuralFieldChanges(FieldInfo currentFieldInfo, FieldInfo otherFieldInfo) {
680         if (currentFieldInfo.getModifiers() != otherFieldInfo.getModifiers())
681                 return true;
682         if (!CharOperation.equals(currentFieldInfo.getName(), otherFieldInfo.getName()))
683                 return true;
684         if (!CharOperation.equals(currentFieldInfo.getTypeName(), otherFieldInfo.getTypeName()))
685                 return true;
686         if (currentFieldInfo.hasConstant() != otherFieldInfo.hasConstant())
687                 return true;
688         if (currentFieldInfo.hasConstant()) {
689                 Constant currentConstant = currentFieldInfo.getConstant();
690                 Constant otherConstant = otherFieldInfo.getConstant();
691                 if (currentConstant.typeID() != otherConstant.typeID())
692                         return true;
693                 if (!currentConstant.getClass().equals(otherConstant.getClass()))
694                         return true;
695                 switch (currentConstant.typeID()) {
696                         case TypeIds.T_int :
697                                 return currentConstant.intValue() != otherConstant.intValue();
698                         case TypeIds.T_byte :
699                                 return currentConstant.byteValue() != otherConstant.byteValue();
700                         case TypeIds.T_short :
701                                 return currentConstant.shortValue() != otherConstant.shortValue();
702                         case TypeIds.T_char :
703                                 return currentConstant.charValue() != otherConstant.charValue();
704                         case TypeIds.T_float :
705                                 return currentConstant.floatValue() != otherConstant.floatValue();
706                         case TypeIds.T_double :
707                                 return currentConstant.doubleValue() != otherConstant.doubleValue();
708                         case TypeIds.T_boolean :
709                                 return currentConstant.booleanValue() != otherConstant.booleanValue();
710                         case TypeIds.T_String :
711                                 return !currentConstant.stringValue().equals(otherConstant.stringValue());
712                         case TypeIds.T_null :
713                                 return otherConstant != NullConstant.Default;
714                 }
715         }
716         return false;
717 }
718 private boolean hasNonSyntheticMethodChanges(MethodInfo[] currentMethodInfos, MethodInfo[] otherMethodInfos) {
719         int length1 = currentMethodInfos == null ? 0 : currentMethodInfos.length;
720         int length2 = otherMethodInfos == null ? 0 : otherMethodInfos.length;
721         int index1 = 0;
722         int index2 = 0;
723
724         MethodInfo m;
725         end : while (index1 < length1 && index2 < length2) {
726                 while ((m = currentMethodInfos[index1]).isSynthetic() || m.isClinit()) {
727                         if (++index1 >= length1) break end;
728                 }
729                 while ((m = otherMethodInfos[index2]).isSynthetic() || m.isClinit()) {
730                         if (++index2 >= length2) break end;
731                 }
732                 if (hasStructuralMethodChanges(currentMethodInfos[index1++], otherMethodInfos[index2++]))
733                         return true;
734         }
735
736         while (index1 < length1) {
737                 if (!((m = currentMethodInfos[index1++]).isSynthetic() || m.isClinit())) return true;
738         }
739         while (index2 < length2) {
740                 if (!((m = otherMethodInfos[index2++]).isSynthetic() || m.isClinit())) return true;
741         }
742         return false;
743 }
744 private boolean hasStructuralMethodChanges(MethodInfo currentMethodInfo, MethodInfo otherMethodInfo) {
745         if (currentMethodInfo.getModifiers() != otherMethodInfo.getModifiers())
746                 return true;
747         if (!CharOperation.equals(currentMethodInfo.getSelector(), otherMethodInfo.getSelector()))
748                 return true;
749         if (!CharOperation.equals(currentMethodInfo.getMethodDescriptor(), otherMethodInfo.getMethodDescriptor()))
750                 return true;
751
752         char[][] currentThrownExceptions = currentMethodInfo.getExceptionTypeNames();
753         char[][] otherThrownExceptions = otherMethodInfo.getExceptionTypeNames();
754         if (currentThrownExceptions != otherThrownExceptions) { // TypeConstants.NoExceptions
755                 int currentThrownExceptionsLength = currentThrownExceptions == null ? 0 : currentThrownExceptions.length;
756                 int otherThrownExceptionsLength = otherThrownExceptions == null ? 0 : otherThrownExceptions.length;
757                 if (currentThrownExceptionsLength != otherThrownExceptionsLength)
758                         return true;
759                 for (int k = 0; k < currentThrownExceptionsLength; k++)
760                         if (!CharOperation.equals(currentThrownExceptions[k], otherThrownExceptions[k]))
761                                 return true;
762         }
763         return false;
764 }
765 /**
766  * This method is used to fully initialize the contents of the receiver. All methodinfos, fields infos
767  * will be therefore fully initialized and we can get rid of the bytes.
768  */
769 private void initialize() {
770         for (int i = 0, max = fieldsCount; i < max; i++) {
771                 fields[i].initialize();
772         }
773         for (int i = 0, max = methodsCount; i < max; i++) {
774                 methods[i].initialize();
775         }
776         if (innerInfos != null) {
777                 for (int i = 0, max = innerInfos.length; i < max; i++) {
778                         innerInfos[i].initialize();
779                 }
780         }
781         this.reset();
782 }
783 protected void reset() {
784         this.constantPoolOffsets = null;
785         super.reset();
786 }
787
788 }