org.incha.core.jswingripples.MethodOverrideTester.java Source code

Java tutorial

Introduction

Here is the source code for org.incha.core.jswingripples.MethodOverrideTester.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.incha.core.jswingripples;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.incha.core.jswingripples.util.LRUMap;

public class MethodOverrideTester {
    private static class Substitutions {

        public static final Substitutions EMPTY_SUBST = new Substitutions();

        private HashMap<String, String[]> fMap;

        public Substitutions() {
            fMap = null;
        }

        public void addSubstitution(final String typeVariable, final String substitution, final String erasure) {
            if (fMap == null) {
                fMap = new HashMap<String, String[]>(3);
            }
            fMap.put(typeVariable, new String[] { substitution, erasure });
        }

        private String[] getSubstArray(final String typeVariable) {
            if (fMap != null) {
                return fMap.get(typeVariable);
            }
            return null;
        }

        public String getSubstitution(final String typeVariable) {
            final String[] subst = getSubstArray(typeVariable);
            if (subst != null) {
                return subst[0];
            }
            return null;
        }

        public String getErasure(final String typeVariable) {
            final String[] subst = getSubstArray(typeVariable);
            if (subst != null) {
                return subst[1];
            }
            return null;
        }
    }

    private IType fFocusType;
    private final TypeHierarchySupport hierarhySupport;

    private Map<IMethod, Substitutions> fMethodSubstitutions;
    private Map<IType, Substitutions> fTypeVariableSubstitutions;

    public MethodOverrideTester(final IType focusType, final TypeHierarchySupport hierarhySupport) {
        fFocusType = focusType;
        this.hierarhySupport = hierarhySupport;
        fTypeVariableSubstitutions = null;
        fMethodSubstitutions = null;
    }

    public IType getFocusType() {
        return fFocusType;
    }

    public void setFocusType(final IType type) {
        if (fFocusType != null && type == fFocusType)
            return;
        fFocusType = type;
        fTypeVariableSubstitutions = null;
        fMethodSubstitutions = null;
    }

    /**
     * Finds the method that declares the given method. A declaring method is the 'original' method declaration that does
     * not override nor implement a method. <code>null</code> is returned it the given method does not override
     * a method. When searching, super class are examined before implemented interfaces.
     * @param testVisibility If true the result is tested on visibility. Null is returned if the method is not visible.
     * @throws JavaModelException
     */
    public IMethod findDeclaringMethod(final IMethod overriding, final boolean testVisibility)
            throws JavaModelException {
        IMethod result = null;
        IMethod overridden = findOverriddenMethod(overriding, testVisibility);
        while (overridden != null) {
            result = overridden;
            overridden = findOverriddenMethod(result, testVisibility);
        }
        return result;
    }

    /**
     * Finds all overrides
     */
    public IMethod findAllDeclaringMethods(final IMethod overriding, final boolean testVisibility,
            final HashSet<IMethod> overrides) throws JavaModelException {

        IMethod result = null;
        IMethod overridden = findOverriddenMethod(overriding, testVisibility);

        while (overridden != null) {
            if (overridden.getResource() == null)
                return null;
            overrides.add(overridden);
            result = overridden;
            overridden = findOverriddenMethod(result, testVisibility);
        }
        return result;
    }

    /**
     * Finds the method that is overridden by the given method.
     * First the super class is examined and then the implemented interfaces.
     * @param testVisibility If true the result is tested on visibility. Null is returned if the method is not visible.
     * @throws JavaModelException
     */
    public IMethod findOverriddenMethod(final IMethod overriding, final boolean testVisibility)
            throws JavaModelException {
        final int flags = overriding.getFlags();
        if (Flags.isPrivate(flags) || Flags.isStatic(flags) || overriding.isConstructor()) {
            return null;
        }

        final IType type = overriding.getDeclaringType();
        final IType superClass = hierarhySupport.getSuperclass(type);
        if (superClass != null) {
            //if (superClass.getResource()==null) return null;
            final IMethod res = findOverriddenMethodInHierarchy(superClass, overriding);
            if (res != null && !Flags.isPrivate(res.getFlags())) {
                if (!testVisibility || isVisibleInHierarchy(res, type.getPackageFragment())) {
                    return res;
                }
            }
        }
        if (!overriding.isConstructor()) {
            final IType[] interfaces = hierarhySupport.getSuperInterfaces(type);
            for (int i = 0; i < interfaces.length; i++) {
                //if (interfaces[i].getResource()==null)  continue;
                final IMethod res = findOverriddenMethodInHierarchy(interfaces[i], overriding);
                if (res != null) {
                    return res; // methods from interfaces are always public and therefore visible
                }
            }
        }
        return null;
    }

    /**
     * Finds the directly overridden method in a type and its super types. First the super class is examined and then the implemented interfaces.
     * With generics it is possible that 2 methods in the same type are overidden at the same time. In that case, the first overridden method found is returned.
     *    @param type The type to find methods in
     * @param overriding The overriding method
     * @return The first overridden method or <code>null</code> if no method is overridden
     * @throws JavaModelException
     */
    public IMethod findOverriddenMethodInHierarchy(final IType type, final IMethod overriding)
            throws JavaModelException {
        final IMethod method = findOverriddenMethodInType(type, overriding);
        if (method != null) {
            return method;
        }
        final IType superClass = hierarhySupport.getSuperclass(type);
        if (superClass != null) {
            //if (superClass.getResource()==null) return null;
            final IMethod res = findOverriddenMethodInHierarchy(superClass, overriding);
            if (res != null) {
                return res;
            }
        }
        if (!overriding.isConstructor()) {
            final IType[] superInterfaces = hierarhySupport.getSuperInterfaces(type);
            for (int i = 0; i < superInterfaces.length; i++) {
                //if (superInterfaces[i].getResource()==null) continue;
                final IMethod res = findOverriddenMethodInHierarchy(superInterfaces[i], overriding);
                if (res != null) {
                    return res;
                }
            }
        }
        return method;
    }

    /**
     * Finds an overridden method in a type. WWith generics it is possible that 2 methods in the same type are overidden at the same time.
     * In that case the first overridden method found is returned.
     * @param overriddenType The type to find methods in
     * @param overriding The overriding method
     * @return The first overridden method or <code>null</code> if no method is overridden
     * @throws JavaModelException
     */
    public IMethod findOverriddenMethodInType(final IType overriddenType, final IMethod overriding)
            throws JavaModelException {
        final IMethod[] overriddenMethods = overriddenType.getMethods();
        for (int i = 0; i < overriddenMethods.length; i++) {
            if (isSubsignature(overriding, overriddenMethods[i])) {
                return overriddenMethods[i];
            }
        }
        return null;
    }

    /**
     * Finds an overriding method in a type.
     * @param overridingType The type to find methods in
     * @param overridden The overridden method
     * @return The overriding method or <code>null</code> if no method is overriding.
     * @throws JavaModelException
     */
    public IMethod findOverridingMethodInType(final IType overridingType, final IMethod overridden)
            throws JavaModelException {
        final IMethod[] overridingMethods = overridingType.getMethods();
        for (int i = 0; i < overridingMethods.length; i++) {
            if (isSubsignature(overridingMethods[i], overridden)) {
                return overridingMethods[i];
            }
        }
        return null;
    }

    /**
     * Tests if a method is a subsignature of another method.
     * @param overriding overriding method (m1)
     * @param overridden overridden method (m2)
     * @return <code>true</code> iff the method <code>m1</code> is a subsignature of the method <code>m2</code>.
     *       This is one of the requirements for m1 to override m2.
     *       Accessibility and return types are not taken into account.
     *       Note that subsignature is <em>not</em> symmetric!
     * @throws JavaModelException
     */
    public boolean isSubsignature(final IMethod overriding, final IMethod overridden) throws JavaModelException {
        if (!overridden.getElementName().equals(overriding.getElementName())) {
            return false;
        }
        final int nParameters = overridden.getNumberOfParameters();
        if (nParameters != overriding.getNumberOfParameters()) {
            return false;
        }

        if (!hasCompatibleTypeParameters(overriding, overridden)) {
            return false;
        }

        return nParameters == 0 || hasCompatibleParameterTypes(overriding, overridden);
    }

    private boolean hasCompatibleTypeParameters(final IMethod overriding, final IMethod overridden)
            throws JavaModelException {
        final ITypeParameter[] overriddenTypeParameters = overridden.getTypeParameters();
        final ITypeParameter[] overridingTypeParameters = overriding.getTypeParameters();
        final int nOverridingTypeParameters = overridingTypeParameters.length;
        if (overriddenTypeParameters.length != nOverridingTypeParameters) {
            return nOverridingTypeParameters == 0;
        }
        final Substitutions overriddenSubst = getMethodSubstitions(overridden);
        final Substitutions overridingSubst = getMethodSubstitions(overriding);
        for (int i = 0; i < nOverridingTypeParameters; i++) {
            final String erasure1 = overriddenSubst.getErasure(overriddenTypeParameters[i].getElementName());
            final String erasure2 = overridingSubst.getErasure(overridingTypeParameters[i].getElementName());
            if (erasure1 == null || !erasure1.equals(erasure2)) {
                return false;
            }
            // comparing only the erasure is not really correct: Need to compare all bounds, that can be in different order
            final int nBounds = overriddenTypeParameters[i].getBounds().length;
            if (nBounds > 1 && nBounds != overridingTypeParameters[i].getBounds().length) {
                return false;
            }
        }
        return true;
    }

    private boolean hasCompatibleParameterTypes(final IMethod overriding, final IMethod overridden)
            throws JavaModelException {
        final String[] overriddenParamTypes = overridden.getParameterTypes();
        final String[] overridingParamTypes = overriding.getParameterTypes();

        final String[] substitutedOverriding = new String[overridingParamTypes.length];
        boolean testErasure = false;

        for (int i = 0; i < overridingParamTypes.length; i++) {
            final String overriddenParamSig = overriddenParamTypes[i];
            final String overriddenParamName = getSubstitutedTypeName(overriddenParamSig, overridden);
            final String overridingParamName = getSubstitutedTypeName(overridingParamTypes[i], overriding);
            substitutedOverriding[i] = overridingParamName;
            if (!overriddenParamName.equals(overridingParamName)) {
                testErasure = true;
                break;
            }
        }
        if (testErasure) {
            for (int i = 0; i < overridingParamTypes.length; i++) {
                final String overriddenParamSig = overriddenParamTypes[i];
                final String overriddenParamName = getErasedTypeName(overriddenParamSig, overridden);
                String overridingParamName = substitutedOverriding[i];
                if (overridingParamName == null)
                    overridingParamName = getSubstitutedTypeName(overridingParamTypes[i], overriding);
                if (!overriddenParamName.equals(overridingParamName)) {
                    return false;
                }
            }
        }
        return true;
    }

    private String getVariableSubstitution(final IMember context, final String variableName)
            throws JavaModelException {
        IType type;
        if (context instanceof IMethod) {
            final String subst = getMethodSubstitions((IMethod) context).getSubstitution(variableName);
            if (subst != null) {
                return subst;
            }
            type = context.getDeclaringType();
        } else {
            type = (IType) context;
        }
        final String subst = getTypeSubstitions(type).getSubstitution(variableName);
        if (subst != null) {
            return subst;
        }
        return variableName; // not a type variable
    }

    private String getVariableErasure(final IMember context, final String variableName) throws JavaModelException {
        IType type;
        if (context instanceof IMethod) {
            final String subst = getMethodSubstitions((IMethod) context).getErasure(variableName);
            if (subst != null) {
                return subst;
            }
            type = context.getDeclaringType();
        } else {
            type = (IType) context;
        }
        final String subst = getTypeSubstitions(type).getErasure(variableName);
        if (subst != null) {
            return subst;
        }
        return variableName; // not a type variable
    }

    /*
     * Returns the substitutions for a method's type parameters
     */
    private Substitutions getMethodSubstitions(final IMethod method) throws JavaModelException {
        if (fMethodSubstitutions == null) {
            fMethodSubstitutions = new LRUMap<IMethod, Substitutions>(3);
        }

        Substitutions s = fMethodSubstitutions.get(method);
        if (s == null) {
            final ITypeParameter[] typeParameters = method.getTypeParameters();
            if (typeParameters.length == 0) {
                s = Substitutions.EMPTY_SUBST;
            } else {
                final IType instantiatedType = method.getDeclaringType();
                s = new Substitutions();
                for (int i = 0; i < typeParameters.length; i++) {
                    final ITypeParameter curr = typeParameters[i];
                    s.addSubstitution(curr.getElementName(), '+' + String.valueOf(i),
                            getTypeParameterErasure(curr, instantiatedType));
                }
            }
            fMethodSubstitutions.put(method, s);
        }
        return s;
    }

    /*
     * Returns the substitutions for a type's type parameters
     */
    private Substitutions getTypeSubstitions(final IType type) throws JavaModelException {
        if (fTypeVariableSubstitutions == null) {
            fTypeVariableSubstitutions = new HashMap<IType, Substitutions>();
            computeSubstitutions(fFocusType, null, null);
        }
        final Substitutions subst = fTypeVariableSubstitutions.get(type);
        if (subst == null) {
            return Substitutions.EMPTY_SUBST;
        }
        return subst;
    }

    private void computeSubstitutions(final IType instantiatedType, final IType instantiatingType,
            final String[] typeArguments) throws JavaModelException {
        final Substitutions s = new Substitutions();
        fTypeVariableSubstitutions.put(instantiatedType, s);

        final ITypeParameter[] typeParameters = instantiatedType.getTypeParameters();

        if (instantiatingType == null) { // the focus type
            for (int i = 0; i < typeParameters.length; i++) {
                final ITypeParameter curr = typeParameters[i];
                // use star to make type variables different from type refs
                s.addSubstitution(curr.getElementName(), '*' + curr.getElementName(),
                        getTypeParameterErasure(curr, instantiatedType));
            }
        } else {
            if (typeParameters.length == typeArguments.length) {
                for (int i = 0; i < typeParameters.length; i++) {
                    final ITypeParameter curr = typeParameters[i];
                    final String substString = getSubstitutedTypeName(typeArguments[i], instantiatingType); // substitute in the context of the instantiatingType
                    final String erasure = getErasedTypeName(typeArguments[i], instantiatingType); // get the erasure from the type argument
                    s.addSubstitution(curr.getElementName(), substString, erasure);
                }
            } else if (typeArguments.length == 0) { // raw type reference
                for (int i = 0; i < typeParameters.length; i++) {
                    final ITypeParameter curr = typeParameters[i];
                    final String erasure = getTypeParameterErasure(curr, instantiatedType);
                    s.addSubstitution(curr.getElementName(), erasure, erasure);
                }
            } else {
                // code with errors
            }
        }
        final String superclassTypeSignature = instantiatedType.getSuperclassTypeSignature();
        if (superclassTypeSignature != null) {
            final String[] superTypeArguments = Signature.getTypeArguments(superclassTypeSignature);
            final IType superclass = hierarhySupport.getSuperclass(instantiatedType);
            if (superclass != null && !fTypeVariableSubstitutions.containsKey(superclass)) {
                computeSubstitutions(superclass, instantiatedType, superTypeArguments);
            }
        }
        final String[] superInterfacesTypeSignature = instantiatedType.getSuperInterfaceTypeSignatures();
        final int nInterfaces = superInterfacesTypeSignature.length;
        if (nInterfaces > 0) {
            final IType[] superInterfaces = hierarhySupport.getSuperInterfaces(instantiatedType);
            if (superInterfaces.length == nInterfaces) {
                for (int i = 0; i < nInterfaces; i++) {
                    final String[] superTypeArguments = Signature.getTypeArguments(superInterfacesTypeSignature[i]);
                    final IType superInterface = superInterfaces[i];
                    if (!fTypeVariableSubstitutions.containsKey(superInterface)) {
                        computeSubstitutions(superInterface, instantiatedType, superTypeArguments);
                    }
                }
            }
        }
    }

    private String getTypeParameterErasure(final ITypeParameter typeParameter, final IType context)
            throws JavaModelException {
        final String[] bounds = typeParameter.getBounds();
        if (bounds.length > 0) {
            return getSubstitutedTypeName(Signature.createTypeSignature(bounds[0], false), context);
        }
        return "Object"; //$NON-NLS-1$
    }

    /**
     * Translates the type signature to a 'normalized' type name where all variables are substituted for the given type or method context.
     * The returned name contains only simple names and can be used to compare against other substituted type names
     * @param typeSig The type signature to translate
     * @param context The context for the substitution
     * @return a type name
     * @throws JavaModelException
     */
    private String getSubstitutedTypeName(final String typeSig, final IMember context) throws JavaModelException {
        return internalGetSubstitutedTypeName(typeSig, context, false, new StringBuffer()).toString();
    }

    private String getErasedTypeName(final String typeSig, final IMember context) throws JavaModelException {
        return internalGetSubstitutedTypeName(typeSig, context, true, new StringBuffer()).toString();
    }

    private StringBuffer internalGetSubstitutedTypeName(final String typeSig, final IMember context,
            final boolean erasure, final StringBuffer buf) throws JavaModelException {
        final int sigKind = Signature.getTypeSignatureKind(typeSig);
        switch (sigKind) {
        case Signature.BASE_TYPE_SIGNATURE:
            return buf.append(Signature.toString(typeSig));
        case Signature.ARRAY_TYPE_SIGNATURE:
            internalGetSubstitutedTypeName(Signature.getElementType(typeSig), context, erasure, buf);
            for (int i = Signature.getArrayCount(typeSig); i > 0; i--) {
                buf.append('[').append(']');
            }
            return buf;
        case Signature.CLASS_TYPE_SIGNATURE: {
            final String erasureSig = Signature.getTypeErasure(typeSig);
            final String erasureName = Signature.getSimpleName(Signature.toString(erasureSig));

            final char ch = erasureSig.charAt(0);
            if (ch == Signature.C_RESOLVED) {
                buf.append(erasureName);
            } else if (ch == Signature.C_UNRESOLVED) { // could be a type variable
                if (erasure) {
                    buf.append(getVariableErasure(context, erasureName));
                } else {
                    buf.append(getVariableSubstitution(context, erasureName));
                }
            } else {
                Assert.isTrue(false, "Unknown class type signature"); //$NON-NLS-1$
            }
            if (!erasure) {
                final String[] typeArguments = Signature.getTypeArguments(typeSig);
                if (typeArguments.length > 0) {
                    buf.append('<');
                    for (int i = 0; i < typeArguments.length; i++) {
                        if (i > 0) {
                            buf.append(',');
                        }
                        internalGetSubstitutedTypeName(typeArguments[i], context, erasure, buf);
                    }
                    buf.append('>');
                }
            }
            return buf;
        }
        case Signature.TYPE_VARIABLE_SIGNATURE:
            final String varName = Signature.toString(typeSig);
            if (erasure) {
                return buf.append(getVariableErasure(context, varName));
            } else {
                return buf.append(getVariableSubstitution(context, varName));
            }
        case Signature.WILDCARD_TYPE_SIGNATURE: {
            buf.append('?');
            final char ch = typeSig.charAt(0);
            if (ch == Signature.C_STAR) {
                return buf;
            } else if (ch == Signature.C_EXTENDS) {
                buf.append(" extends "); //$NON-NLS-1$
            } else {
                buf.append(" super "); //$NON-NLS-1$
            }
            return internalGetSubstitutedTypeName(typeSig.substring(1), context, erasure, buf);
        }
        case Signature.CAPTURE_TYPE_SIGNATURE:
            return internalGetSubstitutedTypeName(typeSig.substring(1), context, erasure, buf);
        default:
            Assert.isTrue(false, "Unhandled type signature kind"); //$NON-NLS-1$
            return buf;
        }
    }

    /**
     * Evaluates if a member in the focus' element hierarchy is visible from
     * elements in a package.
     * @param member The member to test the visibility for
     * @param pack The package of the focus element focus
     * @return returns <code>true</code> if the member is visible from the package
     * @throws JavaModelException thrown when the member can not be accessed
     */
    public static boolean isVisibleInHierarchy(final IMember member, final IPackageFragment pack)
            throws JavaModelException {
        final int type = member.getElementType();
        if (type == IJavaElement.INITIALIZER
                || (type == IJavaElement.METHOD && member.getElementName().startsWith("<"))) { //$NON-NLS-1$
            return false;
        }

        final int otherflags = member.getFlags();

        final IType declaringType = member.getDeclaringType();
        if (Flags.isPublic(otherflags) || Flags.isProtected(otherflags)
                || (declaringType != null && declaringType.isInterface())) {
            return true;
        } else if (Flags.isPrivate(otherflags)) {
            return false;
        }

        final IPackageFragment otherpack = (IPackageFragment) member.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
        return (pack != null && pack.equals(otherpack));
    }

}