1   package com.stateofflow.eclipse.metrics.bindings;
2   
3   import java.util.Collections;
4   import java.util.HashSet;
5   import java.util.Set;
6   
7   import org.eclipse.core.runtime.Assert;
8   import org.eclipse.jdt.core.dom.IMethodBinding;
9   import org.eclipse.jdt.core.dom.ITypeBinding;
10  import org.eclipse.jdt.core.dom.Modifier;
11  
12  public class Bindings {
13      private static IMethodBinding findDeclaredMethodInInterfaceHierarchy(final ITypeBinding type, final IMethodBinding overriding) {
14          final ITypeBinding[] interfaces = type.getInterfaces();
15          for (final ITypeBinding interface1 : interfaces) {
16              final IMethodBinding res = findOverriddenMethodInHierarchy(interface1, overriding);
17              if (res != null) {
18                  return res;
19              }
20          }
21          return null;
22      }
23  
24      public static IMethodBinding findOverriddenMethod(final IMethodBinding overriding) {
25          if (isStaticallyBindable(overriding)) {
26              return null;
27          }
28  
29          final IMethodBinding overridden = findOverriddenMethodInSuperclassHeirarchy(overriding);
30          return overridden != null ? overridden : findDeclaredMethodInInterfaceHierarchy(overriding.getDeclaringClass(), overriding);
31      }
32  
33      private static boolean isStaticallyBindable(final IMethodBinding overriding) {
34          final int modifiers = overriding.getModifiers();
35          return Modifier.isPrivate(modifiers) || Modifier.isStatic(modifiers) || overriding.isConstructor();
36      }
37  
38      private static IMethodBinding findOverriddenMethodInHierarchy(final ITypeBinding type, final IMethodBinding binding) {
39          IMethodBinding method = findOverriddenMethodInType(type, binding);
40          if (method != null) {
41              return method;
42          }
43          final ITypeBinding superClass = type.getSuperclass();
44          if (superClass != null) {
45              method = findOverriddenMethodInHierarchy(superClass, binding);
46              if (method != null) {
47                  return method;
48              }
49          }
50          return findDeclaredMethodInInterfaceHierarchy(type, binding);
51      }
52  
53      private static IMethodBinding findOverriddenMethodInSuperclassHeirarchy(final IMethodBinding overriding) {
54          final ITypeBinding type = overriding.getDeclaringClass();
55          if (type.getSuperclass() != null) {
56              final IMethodBinding res = findOverriddenMethodInHierarchy(type.getSuperclass(), overriding);
57              if (res != null && !Modifier.isPrivate(res.getModifiers())) {
58                  if (VisibilityAnalyser.isVisibleInHierarchy(res, overriding.getDeclaringClass().getPackage())) {
59                      return res;
60                  }
61              }
62          }
63  
64          return null;
65      }
66  
67      private static IMethodBinding findOverriddenMethodInType(final ITypeBinding type, final IMethodBinding method) {
68          final IMethodBinding[] methods = type.getDeclaredMethods();
69          for (final IMethodBinding method2 : methods) {
70              if (isSubsignature(method, method2)) {
71                  return method2;
72              }
73          }
74          return null;
75      }
76  
77      private static Set<ITypeBinding> getNonTrivialNormalisedTypeBounds(final ITypeBinding[] typeBounds) {
78          final Set<ITypeBinding> result = new HashSet<ITypeBinding>(typeBounds.length);
79          for (final ITypeBinding bound : typeBounds) {
80              result.add(normaliseTypeBound(bound));
81          }
82          return result;
83      }
84  
85      private static Set<ITypeBinding> getNormalisedTypeBounds(final ITypeBinding[] typeBounds) {
86          return typeBounds.length == 0 || "java.lang.Object".equals(typeBounds[0].getQualifiedName()) ? Collections.<ITypeBinding> emptySet() : getNonTrivialNormalisedTypeBounds(typeBounds);
87      }
88  
89      private static Set<ITypeBinding> getTypeBoundsForSubsignature(final ITypeBinding typeParameter) {
90          final ITypeBinding[] typeBounds = typeParameter.getTypeBounds();
91          final int count = typeBounds.length;
92          if (count == 0) {
93              return Collections.<ITypeBinding> emptySet();
94          }
95  
96          return getNormalisedTypeBounds(typeBounds);
97      }
98  
99      private static boolean isSubsignature(final IMethodBinding overriding, final IMethodBinding overridden) {
100         if (!overriding.getName().equals(overridden.getName())) {
101             return false;
102         }
103 
104         final ITypeBinding[] m1Params = overriding.getParameterTypes();
105         final ITypeBinding[] m2Params = overridden.getParameterTypes();
106         if (m1Params.length != m2Params.length) {
107             return false;
108         }
109 
110         final ITypeBinding[] m1TypeParams = overriding.getTypeParameters();
111         final ITypeBinding[] m2TypeParams = overridden.getTypeParameters();
112         return m1TypeParams.length == m2TypeParams.length && areAllTypeParametersBoundsEqual(m1TypeParams, m2TypeParams) && equalsErased(m1Params, m2Params);
113     }
114 
115     private static boolean areAllTypeParametersBoundsEqual(final ITypeBinding[] m1TypeParams, final ITypeBinding[] m2TypeParams) {
116         Assert.isTrue(m1TypeParams.length == m2TypeParams.length);
117 
118         for (int i = 0; i < m1TypeParams.length; i++) {
119             if (!getTypeBoundsForSubsignature(m1TypeParams[i]).equals(getTypeBoundsForSubsignature(m2TypeParams[i]))) {
120                 return false;
121             }
122         }
123 
124         return true;
125     }
126 
127     private static boolean equalsErased(final ITypeBinding[] m1Params, final ITypeBinding[] m2Params) {
128         if (Equality.equals(m1Params, m2Params)) {
129             return true;
130         }
131         for (int i = 0; i < m1Params.length; i++) {
132             if (!(Equality.equals(normaliseTypeBound(m1Params[i]), m2Params[i].getErasure()))) {
133                 return false;
134             }
135         }
136         return true;
137     }
138 
139     private static ITypeBinding normaliseTypeBound(final ITypeBinding bound) {
140         if (TypeVariableAnalyser.containsTypeVariables(bound)) {
141             return bound.getErasure(); // try to achieve effect of "rename type variables"
142         }
143         if (bound.isRawType()) {
144             return bound.getTypeDeclaration();
145         }
146         return bound;
147     }
148 }
149