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