summaryrefslogtreecommitdiff
path: root/java/java-impl/src/com/intellij/ide/hierarchy/method/MethodHierarchyTreeStructure.java
blob: 7a5e869536895d58abe7afad17fb65deaf488596 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*
 * Copyright 2000-2013 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.ide.hierarchy.method;

import com.intellij.ide.hierarchy.HierarchyBrowserManager;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.HierarchyTreeStructure;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.search.searches.FunctionalExpressionSearch;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public final class MethodHierarchyTreeStructure extends HierarchyTreeStructure {
  private final SmartPsiElementPointer myMethod;

  /**
   * Should be called in read action
   */
  public MethodHierarchyTreeStructure(final Project project, final PsiMethod method) {
    super(project, null);
    myBaseDescriptor = buildHierarchyElement(project, method);
    ((MethodHierarchyNodeDescriptor)myBaseDescriptor).setTreeStructure(this);
    myMethod = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(method);
    setBaseElement(myBaseDescriptor); //to set myRoot
  }

  private HierarchyNodeDescriptor buildHierarchyElement(final Project project, final PsiMethod method) {
    final PsiClass suitableBaseClass = findSuitableBaseClass(method);

    HierarchyNodeDescriptor descriptor = null;
    final ArrayList<PsiClass> superClasses = createSuperClasses(suitableBaseClass);

    if (!suitableBaseClass.equals(method.getContainingClass())) {
      superClasses.add(0, suitableBaseClass);
    }

    // remove from the top of the branch the classes that contain no 'method'
    for(int i = superClasses.size() - 1; i >= 0; i--){
      final PsiClass psiClass = superClasses.get(i);

      if (MethodHierarchyUtil.findBaseMethodInClass(method, psiClass, false) == null) {
        superClasses.remove(i);
      }
      else {
        break;
      }
    }

    for(int i = superClasses.size() - 1; i >= 0; i--){
      final PsiClass superClass = superClasses.get(i);
      final HierarchyNodeDescriptor newDescriptor = new MethodHierarchyNodeDescriptor(project, descriptor, superClass, false, MethodHierarchyTreeStructure.this);
      if (descriptor != null){
        descriptor.setCachedChildren(new HierarchyNodeDescriptor[] {newDescriptor});
      }
      descriptor = newDescriptor;
    }
    final HierarchyNodeDescriptor newDescriptor = new MethodHierarchyNodeDescriptor(project, descriptor, method.getContainingClass(), true, MethodHierarchyTreeStructure.this);
    if (descriptor != null) {
      descriptor.setCachedChildren(new HierarchyNodeDescriptor[] {newDescriptor});
    }
    return newDescriptor;
  }

  private static ArrayList<PsiClass> createSuperClasses(PsiClass aClass) {
    if (!aClass.isValid()) {
      return new ArrayList<PsiClass>();
    }

    final ArrayList<PsiClass> superClasses = new ArrayList<PsiClass>();
    while (!isJavaLangObject(aClass)) {
      final PsiClass aClass1 = aClass;
      final PsiClass[] superTypes = aClass1.getSupers();
      PsiClass superType = null;
      // find class first
      for (final PsiClass type : superTypes) {
        if (!type.isInterface() && !isJavaLangObject(type)) {
          superType = type;
          break;
        }
      }
      // if we haven't found a class, try to find an interface
      if (superType == null) {
        for (final PsiClass type : superTypes) {
          if (!isJavaLangObject(type)) {
            superType = type;
            break;
          }
        }
      }
      if (superType == null) break;
      if (superClasses.contains(superType)) break;
      superClasses.add(superType);
      aClass = superType;
    }

    return superClasses;
  }

  private static boolean isJavaLangObject(final PsiClass aClass) {
    return CommonClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName());
  }

  private static PsiClass findSuitableBaseClass(final PsiMethod method) {
    final PsiClass containingClass = method.getContainingClass();

    if (containingClass instanceof PsiAnonymousClass) {
      return containingClass;
    }

    final PsiClass superClass = containingClass.getSuperClass();
    if (superClass == null) {
      return containingClass;
    }

    if (MethodHierarchyUtil.findBaseMethodInClass(method, superClass, true) == null) {
      for (final PsiClass anInterface : containingClass.getInterfaces()) {
        if (MethodHierarchyUtil.findBaseMethodInClass(method, anInterface, true) != null) {
          return anInterface;
        }
      }
    }

    return containingClass;
  }

  @Nullable
  public final PsiMethod getBaseMethod() {
    final PsiElement element = myMethod.getElement();
    return element instanceof PsiMethod ? (PsiMethod)element : null;
  }


  @NotNull
  @Override
  protected final Object[] buildChildren(@NotNull final HierarchyNodeDescriptor descriptor) {
    final PsiElement psiElement = ((MethodHierarchyNodeDescriptor)descriptor).getPsiClass();
    if (!(psiElement instanceof PsiClass)) return ArrayUtil.EMPTY_OBJECT_ARRAY;
    final PsiClass psiClass = (PsiClass)psiElement;
    final Collection<PsiClass> subclasses = getSubclasses(psiClass);

    final List<HierarchyNodeDescriptor> descriptors = new ArrayList<HierarchyNodeDescriptor>(subclasses.size());
    for (final PsiClass aClass : subclasses) {
      if (HierarchyBrowserManager.getInstance(myProject).getState().HIDE_CLASSES_WHERE_METHOD_NOT_IMPLEMENTED) {
        if (shouldHideClass(aClass)) {
          continue;
        }
      }

      final MethodHierarchyNodeDescriptor d = new MethodHierarchyNodeDescriptor(myProject, descriptor, aClass, false, this);
      descriptors.add(d);
    }

    FunctionalExpressionSearch.search(getBaseMethod()).forEach(new Processor<PsiFunctionalExpression>() {
      @Override
      public boolean process(PsiFunctionalExpression expression) {
        descriptors.add(new MethodHierarchyNodeDescriptor(myProject, descriptor, expression, false, MethodHierarchyTreeStructure.this));
        return true;
      }
    });

    return descriptors.toArray(new HierarchyNodeDescriptor[descriptors.size()]);
  }

  private static Collection<PsiClass> getSubclasses(final PsiClass psiClass) {
    if (psiClass instanceof PsiAnonymousClass || psiClass.hasModifierProperty(PsiModifier.FINAL)) {
      return Collections.emptyList();
    }

    return ClassInheritorsSearch.search(psiClass, false).findAll();
  }

  private boolean shouldHideClass(final PsiClass psiClass) {
    if (getMethod(psiClass, false) != null || isSuperClassForBaseClass(psiClass)) {
      return false;
    }

    if (hasBaseClassMethod(psiClass) || isAbstract(psiClass)) {
      for (final PsiClass subclass : getSubclasses(psiClass)) {
        if (!shouldHideClass(subclass)) {
          return false;
        }
      }
      return true;
    }

    return false;
  }

  private boolean isAbstract(final PsiModifierListOwner owner) {
    return owner.hasModifierProperty(PsiModifier.ABSTRACT);
  }

  private boolean hasBaseClassMethod(final PsiClass psiClass) {
    final PsiMethod baseClassMethod = getMethod(psiClass, true);
    return baseClassMethod != null && !isAbstract(baseClassMethod);
  }

  private PsiMethod getMethod(final PsiClass aClass, final boolean checkBases) {
    return MethodHierarchyUtil.findBaseMethodInClass(getBaseMethod(), aClass, checkBases);
  }

  boolean isSuperClassForBaseClass(final PsiClass aClass) {
    final PsiMethod baseMethod = getBaseMethod();
    if (baseMethod == null) {
      return false;
    }
    final PsiClass baseClass = baseMethod.getContainingClass();
    if (baseClass == null) {
      return false;
    }
    // NB: parameters here are at CORRECT places!!!
    return baseClass.isInheritor(aClass, true);
  }
}