/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.callhierarchy;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.HierarchyTreeStructure;
import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiAnonymousClass;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiMember;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiNewExpression;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiSuperExpression;
import com.intellij.psi.PsiType;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import gw.plugin.ij.lang.psi.impl.expressions.GosuBeanMethodCallExpressionImpl;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class GosuCallerMethodsTreeStructure extends HierarchyTreeStructure {
private final String myScopeType;
/**
* Should be called in read action
*/
public GosuCallerMethodsTreeStructure(final Project project, final PsiMethod method, final String scopeType) {
super(project, new CallHierarchyNodeDescriptor(project, null, method, true, false));
myScopeType = scopeType;
}
@Override
protected final Object[] buildChildren(final HierarchyNodeDescriptor descriptor) {
final PsiMember enclosingElement = ((CallHierarchyNodeDescriptor) descriptor).getEnclosingElement();
if (!(enclosingElement instanceof PsiMethod)) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
final PsiMethod method = (PsiMethod) enclosingElement;
final PsiMethod baseMethod = (PsiMethod) ((CallHierarchyNodeDescriptor) getBaseDescriptor()).getTargetElement();
final SearchScope searchScope = getSearchScope(myScopeType, baseMethod.getContainingClass());
final PsiClass originalClass = method.getContainingClass();
assert originalClass != null;
final PsiClassType originalType = JavaPsiFacade.getElementFactory(myProject).createType(originalClass);
final Set<PsiMethod> methodsToFind = new HashSet<PsiMethod>();
methodsToFind.add(method);
ContainerUtil.addAll(methodsToFind, method.findDeepestSuperMethods());
final Map<PsiMember, CallHierarchyNodeDescriptor> methodToDescriptorMap = new HashMap<PsiMember, CallHierarchyNodeDescriptor>();
for (final PsiMethod methodToFind : methodsToFind) {
MethodReferencesSearch.search(methodToFind, searchScope, true).forEach(new Processor<PsiReference>() {
@Override
public boolean process(final PsiReference reference) {
if (reference instanceof PsiReferenceExpression) {
final PsiExpression qualifier = ((PsiReferenceExpression) reference).getQualifierExpression();
if (qualifier instanceof PsiSuperExpression) { // filter super.foo() call inside foo() and similar cases (bug 8411)
final PsiClass superClass = PsiUtil.resolveClassInType(qualifier.getType());
if (originalClass.isInheritor(superClass, true)) {
return true;
}
}
if (qualifier != null && !methodToFind.hasModifierProperty(PsiModifier.STATIC)) {
final PsiType qualifierType = qualifier.getType();
if (qualifierType instanceof PsiClassType &&
!TypeConversionUtil.isAssignable(qualifierType, originalType) &&
methodToFind != method) {
final PsiClass psiClass = ((PsiClassType) qualifierType).resolve();
if (psiClass != null) {
final PsiMethod callee = psiClass.findMethodBySignature(methodToFind, true);
if (callee != null && !methodsToFind.contains(callee)) {
// skip sibling methods
return true;
}
}
}
}
} else if (reference instanceof GosuBeanMethodCallExpressionImpl) {
final GosuBeanMethodCallExpressionImpl gosuBeanMC = (GosuBeanMethodCallExpressionImpl) reference;
final PsiMethod psiMethod = gosuBeanMC.resolveMethod();
if (psiMethod != null && !methodToFind.hasModifierProperty(PsiModifier.STATIC)) {
final PsiClass containingClass = psiMethod.getContainingClass();
final PsiClassType psiClassType = JavaPsiFacade.getElementFactory(myProject).createType(containingClass);
if (psiClassType != null && !TypeConversionUtil.isAssignable(psiClassType, originalType) &&
methodToFind != method) {
final PsiMethod callee = containingClass.findMethodBySignature(methodToFind, true);
if (callee != null && !methodsToFind.contains(callee)) {
// skip sibling methods
return true;
}
}
}
} else {
if (!(reference instanceof PsiElement)) {
return true;
}
final PsiElement parent = ((PsiElement) reference).getParent();
if (parent instanceof PsiNewExpression) {
if (((PsiNewExpression) parent).getClassReference() != reference) {
return true;
}
} else if (parent instanceof PsiAnonymousClass) {
if (((PsiAnonymousClass) parent).getBaseClassReference() != reference) {
return true;
}
}
// else {
// return true;
// }
}
final PsiElement element = reference.getElement();
final PsiMember key = PsiTreeUtil.getNonStrictParentOfType(element, PsiMethod.class, PsiClass.class);
synchronized (methodToDescriptorMap) {
CallHierarchyNodeDescriptor d = methodToDescriptorMap.get(key);
if (d == null) {
d = new CallHierarchyNodeDescriptor(myProject, descriptor, element, false, true);
methodToDescriptorMap.put(key, d);
} else if (!d.hasReference(reference)) {
d.incrementUsageCount();
}
d.addReference(reference);
}
return true;
}
});
}
return methodToDescriptorMap.values().toArray(new Object[methodToDescriptorMap.size()]);
}
@Override
public boolean isAlwaysShowPlus() {
return true;
}
}