/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.usages;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiCatchSection;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassObjectAccessExpression;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiForeachStatement;
import com.intellij.psi.PsiImportStatement;
import com.intellij.psi.PsiInstanceOfExpression;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiLiteralExpression;
import com.intellij.psi.PsiLocalVariable;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiNewExpression;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiReferenceList;
import com.intellij.psi.PsiSuperExpression;
import com.intellij.psi.PsiThisExpression;
import com.intellij.psi.PsiTypeCastExpression;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.usages.impl.rules.UsageType;
import com.intellij.usages.impl.rules.UsageTypeProvider;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
public class GosuUsageTypeProvider implements UsageTypeProvider {
public UsageType getUsageType(@NotNull final PsiElement element) {
UsageType classUsageType = getClassUsageType(element);
if (classUsageType != null) return classUsageType;
UsageType methodUsageType = getMethodUsageType(element);
if (methodUsageType != null) return methodUsageType;
if (element instanceof PsiLiteralExpression) {
return UsageType.LITERAL_USAGE;
}
return null;
}
@Nullable
private static UsageType getMethodUsageType(PsiElement element) {
if (element instanceof PsiReferenceExpression) {
final PsiMethod containerMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
if (containerMethod != null) {
final PsiReferenceExpression referenceExpression = (PsiReferenceExpression) element;
final PsiExpression qualifier = referenceExpression.getQualifierExpression();
final PsiElement p = referenceExpression.getParent();
if (p instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression callExpression = (PsiMethodCallExpression) p;
final PsiMethod calledMethod = callExpression.resolveMethod();
if (qualifier != null && !(qualifier instanceof PsiThisExpression) && calledMethod != null) {
if (haveCommonSuperMethod(containerMethod, calledMethod)) {
boolean parametersDelegated = parametersDelegated(containerMethod, callExpression);
if (qualifier instanceof PsiSuperExpression) {
return parametersDelegated ? UsageType.DELEGATE_TO_SUPER : UsageType.DELEGATE_TO_SUPER_PARAMETERS_CHANGED;
} else {
return parametersDelegated ? UsageType.DELEGATE_TO_ANOTHER_INSTANCE : UsageType.DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED;
}
}
} else if (calledMethod == containerMethod) {
return UsageType.RECURSION;
}
}
}
}
return null;
}
private static boolean parametersDelegated(@NotNull final PsiMethod method, @NotNull final PsiMethodCallExpression call) {
final PsiParameter[] parameters = method.getParameterList().getParameters();
final PsiExpression[] arguments = call.getArgumentList().getExpressions();
if (parameters.length != arguments.length) return false;
for (int i = 0; i < parameters.length; i++) {
PsiParameter parameter = parameters[i];
PsiExpression argument = arguments[i];
if (!(argument instanceof PsiReferenceExpression)) return false;
if (!((PsiReferenceExpression) argument).isReferenceTo(parameter)) return false;
}
for (PsiParameter parameter : parameters) {
if (HighlightControlFlowUtil.isAssigned(parameter)) return false;
}
return true;
}
private static boolean haveCommonSuperMethod(@NotNull PsiMethod m1, @NotNull PsiMethod m2) {
HashSet<PsiMethod> s1 = new HashSet<>(Arrays.asList(m1.findDeepestSuperMethods()));
s1.add(m1);
HashSet<PsiMethod> s2 = new HashSet<>(Arrays.asList(m2.findDeepestSuperMethods()));
s2.add(m2);
s1.retainAll(s2);
return !s1.isEmpty();
}
@Nullable
private static UsageType getClassUsageType(@NotNull PsiElement element) {
if (element.getParent() instanceof PsiAnnotation &&
element == ((PsiAnnotation) element.getParent()).getNameReferenceElement()) return UsageType.ANNOTATION;
if (PsiTreeUtil.getParentOfType(element, PsiImportStatement.class, false) != null) return UsageType.CLASS_IMPORT;
PsiReferenceList referenceList = PsiTreeUtil.getParentOfType(element, PsiReferenceList.class);
if (referenceList != null) {
if (referenceList.getParent() instanceof PsiClass) return UsageType.CLASS_EXTENDS_IMPLEMENTS_LIST;
if (referenceList.getParent() instanceof PsiMethod) return UsageType.CLASS_METHOD_THROWS_LIST;
}
PsiTypeCastExpression castExpression = PsiTreeUtil.getParentOfType(element, PsiTypeCastExpression.class);
if (castExpression != null) {
if (PsiTreeUtil.isAncestor(castExpression.getCastType(), element, true)) return UsageType.CLASS_CAST_TO;
}
PsiInstanceOfExpression instanceOfExpression = PsiTreeUtil.getParentOfType(element, PsiInstanceOfExpression.class);
if (instanceOfExpression != null) {
if (PsiTreeUtil.isAncestor(instanceOfExpression.getCheckType(), element, true))
return UsageType.CLASS_INSTANCE_OF;
}
if (PsiTreeUtil.getParentOfType(element, PsiClassObjectAccessExpression.class) != null)
return UsageType.CLASS_CLASS_OBJECT_ACCESS;
//TODO-dp fix this
// if (element instanceof IGosuReferenceExpression) {
// IGosuReferenceExpression expression = (IGosuReferenceExpression) element;
// if (expression.resolve() instanceof PsiClass) {
// return UsageType.CLASS_STATIC_MEMBER_ACCESS;
// }
// }
final PsiParameter psiParameter = PsiTreeUtil.getParentOfType(element, PsiParameter.class);
if (psiParameter != null) {
final PsiElement scope = psiParameter.getDeclarationScope();
if (scope instanceof PsiMethod) return UsageType.CLASS_METHOD_PARAMETER_DECLARATION;
if (scope instanceof PsiCatchSection) return UsageType.CLASS_CATCH_CLAUSE_PARAMETER_DECLARATION;
if (scope instanceof PsiForeachStatement) return UsageType.CLASS_LOCAL_VAR_DECLARATION;
return null;
}
PsiField psiField = PsiTreeUtil.getParentOfType(element, PsiField.class);
if (psiField != null) {
//TODO-dp fix this
// if (PsiTreeUtil.isAncestor(psiField.getTypeElement(), element, true))
return UsageType.CLASS_FIELD_DECLARATION;
}
PsiLocalVariable psiLocalVar = PsiTreeUtil.getParentOfType(element, PsiLocalVariable.class);
if (psiLocalVar != null) {
if (PsiTreeUtil.isAncestor(psiLocalVar.getTypeElement(), element, true))
return UsageType.CLASS_LOCAL_VAR_DECLARATION;
}
PsiMethod psiMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
if (psiMethod != null) {
final PsiTypeElement retType = psiMethod.getReturnTypeElement();
//TODO-dp fix this
// if (retType != null && PsiTreeUtil.isAncestor(retType, element, true))
return UsageType.CLASS_METHOD_RETURN_TYPE;
}
final PsiNewExpression psiNewExpression = PsiTreeUtil.getParentOfType(element, PsiNewExpression.class);
if (psiNewExpression != null) {
final PsiJavaCodeReferenceElement classReference = psiNewExpression.getClassReference();
if (classReference != null && PsiTreeUtil.isAncestor(classReference, element, false))
return UsageType.CLASS_NEW_OPERATOR;
}
return null;
}
}