blob: 58ff518990baac44ce2b1044f0dd7544c4a88a77 [file] [log] [blame]
/*
* 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, this);
if (descriptor != null){
descriptor.setCachedChildren(new HierarchyNodeDescriptor[] {newDescriptor});
}
descriptor = newDescriptor;
}
final HierarchyNodeDescriptor newDescriptor = new MethodHierarchyNodeDescriptor(project, descriptor, method.getContainingClass(), true, 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);
}
final PsiMethod existingMethod = ((MethodHierarchyNodeDescriptor)descriptor).getMethod(psiClass, false);
if (existingMethod != null) {
FunctionalExpressionSearch.search(existingMethod).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);
}
}