KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > loskutov > bco > ui > JdtUtils


1 /*****************************************************************************************
2  * Copyright (c) 2004 Andrei Loskutov. All rights reserved. This program and the
3  * accompanying materials are made available under the terms of the BSD License which
4  * accompanies this distribution, and is available at
5  * http://www.opensource.org/licenses/bsd-license.php Contributor: Andrei Loskutov -
6  * initial API and implementation
7  ****************************************************************************************/

8 package de.loskutov.bco.ui;
9
10 import java.io.File JavaDoc;
11 import java.io.FileInputStream JavaDoc;
12 import java.io.FileNotFoundException JavaDoc;
13 import java.io.IOException JavaDoc;
14 import java.io.InputStream JavaDoc;
15 import java.net.MalformedURLException JavaDoc;
16 import java.net.URL JavaDoc;
17 import java.net.URLClassLoader JavaDoc;
18 import java.util.ArrayList JavaDoc;
19 import java.util.Arrays JavaDoc;
20 import java.util.Comparator JavaDoc;
21 import java.util.List JavaDoc;
22 import java.util.jar.JarEntry JavaDoc;
23 import java.util.jar.JarFile JavaDoc;
24
25 import org.eclipse.core.resources.IFolder;
26 import org.eclipse.core.resources.IPathVariableManager;
27 import org.eclipse.core.resources.IProject;
28 import org.eclipse.core.resources.IResource;
29 import org.eclipse.core.resources.IWorkspace;
30 import org.eclipse.core.resources.IWorkspaceRoot;
31 import org.eclipse.core.resources.ResourcesPlugin;
32 import org.eclipse.core.runtime.IPath;
33 import org.eclipse.core.runtime.IStatus;
34 import org.eclipse.jdt.core.Flags;
35 import org.eclipse.jdt.core.IClassFile;
36 import org.eclipse.jdt.core.IClasspathEntry;
37 import org.eclipse.jdt.core.ICompilationUnit;
38 import org.eclipse.jdt.core.IInitializer;
39 import org.eclipse.jdt.core.IJavaElement;
40 import org.eclipse.jdt.core.IJavaProject;
41 import org.eclipse.jdt.core.IMember;
42 import org.eclipse.jdt.core.IMethod;
43 import org.eclipse.jdt.core.IPackageFragment;
44 import org.eclipse.jdt.core.IPackageFragmentRoot;
45 import org.eclipse.jdt.core.IParent;
46 import org.eclipse.jdt.core.ISourceRange;
47 import org.eclipse.jdt.core.IType;
48 import org.eclipse.jdt.core.ITypeParameter;
49 import org.eclipse.jdt.core.JavaCore;
50 import org.eclipse.jdt.core.JavaModelException;
51 import org.eclipse.jdt.core.Signature;
52 import org.eclipse.jface.text.ITextSelection;
53 import org.objectweb.asm.tree.ClassNode;
54 import org.objectweb.asm.tree.InnerClassNode;
55
56 import de.loskutov.bco.BytecodeOutlinePlugin;
57 import de.loskutov.bco.asm.DecompiledClass;
58
59 /**
60  * @author Andrei
61  */

62 public class JdtUtils {
63     /** package separator in bytecode notation */
64     private static final char PACKAGE_SEPARATOR = '/';
65     /** type name separator (for inner types) in bytecode notation */
66     private static final char TYPE_SEPARATOR = '$';
67
68     /**
69      *
70      */

71     private JdtUtils() {
72         // don't call
73
}
74
75     public static IJavaElement getMethod(IParent parent, String JavaDoc signature){
76         try {
77             IJavaElement[] children = parent.getChildren();
78             for (int i = 0; i < children.length; i++) {
79                 IJavaElement javaElement = children[i];
80                 switch (javaElement.getElementType()) {
81                     case IJavaElement.INITIALIZER :
82                         // fall through
83
case IJavaElement.METHOD :
84                         if(signature.equals(getMethodSignature(javaElement))){
85                             return javaElement;
86                         }
87                         break;
88                     default :
89                         break;
90                 }
91                 if(javaElement instanceof IParent){
92                     javaElement = getMethod((IParent) javaElement, signature);
93                     if(javaElement != null){
94                         return javaElement;
95                     }
96                 }
97             }
98         } catch (JavaModelException e) {
99             // just ignore it. Mostly caused by class files not on the class path
100
// which is not a problem for us, but a big problem for JDT
101
}
102         return null;
103     }
104
105     /**
106      * @param childEl
107      * @return method signature, if given java element is either initializer or method,
108      * otherwise returns null.
109      */

110     public static String JavaDoc getMethodSignature(IJavaElement childEl) {
111         String JavaDoc methodName = null;
112         if (childEl.getElementType() == IJavaElement.INITIALIZER) {
113             IInitializer ini = (IInitializer) childEl;
114             try {
115                 if (Flags.isStatic(ini.getFlags())) {
116                     methodName = "<clinit>()V";
117                 } else {
118                     methodName = "<init>()";
119                 }
120             } catch (JavaModelException e) {
121                 // this is compilation problem - don't show the message
122
BytecodeOutlinePlugin.log(e, IStatus.WARNING);
123             }
124         } else if (childEl.getElementType() == IJavaElement.METHOD) {
125             IMethod iMethod = (IMethod) childEl;
126             try {
127                 methodName = createMethodSignature(iMethod);
128             } catch (JavaModelException e) {
129                 // this is compilation problem - don't show the message
130
BytecodeOutlinePlugin.log(e, IStatus.WARNING);
131             }
132         }
133         return methodName;
134     }
135
136     public static String JavaDoc createMethodSignature(IMethod iMethod)
137         throws JavaModelException {
138         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
139
140         // Eclipse put class name as constructor name - we change it!
141
if (iMethod.isConstructor()) {
142             sb.append("<init>"); //$NON-NLS-1$
143
} else {
144             sb.append(iMethod.getElementName());
145         }
146
147         if (iMethod.isBinary()) { // iMethod instanceof BinaryMember
148
// binary info should be full qualified
149
return sb.append(iMethod.getSignature()).toString();
150         }
151
152         // start method parameter descriptions list
153
sb.append('(');
154         IType declaringType = iMethod.getDeclaringType();
155         String JavaDoc[] parameterTypes = iMethod.getParameterTypes();
156
157         /*
158          * For non - static inner classes bytecode constructor should contain as first
159          * parameter the enclosing type instance, but in Eclipse AST there are no
160          * appropriated parameter. So we need to create enclosing type signature and
161          * add it as first parameter.
162          */

163         if (iMethod.isConstructor() && isNonStaticInner(declaringType)) {
164             // this is a very special case
165
String JavaDoc typeSignature = getTypeSignature(getFirstAncestor(declaringType));
166             if(typeSignature != null) {
167                 String JavaDoc [] newParams = new String JavaDoc [parameterTypes.length + 1];
168                 newParams[0] = typeSignature;
169                 System.arraycopy(parameterTypes, 0, newParams, 1, parameterTypes.length);
170                 parameterTypes = newParams;
171             }
172         }
173
174         // doSomething(Lgenerics/DummyForAsmGenerics;)Lgenerics/DummyForAsmGenerics;
175
for (int i = 0; i < parameterTypes.length; i++) {
176             String JavaDoc resolvedType = getResolvedType(parameterTypes[i], declaringType);
177             if(resolvedType != null && resolvedType.length() > 0){
178                 sb.append(resolvedType);
179             } else {
180                 // this is a generic type
181
appendGenericType(sb, iMethod, parameterTypes[i]);
182             }
183         }
184         sb.append(')');
185
186         // continue here with adding resolved return type
187
String JavaDoc returnType = iMethod.getReturnType();
188         String JavaDoc resolvedType = getResolvedType(returnType, declaringType);
189         if(resolvedType != null && resolvedType.length() > 0){
190             sb.append(resolvedType);
191         } else {
192             // this is a generic type
193
appendGenericType(sb, iMethod, returnType);
194         }
195
196         return sb.toString();
197     }
198
199     /**
200      * @param type
201      * @return full qualified, resolved type name in bytecode notation
202      */

203     private static String JavaDoc getTypeSignature(IType type) {
204         if(type == null){
205             return null;
206         }
207         /*
208          * getFullyQualifiedName() returns name, where package separator is '.',
209          * but we need '/' for bytecode. The hack with ',' is to use a character
210          * which is not allowed as Java char to be sure not to replace too much
211          */

212         String JavaDoc name = type.getFullyQualifiedName(',');
213         // replace package separators
214
name = name.replace(Signature.C_DOT, PACKAGE_SEPARATOR);
215         // replace class separators
216
name = name.replace(',', TYPE_SEPARATOR);
217         return Signature.C_RESOLVED + name + Signature.C_SEMICOLON;
218     }
219
220     private static void appendGenericType(StringBuffer JavaDoc sb, IMethod iMethod,
221         String JavaDoc unresolvedType) throws JavaModelException{
222         IType declaringType = iMethod.getDeclaringType();
223
224         // unresolvedType is here like "QA;" => we remove "Q" and ";"
225
if(unresolvedType.length() < 3){
226             // ???? something wrong here ....
227
sb.append(unresolvedType);
228             return;
229         }
230         unresolvedType = unresolvedType.substring(1, unresolvedType.length() - 1);
231
232         ITypeParameter typeParameter = iMethod.getTypeParameter(unresolvedType);
233         if(typeParameter == null || !typeParameter.exists()){
234             typeParameter = declaringType.getTypeParameter(unresolvedType);
235         }
236
237         String JavaDoc[] bounds = typeParameter.getBounds();
238         if(bounds.length == 0){
239             sb.append("Ljava/lang/Object;");
240         } else {
241             for (int i = 0; i < bounds.length; i++) {
242                 String JavaDoc simplyName = bounds[i];
243                 simplyName = Signature.C_UNRESOLVED + simplyName + Signature.C_NAME_END;
244                 String JavaDoc resolvedType = getResolvedType(simplyName, declaringType);
245                 sb.append(resolvedType);
246             }
247         }
248     }
249
250     /**
251      * @param typeToResolve
252      * @param declaringType
253      * @return full qualified "bytecode formatted" type
254      * @throws JavaModelException
255      */

256     private static String JavaDoc getResolvedType(String JavaDoc typeToResolve,
257         IType declaringType) throws JavaModelException {
258         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
259         int arrayCount = Signature.getArrayCount(typeToResolve);
260         // test which letter is following - Q or L are for reference types
261
boolean isPrimitive = isPrimitiveType(typeToResolve.charAt(arrayCount));
262         if (isPrimitive) {
263             // simply add whole string (probably with array chars like [[I etc.)
264
sb.append(typeToResolve);
265         } else {
266             boolean isUnresolvedType = isUnresolvedType(typeToResolve, arrayCount);
267             if(!isUnresolvedType) {
268                 sb.append(typeToResolve);
269             } else {
270                 // we need resolved types
271
String JavaDoc resolved = getResolvedTypeName(typeToResolve, declaringType);
272                 if(resolved != null) {
273                     while (arrayCount > 0) {
274                         sb.append(Signature.C_ARRAY);
275                         arrayCount--;
276                     }
277                     sb.append(Signature.C_RESOLVED);
278                     sb.append(resolved);
279                     sb.append(Signature.C_SEMICOLON);
280                 }
281             }
282         }
283         return sb.toString();
284     }
285
286     /**
287      * Copied and modified from JavaModelUtil. Resolves a type name in the context of the
288      * declaring type.
289      * @param refTypeSig the type name in signature notation (for example 'QVector') this
290      * can also be an array type, but dimensions will be ignored.
291      * @param declaringType the context for resolving (type where the reference was made
292      * in)
293      * @return returns the fully qualified <b>bytecode </b> type name or build-in-type
294      * name. if a unresoved type couldn't be resolved null is returned
295      */

296     private static String JavaDoc getResolvedTypeName(String JavaDoc refTypeSig,
297         IType declaringType) throws JavaModelException {
298
299         /* the whole method is copied from JavaModelUtil.getResolvedTypeName(...).
300          * The problem is, that JavaModelUtil uses '.' to separate package
301          * names, but we need '/' -> see JavaModelUtil.concatenateName() vs
302          * JdtUtils.concatenateName()
303          */

304         int arrayCount = Signature.getArrayCount(refTypeSig);
305         if (isUnresolvedType(refTypeSig, arrayCount)) {
306             String JavaDoc name= ""; //$NON-NLS-1$
307
int bracket= refTypeSig.indexOf(Signature.C_GENERIC_START, arrayCount + 1);
308             if (bracket > 0) {
309                 name= refTypeSig.substring(arrayCount + 1, bracket);
310             } else {
311                 int semi= refTypeSig.indexOf(Signature.C_SEMICOLON, arrayCount + 1);
312                 if (semi == -1) {
313                     throw new IllegalArgumentException JavaDoc();
314                 }
315                 name= refTypeSig.substring(arrayCount + 1, semi);
316             }
317             String JavaDoc[][] resolvedNames= declaringType.resolveType(name);
318             if (resolvedNames != null && resolvedNames.length > 0) {
319                 return concatenateName(resolvedNames[0][0], resolvedNames[0][1]);
320             }
321             return null;
322         }
323         return refTypeSig.substring(arrayCount);// Signature.toString(substring);
324
}
325
326     /**
327      * @param refTypeSig
328      * @param arrayCount expected array count in the signature
329      * @return true if the given string is an unresolved signature (Eclipse - internal
330      * representation)
331      */

332     private static boolean isUnresolvedType(String JavaDoc refTypeSig, int arrayCount){
333         char type = refTypeSig.charAt(arrayCount);
334         return type == Signature.C_UNRESOLVED;
335     }
336
337     /**
338      * Concatenates package and class name. Both strings can be empty or <code>null</code>.
339      */

340     private static String JavaDoc concatenateName(String JavaDoc packageName, String JavaDoc className) {
341         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
342         if (packageName != null && packageName.length() > 0) {
343             packageName = packageName.replace(Signature.C_DOT, PACKAGE_SEPARATOR);
344             buf.append(packageName);
345         }
346         if (className != null && className.length() > 0) {
347             if (buf.length() > 0) {
348                 buf.append(PACKAGE_SEPARATOR);
349             }
350             className = className.replace(Signature.C_DOT, TYPE_SEPARATOR);
351             buf.append(className);
352         }
353         return buf.toString();
354     }
355
356     /**
357      * Test which letter is following - Q or L are for reference types
358      * @param first
359      * @return true, if character is not a simbol for reference types
360      */

361     private static boolean isPrimitiveType(char first) {
362         return (first != Signature.C_RESOLVED && first != Signature.C_UNRESOLVED);
363     }
364
365     /**
366      * @param childEl may be null
367      * @return first ancestor with IJavaElement.TYPE element type, or null
368      */

369     public static IType getEnclosingType(IJavaElement childEl) {
370         if (childEl == null) {
371             return null;
372         }
373         return (IType) childEl.getAncestor(IJavaElement.TYPE);
374     }
375
376     /**
377      * @param cf
378      * @param dc
379      * @return inner type which has the same name as the given string, or null
380      */

381     public static IClassFile getInnerType(IClassFile cf, DecompiledClass dc,
382         String JavaDoc typeSignature) {
383         if(typeSignature.endsWith(";")){
384             typeSignature = typeSignature.substring(0, typeSignature.length()-1);
385             if(typeSignature.startsWith("L")){
386                 typeSignature = typeSignature.substring(1, typeSignature.length());
387             }
388         }
389         /*
390          * For inner and anonymous classes from the blocks or methods
391          * getFullyQualifiedName() does not work if class was compiled with 1.5
392          * and will never match the fullTypeName...
393          * I'm not sure if it is intended or if it is a bug
394          * in Eclipse: instead of A$1B we get A$B for B class from a method in A
395          *
396          * NB: for binary types without source attachment the method elements doesn't
397          * contain source and therefore could not resolve child elements.
398          * So the search for local types will never work...
399          *
400          * Therefore we do not use Eclipse API and use ClassNode->InnerClassNode elements
401          */

402         ClassNode cn = dc.getClassNode();
403         List JavaDoc/*<InnerClassNode>*/ innerClasses = cn.innerClasses;
404
405         for (int i = 0; i < innerClasses.size(); i++) {
406             InnerClassNode in = (InnerClassNode) innerClasses.get(i);
407             if(typeSignature.equals(in.name)){
408                 try {
409                     int idx = typeSignature.lastIndexOf(PACKAGE_SEPARATOR);
410                     String JavaDoc className = typeSignature;
411                     if (idx > 0) {
412                         className = typeSignature.substring(idx + 1, typeSignature.length());
413                     }
414                     className += ".class";
415                     return cf.getType().getPackageFragment().getClassFile(className);
416                 } catch (JavaModelException e) {
417                     BytecodeOutlinePlugin.log(e, IStatus.ERROR);
418                 }
419             }
420         }
421         return null;
422     }
423
424     /**
425      * Modified copy from org.eclipse.jdt.internal.ui.actions.SelectionConverter
426      * @param input
427      * @param selection
428      * @return null, if selection is null or could not be resolved to java element
429      * @throws JavaModelException
430      */

431     public static IJavaElement getElementAtOffset(IJavaElement input,
432         ITextSelection selection) throws JavaModelException {
433         if(selection == null){
434             return null;
435         }
436         ICompilationUnit workingCopy = null;
437         if (input instanceof ICompilationUnit) {
438             workingCopy = (ICompilationUnit) input;
439             // be in-sync with model
440
// instead of using internal JavaModelUtil.reconcile(workingCopy);
441
synchronized(workingCopy) {
442                 workingCopy.reconcile(
443                     ICompilationUnit.NO_AST,
444                     false /* don't force problem detection */,
445                     null /* use primary owner */,
446                     null /* no progress monitor */);
447             }
448             IJavaElement ref = workingCopy.getElementAt(selection.getOffset());
449             if (ref != null) {
450                 return ref;
451             }
452         } else if (input instanceof IClassFile) {
453             IClassFile iClass = (IClassFile) input;
454             IJavaElement ref = iClass.getElementAt(selection.getOffset());
455             if (ref != null) {
456                 // If we are in the inner class, try to refine search result now
457
if(ref instanceof IType){
458                     IType type = (IType) ref;
459                     IClassFile classFile = type.getClassFile();
460                     if(classFile != iClass){
461                         /*
462                          * WORKAROUND it seems that source range for constructors from
463                          * bytecode with source attached from zip files is not computed
464                          * in Eclipse (SourceMapper returns nothing useful).
465                          * Example: HashMap$Entry class with constructor
466                          * <init>(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V
467                          * We will get here at least the inner class...
468                          * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=137847
469                          */

470                         ref = classFile.getElementAt(selection.getOffset());
471                     }
472                 }
473                 return ref;
474             }
475         }
476         return null;
477     }
478
479     /**
480      * Modified copy from JavaModelUtil.
481      * @param javaElt
482      * @return true, if corresponding java project has compiler setting to generate
483      * bytecode for jdk 1.5 and above
484      */

485     public static boolean is50OrHigher(IJavaElement javaElt) {
486         IJavaProject project = javaElt.getJavaProject();
487         String JavaDoc option = project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
488         boolean result = JavaCore.VERSION_1_5.equals(option);
489         if(result){
490             return result;
491         }
492         // probably > 1.5?
493
result = JavaCore.VERSION_1_4.equals(option);
494         if(result){
495             return false;
496         }
497         result = JavaCore.VERSION_1_3.equals(option);
498         if(result){
499             return false;
500         }
501         result = JavaCore.VERSION_1_2.equals(option);
502         if(result){
503             return false;
504         }
505         result = JavaCore.VERSION_1_1.equals(option);
506         if(result){
507             return false;
508         }
509         // unknown = > 1.5
510
return true;
511     }
512
513     /**
514      * Cite: jdk1.1.8/docs/guide/innerclasses/spec/innerclasses.doc10.html: For the sake
515      * of tools, there are some additional requirements on the naming of an inaccessible
516      * class N. Its bytecode name must consist of the bytecode name of an enclosing class
517      * (the immediately enclosing class, if it is a member), followed either by `$' and a
518      * positive decimal numeral chosen by the compiler, or by `$' and the simple name of
519      * N, or else by both (in that order). Moreover, the bytecode name of a block-local N
520      * must consist of its enclosing package member T, the characters `$1$', and N, if the
521      * resulting name would be unique.
522      * <br>
523      * Note, that this rule was changed for static blocks after 1.5 jdk.
524      * @param javaElement
525      * @return simply element name
526      */

527     public static String JavaDoc getElementName(IJavaElement javaElement) {
528         if (isAnonymousType(javaElement)) {
529             IType anonType = (IType) javaElement;
530             List JavaDoc allAnonymous = new ArrayList JavaDoc();
531             /*
532              * in order to resolve anon. class name we need to know about all other
533              * anonymous classes in declaring class, therefore we need to collect all here
534              */

535             collectAllAnonymous(allAnonymous, anonType);
536             int idx = getAnonimousIndex(anonType, (IType[]) allAnonymous
537                 .toArray(new IType[allAnonymous.size()]));
538             return Integer.toString(idx);
539         }
540         String JavaDoc name = javaElement.getElementName();
541         if (isLocal(javaElement)) {
542             /*
543              * Compiler have different naming conventions for inner non-anon. classes in
544              * static blocks or any methods, this difference was introduced with 1.5 JDK.
545              * The problem is, that we could have projects with classes, generated
546              * with both 1.5 and earlier settings. One could not see on particular
547              * java element, for which jdk version the existing bytecode was generated.
548              * If we could have a *.class file, but we are just searching for one...
549              * So there could be still a chance, that this code fails, if java element
550              * is not compiled with comiler settings from project, but with different
551              */

552             if(is50OrHigher(javaElement)){
553                 name = "1" + name; // compiler output changed for > 1.5 code
554
} else {
555                 name = "1$" + name; // see method comment, this was the case for older code
556
}
557         }
558
559         if (name.endsWith(".java")) { //$NON-NLS-1$
560
name = name.substring(0, name.lastIndexOf(".java")); //$NON-NLS-1$
561
} else if (name.endsWith(".class")) { //$NON-NLS-1$
562
name = name.substring(0, name.lastIndexOf(".class")); //$NON-NLS-1$
563
}
564         return name;
565     }
566
567     /**
568      * @param javaElement
569      * @return null, if javaElement is top level class
570      */

571     static IType getFirstAncestor(IJavaElement javaElement) {
572         IJavaElement parent = javaElement;
573         if (javaElement.getElementType() == IJavaElement.TYPE) {
574             parent = javaElement.getParent();
575         }
576         if (parent != null) {
577             return (IType) parent.getAncestor(IJavaElement.TYPE);
578         }
579         return null;
580     }
581
582     static IJavaElement getLastAncestor(IJavaElement javaElement,
583         int elementType) {
584         IJavaElement lastFound = null;
585         if (elementType == javaElement.getElementType()) {
586             lastFound = javaElement;
587         }
588         IJavaElement parent = javaElement.getParent();
589         if (parent == null) {
590             return lastFound;
591         }
592         IJavaElement ancestor = parent.getAncestor(elementType);
593         if (ancestor != null) {
594             return getLastAncestor(ancestor, elementType);
595         }
596         return lastFound;
597     }
598
599     /**
600      * @param javaElement
601      * @return distance to given ancestor, 0 if it is the same, -1 if ancestor with type
602      * IJavaElement.TYPE does not exist
603      */

604     static int getTopAncestorDistance(IJavaElement javaElement,
605         IJavaElement topAncestor) {
606         if (topAncestor == javaElement) {
607             return 0;
608         }
609         IJavaElement ancestor = getFirstAncestor(javaElement);
610         if (ancestor != null) {
611             return 1 + getTopAncestorDistance(ancestor, topAncestor);
612         }
613         // this is not possible, if ancestor exist - which return value we should use?
614
return -1;
615     }
616
617     /**
618      * @param javaElement
619      * @return first non-anonymous ancestor
620      */

621     static IJavaElement getFirstNonAnonymous(IJavaElement javaElement,
622         IJavaElement topAncestor) {
623         if (javaElement.getElementType() == IJavaElement.TYPE
624             && !isAnonymousType(javaElement)) {
625             return javaElement;
626         }
627         IJavaElement parent = javaElement.getParent();
628         if (parent == null) {
629             return topAncestor;
630         }
631         IJavaElement ancestor = parent.getAncestor(IJavaElement.TYPE);
632         if (ancestor != null) {
633             return getFirstNonAnonymous(ancestor, topAncestor);
634         }
635         return topAncestor;
636     }
637
638     /**
639      * @param javaElement
640      * @return true, if given element is anonymous inner class
641      */

642     private static boolean isAnonymousType(IJavaElement javaElement) {
643         try {
644             return javaElement instanceof IType && ((IType)javaElement).isAnonymous();
645         } catch (JavaModelException e) {
646             BytecodeOutlinePlugin.log(e, IStatus.ERROR);
647         }
648         return false;
649     }
650
651     /**
652      * @param innerType should be inner type.
653      * @return true, if given element is inner class from initializer block or method body
654      */

655     private static boolean isLocal(IJavaElement innerType) {
656         try {
657             return innerType instanceof IType && ((IType)innerType).isLocal();
658         } catch (JavaModelException e) {
659             BytecodeOutlinePlugin.log(e, IStatus.ERROR);
660         }
661         return false;
662     }
663
664     /**
665      * @param type
666      * @return true, if given element is non static inner class
667      * @throws JavaModelException
668      */

669     private static boolean isNonStaticInner(IType type) throws JavaModelException {
670         if(type.isMember()){
671             return !Flags.isStatic(type.getFlags());
672         }
673         return false;
674     }
675
676     /**
677      * @param innerType should be inner type.
678      * @return true, if given element is inner class from initializer block
679      */

680     private static boolean isInnerFromBlock(IType type) {
681         try {
682             IJavaElement ancestor = type.getAncestor(IJavaElement.INITIALIZER);
683             return type.isLocal() && ancestor != null;
684         } catch (JavaModelException e) {
685             BytecodeOutlinePlugin.log(e, IStatus.ERROR);
686         }
687         return false;
688     }
689
690     /**
691      * @param javaElement
692      * @return absolute path of generated bytecode package for given element
693      * @throws JavaModelException
694      */

695     private static String JavaDoc getPackageOutputPath(IJavaElement javaElement)
696         throws JavaModelException {
697         String JavaDoc dir = ""; //$NON-NLS-1$
698
if (javaElement == null) {
699             return dir;
700         }
701
702         IJavaProject project = javaElement.getJavaProject();
703
704         if (project == null) {
705             return dir;
706         }
707         // default bytecode location
708
IPath path = project.getOutputLocation();
709
710         IResource resource = javaElement.getUnderlyingResource();
711         if (resource == null) {
712             return dir;
713         }
714         // resolve multiple output locations here
715
if (project.exists() && project.getProject().isOpen()) {
716             IClasspathEntry entries[] = project.getRawClasspath();
717             for (int i = 0; i < entries.length; i++) {
718                 IClasspathEntry classpathEntry = entries[i];
719                 if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
720                     IPath outputPath = classpathEntry.getOutputLocation();
721                     if (outputPath != null
722                         && classpathEntry.getPath().isPrefixOf(
723                             resource.getFullPath())) {
724                         path = outputPath;
725                         break;
726                     }
727                 }
728             }
729         }
730
731         if (path == null) {
732             // check the default location if not already included
733
IPath def = project.getOutputLocation();
734             if (def != null && def.isPrefixOf(resource.getFullPath())){
735                 path = def;
736             }
737         }
738
739         if(path == null){
740             return dir;
741         }
742
743         IWorkspace workspace = ResourcesPlugin.getWorkspace();
744
745         if (!project.getPath().equals(path)) {
746             IFolder outputFolder = workspace.getRoot().getFolder(path);
747             if (outputFolder != null) {
748                 // linked resources will be resolved here!
749
IPath rawPath = outputFolder.getRawLocation();
750                 if (rawPath != null) {
751                     path = rawPath;
752                 }
753             }
754         } else {
755             path = project.getProject().getLocation();
756         }
757
758         // here we should resolve path variables,
759
// probably existing at first place of path
760
IPathVariableManager pathManager = workspace.getPathVariableManager();
761         path = pathManager.resolvePath(path);
762
763         if (path == null) {
764             return dir;
765         }
766
767         if (isPackageRoot(project, resource)) {
768             dir = path.toOSString();
769         } else {
770             String JavaDoc packPath = EclipseUtils.getJavaPackageName(javaElement)
771                 .replace(Signature.C_DOT, PACKAGE_SEPARATOR);
772             dir = path.append(packPath).toOSString();
773         }
774         return dir;
775     }
776
777     /**
778      * @param project
779      * @param pack
780      * @return true if 'pack' argument is package root
781      * @throws JavaModelException
782      */

783     private static boolean isPackageRoot(IJavaProject project, IResource pack)
784         throws JavaModelException {
785         boolean isRoot = false;
786         if (project == null || pack == null) {
787             return isRoot;
788         }
789         IPackageFragmentRoot root = project.getPackageFragmentRoot(pack);
790         IClasspathEntry clPathEntry = null;
791         if (root != null) {
792             clPathEntry = root.getRawClasspathEntry();
793         }
794         isRoot = clPathEntry != null;
795         return isRoot;
796     }
797
798     /**
799      * Works only for eclipse - managed/generated bytecode, ergo not with imported
800      * classes/jars
801      * @param javaElement
802      * @return full os-specific file path to .class resource, containing given element
803      */

804     public static String JavaDoc getByteCodePath(IJavaElement javaElement) {
805         if (javaElement == null) {
806             return "";//$NON-NLS-1$
807
}
808         String JavaDoc packagePath = ""; //$NON-NLS-1$
809
try {
810             packagePath = getPackageOutputPath(javaElement);
811         } catch (JavaModelException e) {
812             BytecodeOutlinePlugin.log(e, IStatus.ERROR);
813             return "";
814         }
815         IJavaElement ancestor = getLastAncestor(javaElement, IJavaElement.TYPE);
816         StringBuffer JavaDoc sb = new StringBuffer JavaDoc(packagePath);
817         sb.append(File.separator);
818         sb.append(getClassName(javaElement, ancestor));
819         sb.append(".class"); //$NON-NLS-1$
820
return sb.toString();
821     }
822
823     /**
824      * @param javaElement
825      * @return new generated input stream for given element bytecode class file, or null
826      * if class file cannot be found or this element is not from java source path
827      */

828     public static InputStream JavaDoc createInputStream(IJavaElement javaElement) {
829         IClassFile classFile = (IClassFile) javaElement
830             .getAncestor(IJavaElement.CLASS_FILE);
831         InputStream JavaDoc is = null;
832
833         // existing read-only class files
834
if (classFile != null) {
835             IJavaElement jarParent = classFile.getParent();
836             // TODO dirty hack to be sure, that package is from jar -
837
// because JarPackageFragment is not public class, we cannot
838
// use instanceof here
839
boolean isJar = jarParent != null
840                 && jarParent.getClass().getName()
841                     .endsWith("JarPackageFragment"); //$NON-NLS-1$
842
if (isJar) {
843                 is = createStreamFromJar(classFile);
844             } else {
845                 is = createStreamFromClass(classFile);
846             }
847         } else {
848             // usual eclipse - generated bytecode
849

850             boolean inJavaPath = isOnClasspath(javaElement);
851             if (!inJavaPath) {
852                 return null;
853             }
854             String JavaDoc classPath = getByteCodePath(javaElement);
855
856             try {
857                 is = new FileInputStream JavaDoc(classPath);
858             } catch (FileNotFoundException JavaDoc e) {
859                 // if autobuild is disabled, we get tons of this errors.
860
// but I think we cannot ignore them, therefore WARNING and not
861
// ERROR status
862
BytecodeOutlinePlugin.log(e, IStatus.WARNING);
863             }
864         }
865         return is;
866     }
867
868     /**
869      * Creates stream from external class file from Eclipse classpath (means, that this
870      * class file is read-only)
871      * @param classFile
872      * @return new generated input stream from external class file, or null, if class file
873      * for this element cannot be found
874      */

875     private static InputStream JavaDoc createStreamFromClass(IClassFile classFile) {
876         IResource underlyingResource = null;
877         try {
878             // to tell the truth, I don't know why that different methods
879
// are not working in a particular case. But it seems to be better
880
// to use getResource() with non-java elements (not in model)
881
// and getUnderlyingResource() with java elements.
882
if (classFile.exists()) {
883                 underlyingResource = classFile.getUnderlyingResource();
884             } else {
885                 // this is a class file that is not in java model
886
underlyingResource = classFile.getResource();
887             }
888         } catch (JavaModelException e) {
889             BytecodeOutlinePlugin.log(e, IStatus.ERROR);
890             return null;
891         }
892         IPath rawLocation = underlyingResource.getRawLocation();
893         // here we should resolve path variables,
894
// probably existing at first place of "rawLocation" path
895
IWorkspace workspace = ResourcesPlugin.getWorkspace();
896         IPathVariableManager pathManager = workspace.getPathVariableManager();
897         rawLocation = pathManager.resolvePath(rawLocation);
898         try {
899             return new FileInputStream JavaDoc(rawLocation.toOSString());
900         } catch (FileNotFoundException JavaDoc e) {
901             BytecodeOutlinePlugin.log(e, IStatus.ERROR);
902         }
903         return null;
904     }
905
906     /**
907      * Creates stream from external class file that is stored in jar file
908      * @param classFile
909      * @param javaElement
910      * @return new generated input stream from external class file that is stored in jar
911      * file, or null, if class file for this element cannot be found
912      */

913     private static InputStream JavaDoc createStreamFromJar(IClassFile classFile) {
914         IPath path = null;
915         IResource resource = classFile.getResource();
916         // resource == null => this is a external archive
917
if (resource != null) {
918             path = resource.getRawLocation();
919         } else {
920             path = classFile.getPath();
921         }
922         if (path == null) {
923             return null;
924         }
925         // here we should resolve path variables,
926
// probably existing at first place of path
927
IWorkspace workspace = ResourcesPlugin.getWorkspace();
928         IPathVariableManager pathManager = workspace.getPathVariableManager();
929         path = pathManager.resolvePath(path);
930
931         JarFile JavaDoc jar = null;
932         try {
933             jar = new JarFile JavaDoc(path.toOSString());
934         } catch (IOException JavaDoc e) {
935             BytecodeOutlinePlugin.log(e, IStatus.ERROR);
936             return null;
937         }
938         String JavaDoc fullClassName = getFullBytecodeName(classFile);
939         if (fullClassName == null) {
940             return null;
941         }
942         JarEntry JavaDoc jarEntry = jar.getJarEntry(fullClassName);
943         if (jarEntry != null) {
944             try {
945                 return jar.getInputStream(jarEntry);
946             } catch (IOException JavaDoc e) {
947                 BytecodeOutlinePlugin.log(e, IStatus.ERROR);
948             }
949         }
950         return null;
951     }
952
953     private static boolean isOnClasspath(IJavaElement javaElement) {
954         IJavaProject project = javaElement.getJavaProject();
955         if (project != null) {
956             boolean result = project.isOnClasspath(javaElement);
957             return result;
958         }
959         return false;
960     }
961
962     /**
963      * @param classFile
964      * @return full qualified bytecode name of given class
965      */

966     public static String JavaDoc getFullBytecodeName(IClassFile classFile) {
967         IPackageFragment packageFr = (IPackageFragment) classFile
968             .getAncestor(IJavaElement.PACKAGE_FRAGMENT);
969         if (packageFr == null) {
970             return null;
971         }
972         String JavaDoc packageName = packageFr.getElementName();
973         // switch to java bytecode naming conventions
974
packageName = packageName.replace(Signature.C_DOT, PACKAGE_SEPARATOR);
975
976         String JavaDoc className = classFile.getElementName();
977         if (packageName != null && packageName.length() > 0) {
978             return packageName + PACKAGE_SEPARATOR + className;
979         }
980         return className;
981     }
982
983     /**
984      * @param javaElement
985      * @param topAncestor
986      * @param sb
987      */

988     private static String JavaDoc getClassName(IJavaElement javaElement,
989         IJavaElement topAncestor) {
990         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
991         if (!javaElement.equals(topAncestor)) {
992             int elementType = javaElement.getElementType();
993             if(elementType == IJavaElement.FIELD
994                 || elementType == IJavaElement.METHOD
995                  || elementType == IJavaElement.INITIALIZER) {
996                 // it's field or method
997
javaElement = getFirstAncestor(javaElement);
998             } else {
999                 boolean is50OrHigher = is50OrHigher(javaElement);
1000                if (!is50OrHigher &&
1001                    (isAnonymousType(javaElement) || isLocal(javaElement))) {
1002                    // it's inner type
1003
sb.append(getElementName(topAncestor));
1004                    sb.append(TYPE_SEPARATOR);
1005                } else {
1006                    /*
1007                     * TODO there is an issue with < 1.5 compiler setting and with inner
1008                     * classes with the same name but defined in different methods in the same
1009                     * source file. Then compiler needs to generate *different* content for
1010                     * A$1$B and A$1$B, which is not possible so therefore compiler generates
1011                     * A$1$B and A$2$B. The naming order is the source range order of inner
1012                     * classes, so the first inner B class will get A$1$B and the second
1013                     * inner B class A$2$B etc.
1014                     */

1015
1016                    // override top ancestor with immediate ancestor
1017
topAncestor = getFirstAncestor(javaElement);
1018                    while (topAncestor != null) {
1019                        sb.insert(0, getElementName(topAncestor) + TYPE_SEPARATOR);
1020                        topAncestor = getFirstAncestor(topAncestor);
1021                    }
1022                }
1023            }
1024        }
1025        sb.append(getElementName(javaElement));
1026        return sb.toString();
1027    }
1028
1029    /**
1030     * Collect all anonymous classes which are on the same "name shema level"
1031     * as the given element for the compiler. The list could contain different set of
1032     * elements for the same source code, depends on the compiler and jdk version
1033     * @param list for the found anon. classes, elements instanceof IType.
1034     * @param anonType the anon. type
1035     */

1036    private static void collectAllAnonymous(List JavaDoc list, IType anonType) {
1037        /*
1038         * For JDK >= 1.5 in Eclipse 3.1+ the naming shema for nested anonymous
1039         * classes was changed from A$1, A$2, A$3, A$4, ..., A$n
1040         * to A$1, A$1$1, A$1$2, A$1$2$1, ..., A$2, A$2$1, A$2$2, ..., A$x$y
1041         */

1042        boolean allowNested = ! is50OrHigher(anonType);
1043
1044        IParent declaringType;
1045        if(allowNested) {
1046            declaringType = (IType) getLastAncestor(anonType, IJavaElement.TYPE);
1047        } else {
1048            declaringType = anonType.getDeclaringType();
1049        }
1050
1051        try {
1052            collectAllAnonymous(list, declaringType, allowNested);
1053        } catch (JavaModelException e) {
1054            BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1055        }
1056    }
1057
1058    /**
1059     * Traverses down the children tree of this parent and collect all child anon. classes
1060     * @param list
1061     * @param parent
1062     * @param allowNested true to search in IType child elements too
1063     * @throws JavaModelException
1064     */

1065    private static void collectAllAnonymous(List JavaDoc list, IParent parent,
1066        boolean allowNested) throws JavaModelException {
1067        IJavaElement[] children = parent.getChildren();
1068        for (int i = 0; i < children.length; i++) {
1069            IJavaElement childElem = children[i];
1070            if (isAnonymousType(childElem)) {
1071                list.add(childElem);
1072            }
1073            if (childElem instanceof IParent) {
1074                if(allowNested || !(childElem instanceof IType)) {
1075                    collectAllAnonymous(list, (IParent) childElem, allowNested);
1076                }
1077            }
1078        }
1079    }
1080
1081    /**
1082     * @param anonType
1083     * @param anonymous
1084     * @return the index of given java element in the anon. classes list, which was used
1085     * by compiler to generate bytecode name for given element. If the given type is not
1086     * in the list, then return value is '-1'
1087     */

1088    private static int getAnonimousIndex(IType anonType, IType[] anonymous) {
1089        sortAnonymous(anonymous, anonType);
1090        for (int i = 0; i < anonymous.length; i++) {
1091            if (anonymous[i] == anonType) {
1092                // +1 because compiler starts generated classes always with 1
1093
return i + 1;
1094            }
1095        }
1096        return -1;
1097    }
1098
1099    /**
1100     * Sort given anonymous classes in order like java compiler would generate output
1101     * classes, in context of given anonymous type
1102     * @param anonymous
1103     */

1104    private static void sortAnonymous(IType[] anonymous, IType anonType) {
1105        SourceOffsetComparator sourceComparator = new SourceOffsetComparator();
1106        Arrays.sort(anonymous, new AnonymClassComparator(
1107            anonType, sourceComparator));
1108    }
1109
1110    /**
1111     * 1) from instance init 2) from deepest inner from instance init (deepest first) 3) from
1112     * static init 4) from deepest inner from static init (deepest first) 5) from deepest inner
1113     * (deepest first) 6) regular anon classes from main class
1114     *
1115     * <br>
1116     * Note, that nested inner anon. classes which do not have different non-anon. inner class
1117     * ancestors, are compiled in they nesting order, opposite to rule 2)
1118     *
1119     * @param javaElement
1120     * @return priority - lesser mean wil be compiled later, a value > 0
1121     * @throws JavaModelException
1122     */

1123    static int getAnonCompilePriority(IJavaElement javaElement,
1124        IJavaElement firstAncestor, IJavaElement topAncestor, boolean is50OrHigher) {
1125
1126        // search for initializer block
1127
IJavaElement lastAncestor = getLastAncestor(
1128            javaElement, IJavaElement.INITIALIZER);
1129        // test is for anon. classes from initializer blocks
1130
if (lastAncestor != null) {
1131            IInitializer init = (IInitializer) lastAncestor;
1132            int initFlags = 0;
1133            try {
1134                initFlags = init.getFlags();
1135            } catch (JavaModelException e) {
1136                BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1137            }
1138
1139            if(is50OrHigher){
1140                if (!Flags.isStatic(initFlags)) {
1141                    if (firstAncestor == topAncestor) {
1142                        return 10; // instance init
1143
}
1144                    return 9; // from inner from instance init
1145
}
1146
1147                if (firstAncestor == topAncestor) {
1148                    return 8; // class init
1149
}
1150                return 7; // from inner from class init
1151
}
1152
1153            /*
1154             * crazy 1.4 logic
1155             */

1156            if (Flags.isStatic(initFlags)) {
1157                return 10;
1158            }
1159            IJavaElement firstNonAnon = getFirstNonAnonymous(javaElement, topAncestor);
1160            if (isStatic((IMember)firstNonAnon)) {
1161                return 9;
1162            }
1163
1164            return 6; // class init
1165
}
1166        // from inner from main type
1167
if(!is50OrHigher){
1168            /*
1169             * crazy 1.4 logic
1170             */

1171            int topFlags = 0;
1172            IJavaElement firstNonAnon = getFirstNonAnonymous(javaElement, topAncestor);
1173            try {
1174                topFlags = ((IType)firstNonAnon).getFlags();
1175            } catch (JavaModelException e) {
1176                BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1177            }
1178            if (Flags.isStatic(topFlags)) {
1179                return 8;
1180            }
1181
1182            if (firstAncestor == topAncestor) {
1183                return 6; // regular anonyme classes
1184
}
1185            return 6; // the same prio as regular anonyme classes
1186
}
1187
1188        // test for anon. classes from "regular" code
1189
if (firstAncestor == topAncestor) {
1190            return 5; // regular anonyme classes
1191
}
1192        return 6;
1193    }
1194
1195    private static boolean isStatic(IMember firstNonAnon) {
1196        int topFlags = 0;
1197        try {
1198            topFlags = firstNonAnon.getFlags();
1199        } catch (JavaModelException e) {
1200            BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1201        }
1202        return Flags.isStatic(topFlags);
1203    }
1204
1205    /**
1206     * @param type
1207     * @return
1208     */

1209    public static ClassLoader JavaDoc getClassLoader(IJavaElement type) {
1210        ClassLoader JavaDoc cl;
1211
1212        IJavaProject javaProject = type.getJavaProject();
1213        List JavaDoc urls = new ArrayList JavaDoc();
1214
1215        getClassURLs(javaProject, urls);
1216
1217        if (urls.isEmpty()) {
1218            cl = JdtUtils.class.getClassLoader();
1219        } else {
1220            cl = new URLClassLoader JavaDoc((URL JavaDoc[]) urls.toArray(new URL JavaDoc[urls.size()]));
1221        }
1222        return cl;
1223    }
1224
1225    private static void getClassURLs(IJavaProject javaProject, List JavaDoc urls) {
1226        IProject project = javaProject.getProject();
1227        IWorkspaceRoot workspaceRoot = project.getWorkspace().getRoot();
1228
1229        IClasspathEntry[] paths = null;
1230        IPath defaultOutputLocation = null;
1231        try {
1232            paths = javaProject.getResolvedClasspath(true);
1233            defaultOutputLocation = javaProject.getOutputLocation();
1234        } catch (JavaModelException e) {
1235            // don't show message to user neither log it
1236
// BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1237
}
1238        if (paths != null) {
1239            IPath projectPath = javaProject.getProject().getLocation();
1240            for (int i = 0; i < paths.length; ++i) {
1241                IClasspathEntry cpEntry = paths[i];
1242                IPath p = null;
1243                if (cpEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
1244                    // filter out source container - there are unused for class
1245
// search - add bytecode output location instead
1246
p = cpEntry.getOutputLocation();
1247                    if (p == null) {
1248                        // default output used:
1249
p = defaultOutputLocation;
1250                    }
1251                } else if (cpEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
1252                    String JavaDoc projName = cpEntry.getPath().toPortableString()
1253                        .substring(1);
1254                    IProject proj = workspaceRoot.getProject(projName);
1255                    IJavaProject projj = JavaCore.create(proj);
1256                    getClassURLs(projj, urls);
1257                    continue;
1258                } else {
1259                    p = cpEntry.getPath();
1260                }
1261
1262                if (p == null) {
1263                    continue;
1264                }
1265                if (!p.toFile().exists()) {
1266                    // removeFirstSegments: remove project from relative path
1267
p = projectPath.append(p.removeFirstSegments(1));
1268                    if (!p.toFile().exists()) {
1269                        continue;
1270                    }
1271                }
1272                try {
1273                    urls.add(p.toFile().toURL());
1274                } catch (MalformedURLException JavaDoc e) {
1275                    // don't show message to user
1276
BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1277                }
1278            }
1279        }
1280    }
1281
1282    /**
1283     * Check if java element is an interface or abstract method or a method from
1284     * interface.
1285     */

1286    public static boolean isAbstractOrInterface(IJavaElement javaEl) {
1287        if (javaEl == null) {
1288            return true;
1289        }
1290        boolean abstractOrInterface = false;
1291        try {
1292            switch (javaEl.getElementType()) {
1293                case IJavaElement.CLASS_FILE :
1294                    IClassFile classFile = (IClassFile) javaEl;
1295                    if(isOnClasspath(javaEl)) {
1296                        abstractOrInterface = classFile.isInterface();
1297                    } /*else {
1298                       this is the case for eclipse-generated class files.
1299                       if we do not perform the check in if, then we will have java model
1300                       exception on classFile.isInterface() call.
1301                    }*/

1302                    break;
1303                case IJavaElement.COMPILATION_UNIT :
1304                    ICompilationUnit cUnit = (ICompilationUnit) javaEl;
1305                    IType type = cUnit.findPrimaryType();
1306                    abstractOrInterface = type != null && type.isInterface();
1307                    break;
1308                case IJavaElement.TYPE :
1309                    abstractOrInterface = ((IType) javaEl).isInterface();
1310                    break;
1311                case IJavaElement.METHOD :
1312                    // test for "abstract" flag on method in a class
1313
abstractOrInterface = Flags.isAbstract(((IMethod) javaEl)
1314                        .getFlags());
1315                    // "abstract" flags could be not exist on interface methods
1316
if (!abstractOrInterface) {
1317                        IType ancestor = (IType) javaEl
1318                            .getAncestor(IJavaElement.TYPE);
1319                        abstractOrInterface = ancestor != null
1320                            && ancestor.isInterface();
1321                    }
1322                    break;
1323                default :
1324                    IType ancestor1 = (IType) javaEl
1325                        .getAncestor(IJavaElement.TYPE);
1326                    abstractOrInterface = ancestor1 != null
1327                        && ancestor1.isInterface();
1328                    break;
1329            }
1330        } catch (JavaModelException e) {
1331            // No point to log it here
1332
// BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1333
}
1334        return abstractOrInterface;
1335    }
1336
1337    static class SourceOffsetComparator implements Comparator JavaDoc {
1338
1339        /**
1340         * First source occurence win.
1341         * @param o1 should be IType
1342         * @param o2 should be IType
1343         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
1344         */

1345        public int compare(Object JavaDoc o1, Object JavaDoc o2) {
1346            IType m1 = (IType) o1;
1347            IType m2 = (IType) o2;
1348            int idx1, idx2;
1349            try {
1350                ISourceRange sr1 = m1.getSourceRange();
1351                ISourceRange sr2 = m2.getSourceRange();
1352                if (sr1 == null || sr2 == null) {
1353                    return 0;
1354                }
1355                idx1 = sr1.getOffset();
1356                idx2 = sr2.getOffset();
1357            } catch (JavaModelException e) {
1358                BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1359                return 0;
1360            }
1361            if (idx1 < idx2) {
1362                return -1;
1363            } else if (idx1 > idx2) {
1364                return 1;
1365            }
1366            return 0;
1367        }
1368    }
1369
1370    static class AnonymClassComparator implements Comparator JavaDoc {
1371
1372        private IType topAncestorType;
1373        private SourceOffsetComparator sourceComparator;
1374        private boolean is50OrHigher;
1375
1376        /**
1377         * @param javaElement
1378         * @param sourceComparator
1379         */

1380        public AnonymClassComparator(IType javaElement,
1381            SourceOffsetComparator sourceComparator) {
1382            this.sourceComparator = sourceComparator;
1383            is50OrHigher = is50OrHigher(javaElement);
1384            topAncestorType = (IType) getLastAncestor(
1385                javaElement, IJavaElement.TYPE);
1386        }
1387
1388        /**
1389         * If "deep" is the same, then source order win. 1) from instance init 2) from
1390         * deepest inner from instance init (deepest first) 3) from static init 4) from
1391         * deepest inner from static init (deepest first) 5) from deepest inner (deepest
1392         * first) 7) regular anon classes from main class
1393         *
1394         * <br>
1395         * Note, that nested inner anon. classes which do not have different
1396         * non-anon. inner class ancestors, are compiled in they nesting order, opposite
1397         * to rule 2)
1398         *
1399         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
1400         */

1401        public int compare(Object JavaDoc o1, Object JavaDoc o2) {
1402            if(o1 == o2){
1403                return 0;
1404            }
1405            IType m1 = (IType) o1;
1406            IType m2 = (IType) o2;
1407            // both have the same ancestor as immediate ancestor
1408
if (!is50OrHigher && isInnerFromBlock(m1) && isInnerFromBlock(m2)) {
1409                return sourceComparator.compare(o1, o2);
1410            }
1411            IJavaElement firstAncestor1 = getFirstAncestor(m1);
1412            IJavaElement firstAncestor2 = getFirstAncestor(m2);
1413            int compilePrio1 = getAnonCompilePriority(
1414                m1, firstAncestor1, topAncestorType, is50OrHigher);
1415            int compilePrio2 = getAnonCompilePriority(
1416                m2, firstAncestor2, topAncestorType, is50OrHigher);
1417
1418// System.out.println("" + compilePrio1 + " -> " + o1.toString().substring(0, 50));
1419
// System.out.println("" + compilePrio2 + " -> " + o2.toString().substring(0, 50));
1420

1421            if (compilePrio1 > compilePrio2) {
1422                return -1;
1423            } else if (compilePrio1 < compilePrio2) {
1424                return 1;
1425            } else {
1426                firstAncestor1 = getFirstNonAnonymous(m1, topAncestorType);
1427                firstAncestor2 = getFirstNonAnonymous(m2, topAncestorType);
1428
1429                if(firstAncestor1 == firstAncestor2){
1430                    /*
1431                     * for anonymous classes from same chain and same first common ancestor,
1432                     * the order is the definition order
1433                     */

1434                    int topAncestorDistance1 = getTopAncestorDistance(
1435                        m1, topAncestorType);
1436                    int topAncestorDistance2 = getTopAncestorDistance(
1437                        m2, topAncestorType);
1438                    if (topAncestorDistance1 < topAncestorDistance2) {
1439                        return -1;
1440                    } else if (topAncestorDistance1 > topAncestorDistance2) {
1441                        return 1;
1442                    } else {
1443                        return sourceComparator.compare(o1, o2);
1444                    }
1445                }
1446
1447// if(!is50OrHigher && !isStatic(m1) && !isStatic(m2)){
1448
// return sourceComparator.compare(o1, o2);
1449
// }
1450

1451                /*
1452                 * for anonymous classes which have first non-common non-anonymous ancestor,
1453                 * the order is the reversed definition order
1454                 */

1455                int topAncestorDistance1 = getTopAncestorDistance(
1456                    firstAncestor1, topAncestorType);
1457                int topAncestorDistance2 = getTopAncestorDistance(
1458                    firstAncestor2, topAncestorType);
1459                if (topAncestorDistance1 > topAncestorDistance2) {
1460                    return -1;
1461                } else if (topAncestorDistance1 < topAncestorDistance2) {
1462                    return 1;
1463                } else {
1464                    return sourceComparator.compare(o1, o2);
1465                }
1466            }
1467        }
1468    }
1469}
Popular Tags