/*******************************************************************************
* Copyright (c) 2000, 2009 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.eclipse.wst.jsdt.internal.corext.util;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.wst.jsdt.core.Flags;
import org.eclipse.wst.jsdt.core.IFunction;
import org.eclipse.wst.jsdt.core.IMember;
import org.eclipse.wst.jsdt.core.IType;
import org.eclipse.wst.jsdt.core.ITypeHierarchy;
import org.eclipse.wst.jsdt.core.JavaScriptModelException;
import org.eclipse.wst.jsdt.core.Signature;
public class MethodOverrideTester {
private static class Substitutions {
public static final Substitutions EMPTY_SUBST= new Substitutions();
private HashMap fMap;
public Substitutions() {
fMap= null;
}
private String[] getSubstArray(String typeVariable) {
if (fMap != null) {
return (String[]) fMap.get(typeVariable);
}
return null;
}
public String getSubstitution(String typeVariable) {
String[] subst= getSubstArray(typeVariable);
if (subst != null) {
return subst[0];
}
return null;
}
public String getErasure(String typeVariable) {
String[] subst= getSubstArray(typeVariable);
if (subst != null) {
return subst[1];
}
return null;
}
}
private final IType fFocusType;
private final ITypeHierarchy fHierarchy;
private Map /* <IFunction, Substitutions> */ fMethodSubstitutions;
private Map /* <IType, Substitutions> */ fTypeVariableSubstitutions;
public MethodOverrideTester(IType focusType, ITypeHierarchy hierarchy) {
if (focusType == null || hierarchy == null) {
throw new IllegalArgumentException();
}
fFocusType= focusType;
fHierarchy= hierarchy;
fTypeVariableSubstitutions= null;
fMethodSubstitutions= null;
}
public IType getFocusType() {
return fFocusType;
}
public ITypeHierarchy getTypeHierarchy() {
return fHierarchy;
}
/**
* 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 JavaScriptModelException
*/
public IFunction findDeclaringMethod(IFunction overriding, boolean testVisibility) throws JavaScriptModelException {
IFunction result= null;
IFunction overridden= findOverriddenMethod(overriding, testVisibility);
while (overridden != null) {
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 JavaScriptModelException
*/
public IFunction findOverriddenMethod(IFunction overriding, boolean testVisibility) throws JavaScriptModelException {
int flags= overriding.getFlags();
if (Flags.isPrivate(flags) || Flags.isStatic(flags) || overriding.isConstructor()) {
return null;
}
IType type= overriding.getDeclaringType();
if (type==null)
return null;
IType superClass= fHierarchy.getSuperclass(type);
if (superClass != null) {
IFunction res= findOverriddenMethodInHierarchy(superClass, overriding);
if (res != null && !Flags.isPrivate(res.getFlags())) {
if (!testVisibility || JavaModelUtil.isVisibleInHierarchy(res, type.getPackageFragment())) {
return res;
}
}
}
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 JavaScriptModelException
*/
public IFunction findOverriddenMethodInHierarchy(IType type, IFunction overriding) throws JavaScriptModelException {
IFunction method= findOverriddenMethodInType(type, overriding);
if (method != null) {
return method;
}
IType superClass= fHierarchy.getSuperclass(type);
if (superClass != null) {
IFunction res= findOverriddenMethodInHierarchy(superClass, 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 JavaScriptModelException
*/
public IFunction findOverriddenMethodInType(IType overriddenType, IFunction overriding) throws JavaScriptModelException {
IFunction[] overriddenMethods= overriddenType.getFunctions();
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 JavaScriptModelException
*/
public IFunction findOverridingMethodInType(IType overridingType, IFunction overridden) throws JavaScriptModelException {
IFunction[] overridingMethods= overridingType.getFunctions();
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 JavaScriptModelException
*/
public boolean isSubsignature(IFunction overriding, IFunction overridden) throws JavaScriptModelException {
if (!overridden.getElementName().equals(overriding.getElementName())) {
return false;
}
int nParameters= overridden.getNumberOfParameters();
if (nParameters != overriding.getNumberOfParameters()) {
return false;
}
return nParameters == 0 || hasCompatibleParameterTypes(overriding, overridden);
}
private boolean hasCompatibleParameterTypes(IFunction overriding, IFunction overridden) throws JavaScriptModelException {
String[] overriddenParamTypes= overridden.getParameterTypes();
String[] overridingParamTypes= overriding.getParameterTypes();
String[] substitutedOverriding= new String[overridingParamTypes.length];
boolean testErasure= false;
for (int i= 0; i < overridingParamTypes.length; i++) {
String overriddenParamSig= overriddenParamTypes[i];
String overriddenParamName= getSubstitutedTypeName(overriddenParamSig, overridden);
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++) {
String overriddenParamSig= overriddenParamTypes[i];
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(IMember context, String variableName) throws JavaScriptModelException {
IType type;
if (context instanceof IFunction) {
String subst= getMethodSubstitions((IFunction) context).getSubstitution(variableName);
if (subst != null) {
return subst;
}
type= context.getDeclaringType();
} else {
type= (IType) context;
}
String subst= getTypeSubstitions(type).getSubstitution(variableName);
if (subst != null) {
return subst;
}
return variableName; // not a type variable
}
private String getVariableErasure(IMember context, String variableName) throws JavaScriptModelException {
IType type;
if (context instanceof IFunction) {
String subst= getMethodSubstitions((IFunction) context).getErasure(variableName);
if (subst != null) {
return subst;
}
type= context.getDeclaringType();
} else {
type= (IType) context;
}
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(IFunction method) throws JavaScriptModelException {
if (fMethodSubstitutions == null) {
fMethodSubstitutions= new LRUMap(3);
}
Substitutions s= (Substitutions) fMethodSubstitutions.get(method);
if (s == null) {
s= Substitutions.EMPTY_SUBST;
fMethodSubstitutions.put(method, s);
}
return s;
}
/*
* Returns the substitutions for a type's type parameters
*/
private Substitutions getTypeSubstitions(IType type) throws JavaScriptModelException {
if (fTypeVariableSubstitutions == null) {
fTypeVariableSubstitutions= new HashMap();
computeSubstitutions(fFocusType, null, null);
}
Substitutions subst= (Substitutions) fTypeVariableSubstitutions.get(type);
if (subst == null) {
return Substitutions.EMPTY_SUBST;
}
return subst;
}
private void computeSubstitutions(IType instantiatedType, IType instantiatingType, String[] typeArguments) throws JavaScriptModelException {
Substitutions s= new Substitutions();
fTypeVariableSubstitutions.put(instantiatedType, s);
String superclassTypeSignature= instantiatedType.getSuperclassTypeSignature();
if (superclassTypeSignature != null) {
IType superclass= fHierarchy.getSuperclass(instantiatedType);
if (superclass != null && !fTypeVariableSubstitutions.containsKey(superclass)) {
computeSubstitutions(superclass, instantiatedType, new String[0]);
}
}
}
/**
* 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 JavaScriptModelException
*/
private String getSubstitutedTypeName(String typeSig, IMember context) throws JavaScriptModelException {
return internalGetSubstitutedTypeName(typeSig, context, false, new StringBuffer()).toString();
}
private String getErasedTypeName(String typeSig, IMember context) throws JavaScriptModelException {
return internalGetSubstitutedTypeName(typeSig, context, true, new StringBuffer()).toString();
}
private StringBuffer internalGetSubstitutedTypeName(String typeSig, IMember context, boolean erasure, StringBuffer buf) throws JavaScriptModelException {
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: {
String erasureSig= typeSig;
String erasureName= Signature.getSimpleName(Signature.toString(erasureSig));
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$
}
return buf;
}
default:
Assert.isTrue(false, "Unhandled type signature kind"); //$NON-NLS-1$
return buf;
}
}
}