001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.corext.util;
011:
012: import java.util.HashMap;
013: import java.util.Map;
014:
015: import org.eclipse.core.runtime.Assert;
016:
017: import org.eclipse.jdt.core.Flags;
018: import org.eclipse.jdt.core.IMember;
019: import org.eclipse.jdt.core.IMethod;
020: import org.eclipse.jdt.core.IType;
021: import org.eclipse.jdt.core.ITypeHierarchy;
022: import org.eclipse.jdt.core.ITypeParameter;
023: import org.eclipse.jdt.core.JavaModelException;
024: import org.eclipse.jdt.core.Signature;
025:
026: public class MethodOverrideTester {
027: private static class Substitutions {
028:
029: public static final Substitutions EMPTY_SUBST = new Substitutions();
030:
031: private HashMap fMap;
032:
033: public Substitutions() {
034: fMap = null;
035: }
036:
037: public void addSubstitution(String typeVariable,
038: String substitution, String erasure) {
039: if (fMap == null) {
040: fMap = new HashMap(3);
041: }
042: fMap.put(typeVariable,
043: new String[] { substitution, erasure });
044: }
045:
046: private String[] getSubstArray(String typeVariable) {
047: if (fMap != null) {
048: return (String[]) fMap.get(typeVariable);
049: }
050: return null;
051: }
052:
053: public String getSubstitution(String typeVariable) {
054: String[] subst = getSubstArray(typeVariable);
055: if (subst != null) {
056: return subst[0];
057: }
058: return null;
059: }
060:
061: public String getErasure(String typeVariable) {
062: String[] subst = getSubstArray(typeVariable);
063: if (subst != null) {
064: return subst[1];
065: }
066: return null;
067: }
068: }
069:
070: private final IType fFocusType;
071: private final ITypeHierarchy fHierarchy;
072:
073: private Map /* <IMethod, Substitutions> */fMethodSubstitutions;
074: private Map /* <IType, Substitutions> */fTypeVariableSubstitutions;
075:
076: public MethodOverrideTester(IType focusType,
077: ITypeHierarchy hierarchy) {
078: if (focusType == null || hierarchy == null) {
079: throw new IllegalArgumentException();
080: }
081: fFocusType = focusType;
082: fHierarchy = hierarchy;
083: fTypeVariableSubstitutions = null;
084: fMethodSubstitutions = null;
085: }
086:
087: public IType getFocusType() {
088: return fFocusType;
089: }
090:
091: public ITypeHierarchy getTypeHierarchy() {
092: return fHierarchy;
093: }
094:
095: /**
096: * Finds the method that declares the given method. A declaring method is the 'original' method declaration that does
097: * not override nor implement a method. <code>null</code> is returned it the given method does not override
098: * a method. When searching, super class are examined before implemented interfaces.
099: * @param testVisibility If true the result is tested on visibility. Null is returned if the method is not visible.
100: * @throws JavaModelException
101: */
102: public IMethod findDeclaringMethod(IMethod overriding,
103: boolean testVisibility) throws JavaModelException {
104: IMethod result = null;
105: IMethod overridden = findOverriddenMethod(overriding,
106: testVisibility);
107: while (overridden != null) {
108: result = overridden;
109: overridden = findOverriddenMethod(result, testVisibility);
110: }
111: return result;
112: }
113:
114: /**
115: * Finds the method that is overridden by the given method.
116: * First the super class is examined and then the implemented interfaces.
117: * @param testVisibility If true the result is tested on visibility. Null is returned if the method is not visible.
118: * @throws JavaModelException
119: */
120: public IMethod findOverriddenMethod(IMethod overriding,
121: boolean testVisibility) throws JavaModelException {
122: int flags = overriding.getFlags();
123: if (Flags.isPrivate(flags) || Flags.isStatic(flags)
124: || overriding.isConstructor()) {
125: return null;
126: }
127:
128: IType type = overriding.getDeclaringType();
129: IType super Class = fHierarchy.getSuperclass(type);
130: if (super Class != null) {
131: IMethod res = findOverriddenMethodInHierarchy(super Class,
132: overriding);
133: if (res != null && !Flags.isPrivate(res.getFlags())) {
134: if (!testVisibility
135: || JavaModelUtil.isVisibleInHierarchy(res, type
136: .getPackageFragment())) {
137: return res;
138: }
139: }
140: }
141: if (!overriding.isConstructor()) {
142: IType[] interfaces = fHierarchy.getSuperInterfaces(type);
143: for (int i = 0; i < interfaces.length; i++) {
144: IMethod res = findOverriddenMethodInHierarchy(
145: interfaces[i], overriding);
146: if (res != null) {
147: return res; // methods from interfaces are always public and therefore visible
148: }
149: }
150: }
151: return null;
152: }
153:
154: /**
155: * Finds the directly overridden method in a type and its super types. First the super class is examined and then the implemented interfaces.
156: * 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.
157: * @param type The type to find methods in
158: * @param overriding The overriding method
159: * @return The first overridden method or <code>null</code> if no method is overridden
160: * @throws JavaModelException
161: */
162: public IMethod findOverriddenMethodInHierarchy(IType type,
163: IMethod overriding) throws JavaModelException {
164: IMethod method = findOverriddenMethodInType(type, overriding);
165: if (method != null) {
166: return method;
167: }
168: IType super Class = fHierarchy.getSuperclass(type);
169: if (super Class != null) {
170: IMethod res = findOverriddenMethodInHierarchy(super Class,
171: overriding);
172: if (res != null) {
173: return res;
174: }
175: }
176: if (!overriding.isConstructor()) {
177: IType[] super Interfaces = fHierarchy
178: .getSuperInterfaces(type);
179: for (int i = 0; i < super Interfaces.length; i++) {
180: IMethod res = findOverriddenMethodInHierarchy(
181: super Interfaces[i], overriding);
182: if (res != null) {
183: return res;
184: }
185: }
186: }
187: return method;
188: }
189:
190: /**
191: * 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.
192: * In that case the first overridden method found is returned.
193: * @param overriddenType The type to find methods in
194: * @param overriding The overriding method
195: * @return The first overridden method or <code>null</code> if no method is overridden
196: * @throws JavaModelException
197: */
198: public IMethod findOverriddenMethodInType(IType overriddenType,
199: IMethod overriding) throws JavaModelException {
200: IMethod[] overriddenMethods = overriddenType.getMethods();
201: for (int i = 0; i < overriddenMethods.length; i++) {
202: if (isSubsignature(overriding, overriddenMethods[i])) {
203: return overriddenMethods[i];
204: }
205: }
206: return null;
207: }
208:
209: /**
210: * Finds an overriding method in a type.
211: * @param overridingType The type to find methods in
212: * @param overridden The overridden method
213: * @return The overriding method or <code>null</code> if no method is overriding.
214: * @throws JavaModelException
215: */
216: public IMethod findOverridingMethodInType(IType overridingType,
217: IMethod overridden) throws JavaModelException {
218: IMethod[] overridingMethods = overridingType.getMethods();
219: for (int i = 0; i < overridingMethods.length; i++) {
220: if (isSubsignature(overridingMethods[i], overridden)) {
221: return overridingMethods[i];
222: }
223: }
224: return null;
225: }
226:
227: /**
228: * Tests if a method is a subsignature of another method.
229: * @param overriding overriding method (m1)
230: * @param overridden overridden method (m2)
231: * @return <code>true</code> iff the method <code>m1</code> is a subsignature of the method <code>m2</code>.
232: * This is one of the requirements for m1 to override m2.
233: * Accessibility and return types are not taken into account.
234: * Note that subsignature is <em>not</em> symmetric!
235: * @throws JavaModelException
236: */
237: public boolean isSubsignature(IMethod overriding, IMethod overridden)
238: throws JavaModelException {
239: if (!overridden.getElementName().equals(
240: overriding.getElementName())) {
241: return false;
242: }
243: int nParameters = overridden.getNumberOfParameters();
244: if (nParameters != overriding.getNumberOfParameters()) {
245: return false;
246: }
247:
248: if (!hasCompatibleTypeParameters(overriding, overridden)) {
249: return false;
250: }
251:
252: return nParameters == 0
253: || hasCompatibleParameterTypes(overriding, overridden);
254: }
255:
256: private boolean hasCompatibleTypeParameters(IMethod overriding,
257: IMethod overridden) throws JavaModelException {
258: ITypeParameter[] overriddenTypeParameters = overridden
259: .getTypeParameters();
260: ITypeParameter[] overridingTypeParameters = overriding
261: .getTypeParameters();
262: int nOverridingTypeParameters = overridingTypeParameters.length;
263: if (overriddenTypeParameters.length != nOverridingTypeParameters) {
264: return nOverridingTypeParameters == 0;
265: }
266: Substitutions overriddenSubst = getMethodSubstitions(overridden);
267: Substitutions overridingSubst = getMethodSubstitions(overriding);
268: for (int i = 0; i < nOverridingTypeParameters; i++) {
269: String erasure1 = overriddenSubst
270: .getErasure(overriddenTypeParameters[i]
271: .getElementName());
272: String erasure2 = overridingSubst
273: .getErasure(overridingTypeParameters[i]
274: .getElementName());
275: if (erasure1 == null || !erasure1.equals(erasure2)) {
276: return false;
277: }
278: // comparing only the erasure is not really correct: Need to compare all bounds, that can be in different order
279: int nBounds = overriddenTypeParameters[i].getBounds().length;
280: if (nBounds > 1
281: && nBounds != overridingTypeParameters[i]
282: .getBounds().length) {
283: return false;
284: }
285: }
286: return true;
287: }
288:
289: private boolean hasCompatibleParameterTypes(IMethod overriding,
290: IMethod overridden) throws JavaModelException {
291: String[] overriddenParamTypes = overridden.getParameterTypes();
292: String[] overridingParamTypes = overriding.getParameterTypes();
293:
294: String[] substitutedOverriding = new String[overridingParamTypes.length];
295: boolean testErasure = false;
296:
297: for (int i = 0; i < overridingParamTypes.length; i++) {
298: String overriddenParamSig = overriddenParamTypes[i];
299: String overriddenParamName = getSubstitutedTypeName(
300: overriddenParamSig, overridden);
301: String overridingParamName = getSubstitutedTypeName(
302: overridingParamTypes[i], overriding);
303: substitutedOverriding[i] = overridingParamName;
304: if (!overriddenParamName.equals(overridingParamName)) {
305: testErasure = true;
306: break;
307: }
308: }
309: if (testErasure) {
310: for (int i = 0; i < overridingParamTypes.length; i++) {
311: String overriddenParamSig = overriddenParamTypes[i];
312: String overriddenParamName = getErasedTypeName(
313: overriddenParamSig, overridden);
314: String overridingParamName = substitutedOverriding[i];
315: if (overridingParamName == null)
316: overridingParamName = getSubstitutedTypeName(
317: overridingParamTypes[i], overriding);
318: if (!overriddenParamName.equals(overridingParamName)) {
319: return false;
320: }
321: }
322: }
323: return true;
324: }
325:
326: private String getVariableSubstitution(IMember context,
327: String variableName) throws JavaModelException {
328: IType type;
329: if (context instanceof IMethod) {
330: String subst = getMethodSubstitions((IMethod) context)
331: .getSubstitution(variableName);
332: if (subst != null) {
333: return subst;
334: }
335: type = context.getDeclaringType();
336: } else {
337: type = (IType) context;
338: }
339: String subst = getTypeSubstitions(type).getSubstitution(
340: variableName);
341: if (subst != null) {
342: return subst;
343: }
344: return variableName; // not a type variable
345: }
346:
347: private String getVariableErasure(IMember context,
348: String variableName) throws JavaModelException {
349: IType type;
350: if (context instanceof IMethod) {
351: String subst = getMethodSubstitions((IMethod) context)
352: .getErasure(variableName);
353: if (subst != null) {
354: return subst;
355: }
356: type = context.getDeclaringType();
357: } else {
358: type = (IType) context;
359: }
360: String subst = getTypeSubstitions(type)
361: .getErasure(variableName);
362: if (subst != null) {
363: return subst;
364: }
365: return variableName; // not a type variable
366: }
367:
368: /*
369: * Returns the substitutions for a method's type parameters
370: */
371: private Substitutions getMethodSubstitions(IMethod method)
372: throws JavaModelException {
373: if (fMethodSubstitutions == null) {
374: fMethodSubstitutions = new LRUMap(3);
375: }
376:
377: Substitutions s = (Substitutions) fMethodSubstitutions
378: .get(method);
379: if (s == null) {
380: ITypeParameter[] typeParameters = method
381: .getTypeParameters();
382: if (typeParameters.length == 0) {
383: s = Substitutions.EMPTY_SUBST;
384: } else {
385: IType instantiatedType = method.getDeclaringType();
386: s = new Substitutions();
387: for (int i = 0; i < typeParameters.length; i++) {
388: ITypeParameter curr = typeParameters[i];
389: s.addSubstitution(curr.getElementName(),
390: '+' + String.valueOf(i),
391: getTypeParameterErasure(curr,
392: instantiatedType));
393: }
394: }
395: fMethodSubstitutions.put(method, s);
396: }
397: return s;
398: }
399:
400: /*
401: * Returns the substitutions for a type's type parameters
402: */
403: private Substitutions getTypeSubstitions(IType type)
404: throws JavaModelException {
405: if (fTypeVariableSubstitutions == null) {
406: fTypeVariableSubstitutions = new HashMap();
407: computeSubstitutions(fFocusType, null, null);
408: }
409: Substitutions subst = (Substitutions) fTypeVariableSubstitutions
410: .get(type);
411: if (subst == null) {
412: return Substitutions.EMPTY_SUBST;
413: }
414: return subst;
415: }
416:
417: private void computeSubstitutions(IType instantiatedType,
418: IType instantiatingType, String[] typeArguments)
419: throws JavaModelException {
420: Substitutions s = new Substitutions();
421: fTypeVariableSubstitutions.put(instantiatedType, s);
422:
423: ITypeParameter[] typeParameters = instantiatedType
424: .getTypeParameters();
425:
426: if (instantiatingType == null) { // the focus type
427: for (int i = 0; i < typeParameters.length; i++) {
428: ITypeParameter curr = typeParameters[i];
429: // use star to make type variables different from type refs
430: s.addSubstitution(curr.getElementName(), '*' + curr
431: .getElementName(), getTypeParameterErasure(
432: curr, instantiatedType));
433: }
434: } else {
435: if (typeParameters.length == typeArguments.length) {
436: for (int i = 0; i < typeParameters.length; i++) {
437: ITypeParameter curr = typeParameters[i];
438: String substString = getSubstitutedTypeName(
439: typeArguments[i], instantiatingType); // substitute in the context of the instantiatingType
440: String erasure = getErasedTypeName(
441: typeArguments[i], instantiatingType); // get the erasure from the type argument
442: s.addSubstitution(curr.getElementName(),
443: substString, erasure);
444: }
445: } else if (typeArguments.length == 0) { // raw type reference
446: for (int i = 0; i < typeParameters.length; i++) {
447: ITypeParameter curr = typeParameters[i];
448: String erasure = getTypeParameterErasure(curr,
449: instantiatedType);
450: s.addSubstitution(curr.getElementName(), erasure,
451: erasure);
452: }
453: } else {
454: // code with errors
455: }
456: }
457: String super classTypeSignature = instantiatedType
458: .getSuperclassTypeSignature();
459: if (super classTypeSignature != null) {
460: String[] super TypeArguments = Signature
461: .getTypeArguments(super classTypeSignature);
462: IType super class = fHierarchy
463: .getSuperclass(instantiatedType);
464: if (super class != null
465: && !fTypeVariableSubstitutions
466: .containsKey(super class)) {
467: computeSubstitutions(super class, instantiatedType,
468: super TypeArguments);
469: }
470: }
471: String[] super InterfacesTypeSignature = instantiatedType
472: .getSuperInterfaceTypeSignatures();
473: int nInterfaces = super InterfacesTypeSignature.length;
474: if (nInterfaces > 0) {
475: IType[] super Interfaces = fHierarchy
476: .getSuperInterfaces(instantiatedType);
477: if (super Interfaces.length == nInterfaces) {
478: for (int i = 0; i < nInterfaces; i++) {
479: String[] super TypeArguments = Signature
480: .getTypeArguments(super InterfacesTypeSignature[i]);
481: IType super Interface = super Interfaces[i];
482: if (!fTypeVariableSubstitutions
483: .containsKey(super Interface)) {
484: computeSubstitutions(super Interface,
485: instantiatedType, super TypeArguments);
486: }
487: }
488: }
489: }
490: }
491:
492: private String getTypeParameterErasure(
493: ITypeParameter typeParameter, IType context)
494: throws JavaModelException {
495: String[] bounds = typeParameter.getBounds();
496: if (bounds.length > 0) {
497: return getSubstitutedTypeName(Signature
498: .createTypeSignature(bounds[0], false), context);
499: }
500: return "Object"; //$NON-NLS-1$
501: }
502:
503: /**
504: * Translates the type signature to a 'normalized' type name where all variables are substituted for the given type or method context.
505: * The returned name contains only simple names and can be used to compare against other substituted type names
506: * @param typeSig The type signature to translate
507: * @param context The context for the substitution
508: * @return a type name
509: * @throws JavaModelException
510: */
511: private String getSubstitutedTypeName(String typeSig,
512: IMember context) throws JavaModelException {
513: return internalGetSubstitutedTypeName(typeSig, context, false,
514: new StringBuffer()).toString();
515: }
516:
517: private String getErasedTypeName(String typeSig, IMember context)
518: throws JavaModelException {
519: return internalGetSubstitutedTypeName(typeSig, context, true,
520: new StringBuffer()).toString();
521: }
522:
523: private StringBuffer internalGetSubstitutedTypeName(String typeSig,
524: IMember context, boolean erasure, StringBuffer buf)
525: throws JavaModelException {
526: int sigKind = Signature.getTypeSignatureKind(typeSig);
527: switch (sigKind) {
528: case Signature.BASE_TYPE_SIGNATURE:
529: return buf.append(Signature.toString(typeSig));
530: case Signature.ARRAY_TYPE_SIGNATURE:
531: internalGetSubstitutedTypeName(Signature
532: .getElementType(typeSig), context, erasure, buf);
533: for (int i = Signature.getArrayCount(typeSig); i > 0; i--) {
534: buf.append('[').append(']');
535: }
536: return buf;
537: case Signature.CLASS_TYPE_SIGNATURE: {
538: String erasureSig = Signature.getTypeErasure(typeSig);
539: String erasureName = Signature.getSimpleName(Signature
540: .toString(erasureSig));
541:
542: char ch = erasureSig.charAt(0);
543: if (ch == Signature.C_RESOLVED) {
544: buf.append(erasureName);
545: } else if (ch == Signature.C_UNRESOLVED) { // could be a type variable
546: if (erasure) {
547: buf
548: .append(getVariableErasure(context,
549: erasureName));
550: } else {
551: buf.append(getVariableSubstitution(context,
552: erasureName));
553: }
554: } else {
555: Assert.isTrue(false, "Unknown class type signature"); //$NON-NLS-1$
556: }
557: if (!erasure) {
558: String[] typeArguments = Signature
559: .getTypeArguments(typeSig);
560: if (typeArguments.length > 0) {
561: buf.append('<');
562: for (int i = 0; i < typeArguments.length; i++) {
563: if (i > 0) {
564: buf.append(',');
565: }
566: internalGetSubstitutedTypeName(
567: typeArguments[i], context, erasure, buf);
568: }
569: buf.append('>');
570: }
571: }
572: return buf;
573: }
574: case Signature.TYPE_VARIABLE_SIGNATURE:
575: String varName = Signature.toString(typeSig);
576: if (erasure) {
577: return buf.append(getVariableErasure(context, varName));
578: } else {
579: return buf.append(getVariableSubstitution(context,
580: varName));
581: }
582: case Signature.WILDCARD_TYPE_SIGNATURE: {
583: buf.append('?');
584: char ch = typeSig.charAt(0);
585: if (ch == Signature.C_STAR) {
586: return buf;
587: } else if (ch == Signature.C_EXTENDS) {
588: buf.append(" extends "); //$NON-NLS-1$
589: } else {
590: buf.append(" super "); //$NON-NLS-1$
591: }
592: return internalGetSubstitutedTypeName(typeSig.substring(1),
593: context, erasure, buf);
594: }
595: case Signature.CAPTURE_TYPE_SIGNATURE:
596: return internalGetSubstitutedTypeName(typeSig.substring(1),
597: context, erasure, buf);
598: default:
599: Assert.isTrue(false, "Unhandled type signature kind"); //$NON-NLS-1$
600: return buf;
601: }
602: }
603:
604: }
|