0001: /*****************************************************************************************
0002: * Copyright (c) 2004 Andrei Loskutov. All rights reserved. This program and the
0003: * accompanying materials are made available under the terms of the BSD License which
0004: * accompanies this distribution, and is available at
0005: * http://www.opensource.org/licenses/bsd-license.php Contributor: Andrei Loskutov -
0006: * initial API and implementation
0007: ****************************************************************************************/package de.loskutov.bco.ui;
0008:
0009: import java.io.File;
0010: import java.io.FileInputStream;
0011: import java.io.FileNotFoundException;
0012: import java.io.IOException;
0013: import java.io.InputStream;
0014: import java.net.MalformedURLException;
0015: import java.net.URL;
0016: import java.net.URLClassLoader;
0017: import java.util.ArrayList;
0018: import java.util.Arrays;
0019: import java.util.Comparator;
0020: import java.util.List;
0021: import java.util.jar.JarEntry;
0022: import java.util.jar.JarFile;
0023:
0024: import org.eclipse.core.resources.IFolder;
0025: import org.eclipse.core.resources.IPathVariableManager;
0026: import org.eclipse.core.resources.IProject;
0027: import org.eclipse.core.resources.IResource;
0028: import org.eclipse.core.resources.IWorkspace;
0029: import org.eclipse.core.resources.IWorkspaceRoot;
0030: import org.eclipse.core.resources.ResourcesPlugin;
0031: import org.eclipse.core.runtime.IPath;
0032: import org.eclipse.core.runtime.IStatus;
0033: import org.eclipse.jdt.core.Flags;
0034: import org.eclipse.jdt.core.IClassFile;
0035: import org.eclipse.jdt.core.IClasspathEntry;
0036: import org.eclipse.jdt.core.ICompilationUnit;
0037: import org.eclipse.jdt.core.IInitializer;
0038: import org.eclipse.jdt.core.IJavaElement;
0039: import org.eclipse.jdt.core.IJavaProject;
0040: import org.eclipse.jdt.core.IMember;
0041: import org.eclipse.jdt.core.IMethod;
0042: import org.eclipse.jdt.core.IPackageFragment;
0043: import org.eclipse.jdt.core.IPackageFragmentRoot;
0044: import org.eclipse.jdt.core.IParent;
0045: import org.eclipse.jdt.core.ISourceRange;
0046: import org.eclipse.jdt.core.IType;
0047: import org.eclipse.jdt.core.ITypeParameter;
0048: import org.eclipse.jdt.core.JavaCore;
0049: import org.eclipse.jdt.core.JavaModelException;
0050: import org.eclipse.jdt.core.Signature;
0051: import org.eclipse.jface.text.ITextSelection;
0052: import org.objectweb.asm.tree.ClassNode;
0053: import org.objectweb.asm.tree.InnerClassNode;
0054:
0055: import de.loskutov.bco.BytecodeOutlinePlugin;
0056: import de.loskutov.bco.asm.DecompiledClass;
0057:
0058: /**
0059: * @author Andrei
0060: */
0061: public class JdtUtils {
0062: /** package separator in bytecode notation */
0063: private static final char PACKAGE_SEPARATOR = '/';
0064: /** type name separator (for inner types) in bytecode notation */
0065: private static final char TYPE_SEPARATOR = '$';
0066:
0067: /**
0068: *
0069: */
0070: private JdtUtils() {
0071: // don't call
0072: }
0073:
0074: public static IJavaElement getMethod(IParent parent,
0075: String signature) {
0076: try {
0077: IJavaElement[] children = parent.getChildren();
0078: for (int i = 0; i < children.length; i++) {
0079: IJavaElement javaElement = children[i];
0080: switch (javaElement.getElementType()) {
0081: case IJavaElement.INITIALIZER:
0082: // fall through
0083: case IJavaElement.METHOD:
0084: if (signature
0085: .equals(getMethodSignature(javaElement))) {
0086: return javaElement;
0087: }
0088: break;
0089: default:
0090: break;
0091: }
0092: if (javaElement instanceof IParent) {
0093: javaElement = getMethod((IParent) javaElement,
0094: signature);
0095: if (javaElement != null) {
0096: return javaElement;
0097: }
0098: }
0099: }
0100: } catch (JavaModelException e) {
0101: // just ignore it. Mostly caused by class files not on the class path
0102: // which is not a problem for us, but a big problem for JDT
0103: }
0104: return null;
0105: }
0106:
0107: /**
0108: * @param childEl
0109: * @return method signature, if given java element is either initializer or method,
0110: * otherwise returns null.
0111: */
0112: public static String getMethodSignature(IJavaElement childEl) {
0113: String methodName = null;
0114: if (childEl.getElementType() == IJavaElement.INITIALIZER) {
0115: IInitializer ini = (IInitializer) childEl;
0116: try {
0117: if (Flags.isStatic(ini.getFlags())) {
0118: methodName = "<clinit>()V";
0119: } else {
0120: methodName = "<init>()";
0121: }
0122: } catch (JavaModelException e) {
0123: // this is compilation problem - don't show the message
0124: BytecodeOutlinePlugin.log(e, IStatus.WARNING);
0125: }
0126: } else if (childEl.getElementType() == IJavaElement.METHOD) {
0127: IMethod iMethod = (IMethod) childEl;
0128: try {
0129: methodName = createMethodSignature(iMethod);
0130: } catch (JavaModelException e) {
0131: // this is compilation problem - don't show the message
0132: BytecodeOutlinePlugin.log(e, IStatus.WARNING);
0133: }
0134: }
0135: return methodName;
0136: }
0137:
0138: public static String createMethodSignature(IMethod iMethod)
0139: throws JavaModelException {
0140: StringBuffer sb = new StringBuffer();
0141:
0142: // Eclipse put class name as constructor name - we change it!
0143: if (iMethod.isConstructor()) {
0144: sb.append("<init>"); //$NON-NLS-1$
0145: } else {
0146: sb.append(iMethod.getElementName());
0147: }
0148:
0149: if (iMethod.isBinary()) { // iMethod instanceof BinaryMember
0150: // binary info should be full qualified
0151: return sb.append(iMethod.getSignature()).toString();
0152: }
0153:
0154: // start method parameter descriptions list
0155: sb.append('(');
0156: IType declaringType = iMethod.getDeclaringType();
0157: String[] parameterTypes = iMethod.getParameterTypes();
0158:
0159: /*
0160: * For non - static inner classes bytecode constructor should contain as first
0161: * parameter the enclosing type instance, but in Eclipse AST there are no
0162: * appropriated parameter. So we need to create enclosing type signature and
0163: * add it as first parameter.
0164: */
0165: if (iMethod.isConstructor() && isNonStaticInner(declaringType)) {
0166: // this is a very special case
0167: String typeSignature = getTypeSignature(getFirstAncestor(declaringType));
0168: if (typeSignature != null) {
0169: String[] newParams = new String[parameterTypes.length + 1];
0170: newParams[0] = typeSignature;
0171: System.arraycopy(parameterTypes, 0, newParams, 1,
0172: parameterTypes.length);
0173: parameterTypes = newParams;
0174: }
0175: }
0176:
0177: // doSomething(Lgenerics/DummyForAsmGenerics;)Lgenerics/DummyForAsmGenerics;
0178: for (int i = 0; i < parameterTypes.length; i++) {
0179: String resolvedType = getResolvedType(parameterTypes[i],
0180: declaringType);
0181: if (resolvedType != null && resolvedType.length() > 0) {
0182: sb.append(resolvedType);
0183: } else {
0184: // this is a generic type
0185: appendGenericType(sb, iMethod, parameterTypes[i]);
0186: }
0187: }
0188: sb.append(')');
0189:
0190: // continue here with adding resolved return type
0191: String returnType = iMethod.getReturnType();
0192: String resolvedType = getResolvedType(returnType, declaringType);
0193: if (resolvedType != null && resolvedType.length() > 0) {
0194: sb.append(resolvedType);
0195: } else {
0196: // this is a generic type
0197: appendGenericType(sb, iMethod, returnType);
0198: }
0199:
0200: return sb.toString();
0201: }
0202:
0203: /**
0204: * @param type
0205: * @return full qualified, resolved type name in bytecode notation
0206: */
0207: private static String getTypeSignature(IType type) {
0208: if (type == null) {
0209: return null;
0210: }
0211: /*
0212: * getFullyQualifiedName() returns name, where package separator is '.',
0213: * but we need '/' for bytecode. The hack with ',' is to use a character
0214: * which is not allowed as Java char to be sure not to replace too much
0215: */
0216: String name = type.getFullyQualifiedName(',');
0217: // replace package separators
0218: name = name.replace(Signature.C_DOT, PACKAGE_SEPARATOR);
0219: // replace class separators
0220: name = name.replace(',', TYPE_SEPARATOR);
0221: return Signature.C_RESOLVED + name + Signature.C_SEMICOLON;
0222: }
0223:
0224: private static void appendGenericType(StringBuffer sb,
0225: IMethod iMethod, String unresolvedType)
0226: throws JavaModelException {
0227: IType declaringType = iMethod.getDeclaringType();
0228:
0229: // unresolvedType is here like "QA;" => we remove "Q" and ";"
0230: if (unresolvedType.length() < 3) {
0231: // ???? something wrong here ....
0232: sb.append(unresolvedType);
0233: return;
0234: }
0235: unresolvedType = unresolvedType.substring(1, unresolvedType
0236: .length() - 1);
0237:
0238: ITypeParameter typeParameter = iMethod
0239: .getTypeParameter(unresolvedType);
0240: if (typeParameter == null || !typeParameter.exists()) {
0241: typeParameter = declaringType
0242: .getTypeParameter(unresolvedType);
0243: }
0244:
0245: String[] bounds = typeParameter.getBounds();
0246: if (bounds.length == 0) {
0247: sb.append("Ljava/lang/Object;");
0248: } else {
0249: for (int i = 0; i < bounds.length; i++) {
0250: String simplyName = bounds[i];
0251: simplyName = Signature.C_UNRESOLVED + simplyName
0252: + Signature.C_NAME_END;
0253: String resolvedType = getResolvedType(simplyName,
0254: declaringType);
0255: sb.append(resolvedType);
0256: }
0257: }
0258: }
0259:
0260: /**
0261: * @param typeToResolve
0262: * @param declaringType
0263: * @return full qualified "bytecode formatted" type
0264: * @throws JavaModelException
0265: */
0266: private static String getResolvedType(String typeToResolve,
0267: IType declaringType) throws JavaModelException {
0268: StringBuffer sb = new StringBuffer();
0269: int arrayCount = Signature.getArrayCount(typeToResolve);
0270: // test which letter is following - Q or L are for reference types
0271: boolean isPrimitive = isPrimitiveType(typeToResolve
0272: .charAt(arrayCount));
0273: if (isPrimitive) {
0274: // simply add whole string (probably with array chars like [[I etc.)
0275: sb.append(typeToResolve);
0276: } else {
0277: boolean isUnresolvedType = isUnresolvedType(typeToResolve,
0278: arrayCount);
0279: if (!isUnresolvedType) {
0280: sb.append(typeToResolve);
0281: } else {
0282: // we need resolved types
0283: String resolved = getResolvedTypeName(typeToResolve,
0284: declaringType);
0285: if (resolved != null) {
0286: while (arrayCount > 0) {
0287: sb.append(Signature.C_ARRAY);
0288: arrayCount--;
0289: }
0290: sb.append(Signature.C_RESOLVED);
0291: sb.append(resolved);
0292: sb.append(Signature.C_SEMICOLON);
0293: }
0294: }
0295: }
0296: return sb.toString();
0297: }
0298:
0299: /**
0300: * Copied and modified from JavaModelUtil. Resolves a type name in the context of the
0301: * declaring type.
0302: * @param refTypeSig the type name in signature notation (for example 'QVector') this
0303: * can also be an array type, but dimensions will be ignored.
0304: * @param declaringType the context for resolving (type where the reference was made
0305: * in)
0306: * @return returns the fully qualified <b>bytecode </b> type name or build-in-type
0307: * name. if a unresoved type couldn't be resolved null is returned
0308: */
0309: private static String getResolvedTypeName(String refTypeSig,
0310: IType declaringType) throws JavaModelException {
0311:
0312: /* the whole method is copied from JavaModelUtil.getResolvedTypeName(...).
0313: * The problem is, that JavaModelUtil uses '.' to separate package
0314: * names, but we need '/' -> see JavaModelUtil.concatenateName() vs
0315: * JdtUtils.concatenateName()
0316: */
0317: int arrayCount = Signature.getArrayCount(refTypeSig);
0318: if (isUnresolvedType(refTypeSig, arrayCount)) {
0319: String name = ""; //$NON-NLS-1$
0320: int bracket = refTypeSig.indexOf(Signature.C_GENERIC_START,
0321: arrayCount + 1);
0322: if (bracket > 0) {
0323: name = refTypeSig.substring(arrayCount + 1, bracket);
0324: } else {
0325: int semi = refTypeSig.indexOf(Signature.C_SEMICOLON,
0326: arrayCount + 1);
0327: if (semi == -1) {
0328: throw new IllegalArgumentException();
0329: }
0330: name = refTypeSig.substring(arrayCount + 1, semi);
0331: }
0332: String[][] resolvedNames = declaringType.resolveType(name);
0333: if (resolvedNames != null && resolvedNames.length > 0) {
0334: return concatenateName(resolvedNames[0][0],
0335: resolvedNames[0][1]);
0336: }
0337: return null;
0338: }
0339: return refTypeSig.substring(arrayCount);// Signature.toString(substring);
0340: }
0341:
0342: /**
0343: * @param refTypeSig
0344: * @param arrayCount expected array count in the signature
0345: * @return true if the given string is an unresolved signature (Eclipse - internal
0346: * representation)
0347: */
0348: private static boolean isUnresolvedType(String refTypeSig,
0349: int arrayCount) {
0350: char type = refTypeSig.charAt(arrayCount);
0351: return type == Signature.C_UNRESOLVED;
0352: }
0353:
0354: /**
0355: * Concatenates package and class name. Both strings can be empty or <code>null</code>.
0356: */
0357: private static String concatenateName(String packageName,
0358: String className) {
0359: StringBuffer buf = new StringBuffer();
0360: if (packageName != null && packageName.length() > 0) {
0361: packageName = packageName.replace(Signature.C_DOT,
0362: PACKAGE_SEPARATOR);
0363: buf.append(packageName);
0364: }
0365: if (className != null && className.length() > 0) {
0366: if (buf.length() > 0) {
0367: buf.append(PACKAGE_SEPARATOR);
0368: }
0369: className = className.replace(Signature.C_DOT,
0370: TYPE_SEPARATOR);
0371: buf.append(className);
0372: }
0373: return buf.toString();
0374: }
0375:
0376: /**
0377: * Test which letter is following - Q or L are for reference types
0378: * @param first
0379: * @return true, if character is not a simbol for reference types
0380: */
0381: private static boolean isPrimitiveType(char first) {
0382: return (first != Signature.C_RESOLVED && first != Signature.C_UNRESOLVED);
0383: }
0384:
0385: /**
0386: * @param childEl may be null
0387: * @return first ancestor with IJavaElement.TYPE element type, or null
0388: */
0389: public static IType getEnclosingType(IJavaElement childEl) {
0390: if (childEl == null) {
0391: return null;
0392: }
0393: return (IType) childEl.getAncestor(IJavaElement.TYPE);
0394: }
0395:
0396: /**
0397: * @param cf
0398: * @param dc
0399: * @return inner type which has the same name as the given string, or null
0400: */
0401: public static IClassFile getInnerType(IClassFile cf,
0402: DecompiledClass dc, String typeSignature) {
0403: if (typeSignature.endsWith(";")) {
0404: typeSignature = typeSignature.substring(0, typeSignature
0405: .length() - 1);
0406: if (typeSignature.startsWith("L")) {
0407: typeSignature = typeSignature.substring(1,
0408: typeSignature.length());
0409: }
0410: }
0411: /*
0412: * For inner and anonymous classes from the blocks or methods
0413: * getFullyQualifiedName() does not work if class was compiled with 1.5
0414: * and will never match the fullTypeName...
0415: * I'm not sure if it is intended or if it is a bug
0416: * in Eclipse: instead of A$1B we get A$B for B class from a method in A
0417: *
0418: * NB: for binary types without source attachment the method elements doesn't
0419: * contain source and therefore could not resolve child elements.
0420: * So the search for local types will never work...
0421: *
0422: * Therefore we do not use Eclipse API and use ClassNode->InnerClassNode elements
0423: */
0424: ClassNode cn = dc.getClassNode();
0425: List/*<InnerClassNode>*/innerClasses = cn.innerClasses;
0426:
0427: for (int i = 0; i < innerClasses.size(); i++) {
0428: InnerClassNode in = (InnerClassNode) innerClasses.get(i);
0429: if (typeSignature.equals(in.name)) {
0430: int idx = typeSignature.lastIndexOf(PACKAGE_SEPARATOR);
0431: String className = typeSignature;
0432: if (idx > 0) {
0433: className = typeSignature.substring(idx + 1,
0434: typeSignature.length());
0435: }
0436: className += ".class";
0437: return cf.getType().getPackageFragment().getClassFile(
0438: className);
0439: }
0440: }
0441: return null;
0442: }
0443:
0444: /**
0445: * Modified copy from org.eclipse.jdt.internal.ui.actions.SelectionConverter
0446: * @param input
0447: * @param selection
0448: * @return null, if selection is null or could not be resolved to java element
0449: * @throws JavaModelException
0450: */
0451: public static IJavaElement getElementAtOffset(IJavaElement input,
0452: ITextSelection selection) throws JavaModelException {
0453: if (selection == null) {
0454: return null;
0455: }
0456: ICompilationUnit workingCopy = null;
0457: if (input instanceof ICompilationUnit) {
0458: workingCopy = (ICompilationUnit) input;
0459: // be in-sync with model
0460: // instead of using internal JavaModelUtil.reconcile(workingCopy);
0461: synchronized (workingCopy) {
0462: workingCopy
0463: .reconcile(
0464: ICompilationUnit.NO_AST,
0465: false /* don't force problem detection */,
0466: null /* use primary owner */, null /* no progress monitor */);
0467: }
0468: IJavaElement ref = workingCopy.getElementAt(selection
0469: .getOffset());
0470: if (ref != null) {
0471: return ref;
0472: }
0473: } else if (input instanceof IClassFile) {
0474: IClassFile iClass = (IClassFile) input;
0475: IJavaElement ref = iClass.getElementAt(selection
0476: .getOffset());
0477: if (ref != null) {
0478: // If we are in the inner class, try to refine search result now
0479: if (ref instanceof IType) {
0480: IType type = (IType) ref;
0481: IClassFile classFile = type.getClassFile();
0482: if (classFile != iClass) {
0483: /*
0484: * WORKAROUND it seems that source range for constructors from
0485: * bytecode with source attached from zip files is not computed
0486: * in Eclipse (SourceMapper returns nothing useful).
0487: * Example: HashMap$Entry class with constructor
0488: * <init>(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V
0489: * We will get here at least the inner class...
0490: * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=137847
0491: */
0492: ref = classFile.getElementAt(selection
0493: .getOffset());
0494: }
0495: }
0496: return ref;
0497: }
0498: }
0499: return null;
0500: }
0501:
0502: /**
0503: * Modified copy from JavaModelUtil.
0504: * @param javaElt
0505: * @return true, if corresponding java project has compiler setting to generate
0506: * bytecode for jdk 1.5 and above
0507: */
0508: public static boolean is50OrHigher(IJavaElement javaElt) {
0509: IJavaProject project = javaElt.getJavaProject();
0510: String option = project.getOption(JavaCore.COMPILER_COMPLIANCE,
0511: true);
0512: boolean result = JavaCore.VERSION_1_5.equals(option);
0513: if (result) {
0514: return result;
0515: }
0516: // probably > 1.5?
0517: result = JavaCore.VERSION_1_4.equals(option);
0518: if (result) {
0519: return false;
0520: }
0521: result = JavaCore.VERSION_1_3.equals(option);
0522: if (result) {
0523: return false;
0524: }
0525: result = JavaCore.VERSION_1_2.equals(option);
0526: if (result) {
0527: return false;
0528: }
0529: result = JavaCore.VERSION_1_1.equals(option);
0530: if (result) {
0531: return false;
0532: }
0533: // unknown = > 1.5
0534: return true;
0535: }
0536:
0537: /**
0538: * Cite: jdk1.1.8/docs/guide/innerclasses/spec/innerclasses.doc10.html: For the sake
0539: * of tools, there are some additional requirements on the naming of an inaccessible
0540: * class N. Its bytecode name must consist of the bytecode name of an enclosing class
0541: * (the immediately enclosing class, if it is a member), followed either by `$' and a
0542: * positive decimal numeral chosen by the compiler, or by `$' and the simple name of
0543: * N, or else by both (in that order). Moreover, the bytecode name of a block-local N
0544: * must consist of its enclosing package member T, the characters `$1$', and N, if the
0545: * resulting name would be unique.
0546: * <br>
0547: * Note, that this rule was changed for static blocks after 1.5 jdk.
0548: * @param javaElement
0549: * @return simply element name
0550: */
0551: public static String getElementName(IJavaElement javaElement) {
0552: if (isAnonymousType(javaElement)) {
0553: IType anonType = (IType) javaElement;
0554: List allAnonymous = new ArrayList();
0555: /*
0556: * in order to resolve anon. class name we need to know about all other
0557: * anonymous classes in declaring class, therefore we need to collect all here
0558: */
0559: collectAllAnonymous(allAnonymous, anonType);
0560: int idx = getAnonimousIndex(anonType,
0561: (IType[]) allAnonymous
0562: .toArray(new IType[allAnonymous.size()]));
0563: return Integer.toString(idx);
0564: }
0565: String name = javaElement.getElementName();
0566: if (isLocal(javaElement)) {
0567: /*
0568: * Compiler have different naming conventions for inner non-anon. classes in
0569: * static blocks or any methods, this difference was introduced with 1.5 JDK.
0570: * The problem is, that we could have projects with classes, generated
0571: * with both 1.5 and earlier settings. One could not see on particular
0572: * java element, for which jdk version the existing bytecode was generated.
0573: * If we could have a *.class file, but we are just searching for one...
0574: * So there could be still a chance, that this code fails, if java element
0575: * is not compiled with comiler settings from project, but with different
0576: */
0577: if (is50OrHigher(javaElement)) {
0578: name = "1" + name; // compiler output changed for > 1.5 code
0579: } else {
0580: name = "1$" + name; // see method comment, this was the case for older code
0581: }
0582: }
0583:
0584: if (name.endsWith(".java")) { //$NON-NLS-1$
0585: name = name.substring(0, name.lastIndexOf(".java")); //$NON-NLS-1$
0586: } else if (name.endsWith(".class")) { //$NON-NLS-1$
0587: name = name.substring(0, name.lastIndexOf(".class")); //$NON-NLS-1$
0588: }
0589: return name;
0590: }
0591:
0592: /**
0593: * @param javaElement
0594: * @return null, if javaElement is top level class
0595: */
0596: static IType getFirstAncestor(IJavaElement javaElement) {
0597: IJavaElement parent = javaElement;
0598: if (javaElement.getElementType() == IJavaElement.TYPE) {
0599: parent = javaElement.getParent();
0600: }
0601: if (parent != null) {
0602: return (IType) parent.getAncestor(IJavaElement.TYPE);
0603: }
0604: return null;
0605: }
0606:
0607: static IJavaElement getLastAncestor(IJavaElement javaElement,
0608: int elementType) {
0609: IJavaElement lastFound = null;
0610: if (elementType == javaElement.getElementType()) {
0611: lastFound = javaElement;
0612: }
0613: IJavaElement parent = javaElement.getParent();
0614: if (parent == null) {
0615: return lastFound;
0616: }
0617: IJavaElement ancestor = parent.getAncestor(elementType);
0618: if (ancestor != null) {
0619: return getLastAncestor(ancestor, elementType);
0620: }
0621: return lastFound;
0622: }
0623:
0624: /**
0625: * @param javaElement
0626: * @return distance to given ancestor, 0 if it is the same, -1 if ancestor with type
0627: * IJavaElement.TYPE does not exist
0628: */
0629: static int getTopAncestorDistance(IJavaElement javaElement,
0630: IJavaElement topAncestor) {
0631: if (topAncestor == javaElement) {
0632: return 0;
0633: }
0634: IJavaElement ancestor = getFirstAncestor(javaElement);
0635: if (ancestor != null) {
0636: return 1 + getTopAncestorDistance(ancestor, topAncestor);
0637: }
0638: // this is not possible, if ancestor exist - which return value we should use?
0639: return -1;
0640: }
0641:
0642: /**
0643: * @param javaElement
0644: * @return first non-anonymous ancestor
0645: */
0646: static IJavaElement getFirstNonAnonymous(IJavaElement javaElement,
0647: IJavaElement topAncestor) {
0648: if (javaElement.getElementType() == IJavaElement.TYPE
0649: && !isAnonymousType(javaElement)) {
0650: return javaElement;
0651: }
0652: IJavaElement parent = javaElement.getParent();
0653: if (parent == null) {
0654: return topAncestor;
0655: }
0656: IJavaElement ancestor = parent.getAncestor(IJavaElement.TYPE);
0657: if (ancestor != null) {
0658: return getFirstNonAnonymous(ancestor, topAncestor);
0659: }
0660: return topAncestor;
0661: }
0662:
0663: /**
0664: * @param javaElement
0665: * @return true, if given element is anonymous inner class
0666: */
0667: private static boolean isAnonymousType(IJavaElement javaElement) {
0668: try {
0669: return javaElement instanceof IType
0670: && ((IType) javaElement).isAnonymous();
0671: } catch (JavaModelException e) {
0672: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
0673: }
0674: return false;
0675: }
0676:
0677: /**
0678: * @param innerType should be inner type.
0679: * @return true, if given element is inner class from initializer block or method body
0680: */
0681: private static boolean isLocal(IJavaElement innerType) {
0682: try {
0683: return innerType instanceof IType
0684: && ((IType) innerType).isLocal();
0685: } catch (JavaModelException e) {
0686: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
0687: }
0688: return false;
0689: }
0690:
0691: /**
0692: * @param type
0693: * @return true, if given element is non static inner class
0694: * @throws JavaModelException
0695: */
0696: private static boolean isNonStaticInner(IType type)
0697: throws JavaModelException {
0698: if (type.isMember()) {
0699: return !Flags.isStatic(type.getFlags());
0700: }
0701: return false;
0702: }
0703:
0704: /**
0705: * @param innerType should be inner type.
0706: * @return true, if given element is inner class from initializer block
0707: */
0708: private static boolean isInnerFromBlock(IType type) {
0709: try {
0710: IJavaElement ancestor = type
0711: .getAncestor(IJavaElement.INITIALIZER);
0712: return type.isLocal() && ancestor != null;
0713: } catch (JavaModelException e) {
0714: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
0715: }
0716: return false;
0717: }
0718:
0719: /**
0720: * @param javaElement
0721: * @return absolute path of generated bytecode package for given element
0722: * @throws JavaModelException
0723: */
0724: private static String getPackageOutputPath(IJavaElement javaElement)
0725: throws JavaModelException {
0726: String dir = ""; //$NON-NLS-1$
0727: if (javaElement == null) {
0728: return dir;
0729: }
0730:
0731: IJavaProject project = javaElement.getJavaProject();
0732:
0733: if (project == null) {
0734: return dir;
0735: }
0736: // default bytecode location
0737: IPath path = project.getOutputLocation();
0738:
0739: IResource resource = javaElement.getUnderlyingResource();
0740: if (resource == null) {
0741: return dir;
0742: }
0743: // resolve multiple output locations here
0744: if (project.exists() && project.getProject().isOpen()) {
0745: IClasspathEntry entries[] = project.getRawClasspath();
0746: for (int i = 0; i < entries.length; i++) {
0747: IClasspathEntry classpathEntry = entries[i];
0748: if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
0749: IPath outputPath = classpathEntry
0750: .getOutputLocation();
0751: if (outputPath != null
0752: && classpathEntry.getPath().isPrefixOf(
0753: resource.getFullPath())) {
0754: path = outputPath;
0755: break;
0756: }
0757: }
0758: }
0759: }
0760:
0761: if (path == null) {
0762: // check the default location if not already included
0763: IPath def = project.getOutputLocation();
0764: if (def != null && def.isPrefixOf(resource.getFullPath())) {
0765: path = def;
0766: }
0767: }
0768:
0769: if (path == null) {
0770: return dir;
0771: }
0772:
0773: IWorkspace workspace = ResourcesPlugin.getWorkspace();
0774:
0775: if (!project.getPath().equals(path)) {
0776: IFolder outputFolder = workspace.getRoot().getFolder(path);
0777: if (outputFolder != null) {
0778: // linked resources will be resolved here!
0779: IPath rawPath = outputFolder.getRawLocation();
0780: if (rawPath != null) {
0781: path = rawPath;
0782: }
0783: }
0784: } else {
0785: path = project.getProject().getLocation();
0786: }
0787:
0788: // here we should resolve path variables,
0789: // probably existing at first place of path
0790: IPathVariableManager pathManager = workspace
0791: .getPathVariableManager();
0792: path = pathManager.resolvePath(path);
0793:
0794: if (path == null) {
0795: return dir;
0796: }
0797:
0798: if (isPackageRoot(project, resource)) {
0799: dir = path.toOSString();
0800: } else {
0801: String packPath = EclipseUtils.getJavaPackageName(
0802: javaElement).replace(Signature.C_DOT,
0803: PACKAGE_SEPARATOR);
0804: dir = path.append(packPath).toOSString();
0805: }
0806: return dir;
0807: }
0808:
0809: /**
0810: * @param project
0811: * @param pack
0812: * @return true if 'pack' argument is package root
0813: * @throws JavaModelException
0814: */
0815: private static boolean isPackageRoot(IJavaProject project,
0816: IResource pack) throws JavaModelException {
0817: boolean isRoot = false;
0818: if (project == null || pack == null) {
0819: return isRoot;
0820: }
0821: IPackageFragmentRoot root = project
0822: .getPackageFragmentRoot(pack);
0823: IClasspathEntry clPathEntry = null;
0824: if (root != null) {
0825: clPathEntry = root.getRawClasspathEntry();
0826: }
0827: isRoot = clPathEntry != null;
0828: return isRoot;
0829: }
0830:
0831: /**
0832: * Works only for eclipse - managed/generated bytecode, ergo not with imported
0833: * classes/jars
0834: * @param javaElement
0835: * @return full os-specific file path to .class resource, containing given element
0836: */
0837: public static String getByteCodePath(IJavaElement javaElement) {
0838: if (javaElement == null) {
0839: return "";//$NON-NLS-1$
0840: }
0841: String packagePath = ""; //$NON-NLS-1$
0842: try {
0843: packagePath = getPackageOutputPath(javaElement);
0844: } catch (JavaModelException e) {
0845: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
0846: return "";
0847: }
0848: IJavaElement ancestor = getLastAncestor(javaElement,
0849: IJavaElement.TYPE);
0850: StringBuffer sb = new StringBuffer(packagePath);
0851: sb.append(File.separator);
0852: sb.append(getClassName(javaElement, ancestor));
0853: sb.append(".class"); //$NON-NLS-1$
0854: return sb.toString();
0855: }
0856:
0857: /**
0858: * @param javaElement
0859: * @return new generated input stream for given element bytecode class file, or null
0860: * if class file cannot be found or this element is not from java source path
0861: */
0862: public static InputStream createInputStream(IJavaElement javaElement) {
0863: IClassFile classFile = (IClassFile) javaElement
0864: .getAncestor(IJavaElement.CLASS_FILE);
0865: InputStream is = null;
0866:
0867: // existing read-only class files
0868: if (classFile != null) {
0869: IJavaElement jarParent = classFile.getParent();
0870: // TODO dirty hack to be sure, that package is from jar -
0871: // because JarPackageFragment is not public class, we cannot
0872: // use instanceof here
0873: boolean isJar = jarParent != null
0874: && jarParent.getClass().getName().endsWith(
0875: "JarPackageFragment"); //$NON-NLS-1$
0876: if (isJar) {
0877: is = createStreamFromJar(classFile);
0878: } else {
0879: is = createStreamFromClass(classFile);
0880: }
0881: } else {
0882: // usual eclipse - generated bytecode
0883:
0884: boolean inJavaPath = isOnClasspath(javaElement);
0885: if (!inJavaPath) {
0886: return null;
0887: }
0888: String classPath = getByteCodePath(javaElement);
0889:
0890: try {
0891: is = new FileInputStream(classPath);
0892: } catch (FileNotFoundException e) {
0893: // if autobuild is disabled, we get tons of this errors.
0894: // but I think we cannot ignore them, therefore WARNING and not
0895: // ERROR status
0896: BytecodeOutlinePlugin.log(e, IStatus.WARNING);
0897: }
0898: }
0899: return is;
0900: }
0901:
0902: /**
0903: * Creates stream from external class file from Eclipse classpath (means, that this
0904: * class file is read-only)
0905: * @param classFile
0906: * @return new generated input stream from external class file, or null, if class file
0907: * for this element cannot be found
0908: */
0909: private static InputStream createStreamFromClass(
0910: IClassFile classFile) {
0911: IResource underlyingResource = null;
0912: try {
0913: // to tell the truth, I don't know why that different methods
0914: // are not working in a particular case. But it seems to be better
0915: // to use getResource() with non-java elements (not in model)
0916: // and getUnderlyingResource() with java elements.
0917: if (classFile.exists()) {
0918: underlyingResource = classFile.getUnderlyingResource();
0919: } else {
0920: // this is a class file that is not in java model
0921: underlyingResource = classFile.getResource();
0922: }
0923: } catch (JavaModelException e) {
0924: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
0925: return null;
0926: }
0927: IPath rawLocation = underlyingResource.getRawLocation();
0928: // here we should resolve path variables,
0929: // probably existing at first place of "rawLocation" path
0930: IWorkspace workspace = ResourcesPlugin.getWorkspace();
0931: IPathVariableManager pathManager = workspace
0932: .getPathVariableManager();
0933: rawLocation = pathManager.resolvePath(rawLocation);
0934: try {
0935: return new FileInputStream(rawLocation.toOSString());
0936: } catch (FileNotFoundException e) {
0937: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
0938: }
0939: return null;
0940: }
0941:
0942: /**
0943: * Creates stream from external class file that is stored in jar file
0944: * @param classFile
0945: * @param javaElement
0946: * @return new generated input stream from external class file that is stored in jar
0947: * file, or null, if class file for this element cannot be found
0948: */
0949: private static InputStream createStreamFromJar(IClassFile classFile) {
0950: IPath path = null;
0951: IResource resource = classFile.getResource();
0952: // resource == null => this is a external archive
0953: if (resource != null) {
0954: path = resource.getRawLocation();
0955: } else {
0956: path = classFile.getPath();
0957: }
0958: if (path == null) {
0959: return null;
0960: }
0961: // here we should resolve path variables,
0962: // probably existing at first place of path
0963: IWorkspace workspace = ResourcesPlugin.getWorkspace();
0964: IPathVariableManager pathManager = workspace
0965: .getPathVariableManager();
0966: path = pathManager.resolvePath(path);
0967:
0968: JarFile jar = null;
0969: try {
0970: jar = new JarFile(path.toOSString());
0971: } catch (IOException e) {
0972: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
0973: return null;
0974: }
0975: String fullClassName = getFullBytecodeName(classFile);
0976: if (fullClassName == null) {
0977: return null;
0978: }
0979: JarEntry jarEntry = jar.getJarEntry(fullClassName);
0980: if (jarEntry != null) {
0981: try {
0982: return jar.getInputStream(jarEntry);
0983: } catch (IOException e) {
0984: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
0985: }
0986: }
0987: return null;
0988: }
0989:
0990: private static boolean isOnClasspath(IJavaElement javaElement) {
0991: IJavaProject project = javaElement.getJavaProject();
0992: if (project != null) {
0993: boolean result = project.isOnClasspath(javaElement);
0994: return result;
0995: }
0996: return false;
0997: }
0998:
0999: /**
1000: * @param classFile
1001: * @return full qualified bytecode name of given class
1002: */
1003: public static String getFullBytecodeName(IClassFile classFile) {
1004: IPackageFragment packageFr = (IPackageFragment) classFile
1005: .getAncestor(IJavaElement.PACKAGE_FRAGMENT);
1006: if (packageFr == null) {
1007: return null;
1008: }
1009: String packageName = packageFr.getElementName();
1010: // switch to java bytecode naming conventions
1011: packageName = packageName.replace(Signature.C_DOT,
1012: PACKAGE_SEPARATOR);
1013:
1014: String className = classFile.getElementName();
1015: if (packageName != null && packageName.length() > 0) {
1016: return packageName + PACKAGE_SEPARATOR + className;
1017: }
1018: return className;
1019: }
1020:
1021: /**
1022: * @param javaElement
1023: * @param topAncestor
1024: * @param sb
1025: */
1026: private static String getClassName(IJavaElement javaElement,
1027: IJavaElement topAncestor) {
1028: StringBuffer sb = new StringBuffer();
1029: if (!javaElement.equals(topAncestor)) {
1030: int elementType = javaElement.getElementType();
1031: if (elementType == IJavaElement.FIELD
1032: || elementType == IJavaElement.METHOD
1033: || elementType == IJavaElement.INITIALIZER) {
1034: // it's field or method
1035: javaElement = getFirstAncestor(javaElement);
1036: } else {
1037: boolean is50OrHigher = is50OrHigher(javaElement);
1038: if (!is50OrHigher
1039: && (isAnonymousType(javaElement) || isLocal(javaElement))) {
1040: // it's inner type
1041: sb.append(getElementName(topAncestor));
1042: sb.append(TYPE_SEPARATOR);
1043: } else {
1044: /*
1045: * TODO there is an issue with < 1.5 compiler setting and with inner
1046: * classes with the same name but defined in different methods in the same
1047: * source file. Then compiler needs to generate *different* content for
1048: * A$1$B and A$1$B, which is not possible so therefore compiler generates
1049: * A$1$B and A$2$B. The naming order is the source range order of inner
1050: * classes, so the first inner B class will get A$1$B and the second
1051: * inner B class A$2$B etc.
1052: */
1053:
1054: // override top ancestor with immediate ancestor
1055: topAncestor = getFirstAncestor(javaElement);
1056: while (topAncestor != null) {
1057: sb.insert(0, getElementName(topAncestor)
1058: + TYPE_SEPARATOR);
1059: topAncestor = getFirstAncestor(topAncestor);
1060: }
1061: }
1062: }
1063: }
1064: sb.append(getElementName(javaElement));
1065: return sb.toString();
1066: }
1067:
1068: /**
1069: * Collect all anonymous classes which are on the same "name shema level"
1070: * as the given element for the compiler. The list could contain different set of
1071: * elements for the same source code, depends on the compiler and jdk version
1072: * @param list for the found anon. classes, elements instanceof IType.
1073: * @param anonType the anon. type
1074: */
1075: private static void collectAllAnonymous(List list, IType anonType) {
1076: /*
1077: * For JDK >= 1.5 in Eclipse 3.1+ the naming shema for nested anonymous
1078: * classes was changed from A$1, A$2, A$3, A$4, ..., A$n
1079: * to A$1, A$1$1, A$1$2, A$1$2$1, ..., A$2, A$2$1, A$2$2, ..., A$x$y
1080: */
1081: boolean allowNested = !is50OrHigher(anonType);
1082:
1083: IParent declaringType;
1084: if (allowNested) {
1085: declaringType = (IType) getLastAncestor(anonType,
1086: IJavaElement.TYPE);
1087: } else {
1088: declaringType = anonType.getDeclaringType();
1089: }
1090:
1091: try {
1092: collectAllAnonymous(list, declaringType, allowNested);
1093: } catch (JavaModelException e) {
1094: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1095: }
1096: }
1097:
1098: /**
1099: * Traverses down the children tree of this parent and collect all child anon. classes
1100: * @param list
1101: * @param parent
1102: * @param allowNested true to search in IType child elements too
1103: * @throws JavaModelException
1104: */
1105: private static void collectAllAnonymous(List list, IParent parent,
1106: boolean allowNested) throws JavaModelException {
1107: IJavaElement[] children = parent.getChildren();
1108: for (int i = 0; i < children.length; i++) {
1109: IJavaElement childElem = children[i];
1110: if (isAnonymousType(childElem)) {
1111: list.add(childElem);
1112: }
1113: if (childElem instanceof IParent) {
1114: if (allowNested || !(childElem instanceof IType)) {
1115: collectAllAnonymous(list, (IParent) childElem,
1116: allowNested);
1117: }
1118: }
1119: }
1120: }
1121:
1122: /**
1123: * @param anonType
1124: * @param anonymous
1125: * @return the index of given java element in the anon. classes list, which was used
1126: * by compiler to generate bytecode name for given element. If the given type is not
1127: * in the list, then return value is '-1'
1128: */
1129: private static int getAnonimousIndex(IType anonType,
1130: IType[] anonymous) {
1131: sortAnonymous(anonymous, anonType);
1132: for (int i = 0; i < anonymous.length; i++) {
1133: if (anonymous[i] == anonType) {
1134: // +1 because compiler starts generated classes always with 1
1135: return i + 1;
1136: }
1137: }
1138: return -1;
1139: }
1140:
1141: /**
1142: * Sort given anonymous classes in order like java compiler would generate output
1143: * classes, in context of given anonymous type
1144: * @param anonymous
1145: */
1146: private static void sortAnonymous(IType[] anonymous, IType anonType) {
1147: SourceOffsetComparator sourceComparator = new SourceOffsetComparator();
1148: Arrays.sort(anonymous, new AnonymClassComparator(anonType,
1149: sourceComparator));
1150: }
1151:
1152: /**
1153: * 1) from instance init 2) from deepest inner from instance init (deepest first) 3) from
1154: * static init 4) from deepest inner from static init (deepest first) 5) from deepest inner
1155: * (deepest first) 6) regular anon classes from main class
1156: *
1157: * <br>
1158: * Note, that nested inner anon. classes which do not have different non-anon. inner class
1159: * ancestors, are compiled in they nesting order, opposite to rule 2)
1160: *
1161: * @param javaElement
1162: * @return priority - lesser mean wil be compiled later, a value > 0
1163: * @throws JavaModelException
1164: */
1165: static int getAnonCompilePriority(IJavaElement javaElement,
1166: IJavaElement firstAncestor, IJavaElement topAncestor,
1167: boolean is50OrHigher) {
1168:
1169: // search for initializer block
1170: IJavaElement lastAncestor = getLastAncestor(javaElement,
1171: IJavaElement.INITIALIZER);
1172: // test is for anon. classes from initializer blocks
1173: if (lastAncestor != null) {
1174: IInitializer init = (IInitializer) lastAncestor;
1175: int initFlags = 0;
1176: try {
1177: initFlags = init.getFlags();
1178: } catch (JavaModelException e) {
1179: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1180: }
1181:
1182: if (is50OrHigher) {
1183: if (!Flags.isStatic(initFlags)) {
1184: if (firstAncestor == topAncestor) {
1185: return 10; // instance init
1186: }
1187: return 9; // from inner from instance init
1188: }
1189:
1190: if (firstAncestor == topAncestor) {
1191: return 8; // class init
1192: }
1193: return 7; // from inner from class init
1194: }
1195:
1196: /*
1197: * crazy 1.4 logic
1198: */
1199: if (Flags.isStatic(initFlags)) {
1200: return 10;
1201: }
1202: IJavaElement firstNonAnon = getFirstNonAnonymous(
1203: javaElement, topAncestor);
1204: if (isStatic((IMember) firstNonAnon)) {
1205: return 9;
1206: }
1207:
1208: return 6; // class init
1209: }
1210: // from inner from main type
1211: if (!is50OrHigher) {
1212: /*
1213: * crazy 1.4 logic
1214: */
1215: int topFlags = 0;
1216: IJavaElement firstNonAnon = getFirstNonAnonymous(
1217: javaElement, topAncestor);
1218: try {
1219: topFlags = ((IType) firstNonAnon).getFlags();
1220: } catch (JavaModelException e) {
1221: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1222: }
1223: if (Flags.isStatic(topFlags)) {
1224: return 8;
1225: }
1226:
1227: if (firstAncestor == topAncestor) {
1228: return 6; // regular anonyme classes
1229: }
1230: return 6; // the same prio as regular anonyme classes
1231: }
1232:
1233: // test for anon. classes from "regular" code
1234: if (firstAncestor == topAncestor) {
1235: return 5; // regular anonyme classes
1236: }
1237: return 6;
1238: }
1239:
1240: private static boolean isStatic(IMember firstNonAnon) {
1241: int topFlags = 0;
1242: try {
1243: topFlags = firstNonAnon.getFlags();
1244: } catch (JavaModelException e) {
1245: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1246: }
1247: return Flags.isStatic(topFlags);
1248: }
1249:
1250: /**
1251: * @param type
1252: * @return
1253: */
1254: public static ClassLoader getClassLoader(IJavaElement type) {
1255: ClassLoader cl;
1256:
1257: IJavaProject javaProject = type.getJavaProject();
1258: List urls = new ArrayList();
1259:
1260: getClassURLs(javaProject, urls);
1261:
1262: if (urls.isEmpty()) {
1263: cl = JdtUtils.class.getClassLoader();
1264: } else {
1265: cl = new URLClassLoader((URL[]) urls.toArray(new URL[urls
1266: .size()]));
1267: }
1268: return cl;
1269: }
1270:
1271: private static void getClassURLs(IJavaProject javaProject, List urls) {
1272: IProject project = javaProject.getProject();
1273: IWorkspaceRoot workspaceRoot = project.getWorkspace().getRoot();
1274:
1275: IClasspathEntry[] paths = null;
1276: IPath defaultOutputLocation = null;
1277: try {
1278: paths = javaProject.getResolvedClasspath(true);
1279: defaultOutputLocation = javaProject.getOutputLocation();
1280: } catch (JavaModelException e) {
1281: // don't show message to user neither log it
1282: // BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1283: }
1284: if (paths != null) {
1285: IPath projectPath = javaProject.getProject().getLocation();
1286: for (int i = 0; i < paths.length; ++i) {
1287: IClasspathEntry cpEntry = paths[i];
1288: IPath p = null;
1289: if (cpEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
1290: // filter out source container - there are unused for class
1291: // search - add bytecode output location instead
1292: p = cpEntry.getOutputLocation();
1293: if (p == null) {
1294: // default output used:
1295: p = defaultOutputLocation;
1296: }
1297: } else if (cpEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
1298: String projName = cpEntry.getPath()
1299: .toPortableString().substring(1);
1300: IProject proj = workspaceRoot.getProject(projName);
1301: IJavaProject projj = JavaCore.create(proj);
1302: getClassURLs(projj, urls);
1303: continue;
1304: } else {
1305: p = cpEntry.getPath();
1306: }
1307:
1308: if (p == null) {
1309: continue;
1310: }
1311: if (!p.toFile().exists()) {
1312: // removeFirstSegments: remove project from relative path
1313: p = projectPath.append(p.removeFirstSegments(1));
1314: if (!p.toFile().exists()) {
1315: continue;
1316: }
1317: }
1318: try {
1319: urls.add(p.toFile().toURL());
1320: } catch (MalformedURLException e) {
1321: // don't show message to user
1322: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1323: }
1324: }
1325: }
1326: }
1327:
1328: /**
1329: * Check if java element is an interface or abstract method or a method from
1330: * interface.
1331: */
1332: public static boolean isAbstractOrInterface(IJavaElement javaEl) {
1333: if (javaEl == null) {
1334: return true;
1335: }
1336: boolean abstractOrInterface = false;
1337: try {
1338: switch (javaEl.getElementType()) {
1339: case IJavaElement.CLASS_FILE:
1340: IClassFile classFile = (IClassFile) javaEl;
1341: if (isOnClasspath(javaEl)) {
1342: abstractOrInterface = classFile.isInterface();
1343: } /*else {
1344: this is the case for eclipse-generated class files.
1345: if we do not perform the check in if, then we will have java model
1346: exception on classFile.isInterface() call.
1347: }*/
1348: break;
1349: case IJavaElement.COMPILATION_UNIT:
1350: ICompilationUnit cUnit = (ICompilationUnit) javaEl;
1351: IType type = cUnit.findPrimaryType();
1352: abstractOrInterface = type != null
1353: && type.isInterface();
1354: break;
1355: case IJavaElement.TYPE:
1356: abstractOrInterface = ((IType) javaEl).isInterface();
1357: break;
1358: case IJavaElement.METHOD:
1359: // test for "abstract" flag on method in a class
1360: abstractOrInterface = Flags
1361: .isAbstract(((IMethod) javaEl).getFlags());
1362: // "abstract" flags could be not exist on interface methods
1363: if (!abstractOrInterface) {
1364: IType ancestor = (IType) javaEl
1365: .getAncestor(IJavaElement.TYPE);
1366: abstractOrInterface = ancestor != null
1367: && ancestor.isInterface();
1368: }
1369: break;
1370: default:
1371: IType ancestor1 = (IType) javaEl
1372: .getAncestor(IJavaElement.TYPE);
1373: abstractOrInterface = ancestor1 != null
1374: && ancestor1.isInterface();
1375: break;
1376: }
1377: } catch (JavaModelException e) {
1378: // No point to log it here
1379: // BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1380: }
1381: return abstractOrInterface;
1382: }
1383:
1384: static class SourceOffsetComparator implements Comparator {
1385:
1386: /**
1387: * First source occurence win.
1388: * @param o1 should be IType
1389: * @param o2 should be IType
1390: * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
1391: */
1392: public int compare(Object o1, Object o2) {
1393: IType m1 = (IType) o1;
1394: IType m2 = (IType) o2;
1395: int idx1, idx2;
1396: try {
1397: ISourceRange sr1 = m1.getSourceRange();
1398: ISourceRange sr2 = m2.getSourceRange();
1399: if (sr1 == null || sr2 == null) {
1400: return 0;
1401: }
1402: idx1 = sr1.getOffset();
1403: idx2 = sr2.getOffset();
1404: } catch (JavaModelException e) {
1405: BytecodeOutlinePlugin.log(e, IStatus.ERROR);
1406: return 0;
1407: }
1408: if (idx1 < idx2) {
1409: return -1;
1410: } else if (idx1 > idx2) {
1411: return 1;
1412: }
1413: return 0;
1414: }
1415: }
1416:
1417: static class AnonymClassComparator implements Comparator {
1418:
1419: private IType topAncestorType;
1420: private SourceOffsetComparator sourceComparator;
1421: private boolean is50OrHigher;
1422:
1423: /**
1424: * @param javaElement
1425: * @param sourceComparator
1426: */
1427: public AnonymClassComparator(IType javaElement,
1428: SourceOffsetComparator sourceComparator) {
1429: this .sourceComparator = sourceComparator;
1430: is50OrHigher = is50OrHigher(javaElement);
1431: topAncestorType = (IType) getLastAncestor(javaElement,
1432: IJavaElement.TYPE);
1433: }
1434:
1435: /**
1436: * If "deep" is the same, then source order win. 1) from instance init 2) from
1437: * deepest inner from instance init (deepest first) 3) from static init 4) from
1438: * deepest inner from static init (deepest first) 5) from deepest inner (deepest
1439: * first) 7) regular anon classes from main class
1440: *
1441: * <br>
1442: * Note, that nested inner anon. classes which do not have different
1443: * non-anon. inner class ancestors, are compiled in they nesting order, opposite
1444: * to rule 2)
1445: *
1446: * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
1447: */
1448: public int compare(Object o1, Object o2) {
1449: if (o1 == o2) {
1450: return 0;
1451: }
1452: IType m1 = (IType) o1;
1453: IType m2 = (IType) o2;
1454: // both have the same ancestor as immediate ancestor
1455: if (!is50OrHigher && isInnerFromBlock(m1)
1456: && isInnerFromBlock(m2)) {
1457: return sourceComparator.compare(o1, o2);
1458: }
1459: IJavaElement firstAncestor1 = getFirstAncestor(m1);
1460: IJavaElement firstAncestor2 = getFirstAncestor(m2);
1461: int compilePrio1 = getAnonCompilePriority(m1,
1462: firstAncestor1, topAncestorType, is50OrHigher);
1463: int compilePrio2 = getAnonCompilePriority(m2,
1464: firstAncestor2, topAncestorType, is50OrHigher);
1465:
1466: // System.out.println("" + compilePrio1 + " -> " + o1.toString().substring(0, 50));
1467: // System.out.println("" + compilePrio2 + " -> " + o2.toString().substring(0, 50));
1468:
1469: if (compilePrio1 > compilePrio2) {
1470: return -1;
1471: } else if (compilePrio1 < compilePrio2) {
1472: return 1;
1473: } else {
1474: firstAncestor1 = getFirstNonAnonymous(m1,
1475: topAncestorType);
1476: firstAncestor2 = getFirstNonAnonymous(m2,
1477: topAncestorType);
1478:
1479: if (firstAncestor1 == firstAncestor2) {
1480: /*
1481: * for anonymous classes from same chain and same first common ancestor,
1482: * the order is the definition order
1483: */
1484: int topAncestorDistance1 = getTopAncestorDistance(
1485: m1, topAncestorType);
1486: int topAncestorDistance2 = getTopAncestorDistance(
1487: m2, topAncestorType);
1488: if (topAncestorDistance1 < topAncestorDistance2) {
1489: return -1;
1490: } else if (topAncestorDistance1 > topAncestorDistance2) {
1491: return 1;
1492: } else {
1493: return sourceComparator.compare(o1, o2);
1494: }
1495: }
1496:
1497: // if(!is50OrHigher && !isStatic(m1) && !isStatic(m2)){
1498: // return sourceComparator.compare(o1, o2);
1499: // }
1500:
1501: /*
1502: * for anonymous classes which have first non-common non-anonymous ancestor,
1503: * the order is the reversed definition order
1504: */
1505: int topAncestorDistance1 = getTopAncestorDistance(
1506: firstAncestor1, topAncestorType);
1507: int topAncestorDistance2 = getTopAncestorDistance(
1508: firstAncestor2, topAncestorType);
1509: if (topAncestorDistance1 > topAncestorDistance2) {
1510: return -1;
1511: } else if (topAncestorDistance1 < topAncestorDistance2) {
1512: return 1;
1513: } else {
1514: return sourceComparator.compare(o1, o2);
1515: }
1516: }
1517: }
1518: }
1519: }
|