/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2014-2015 AS3Boyan
* Copyright 2014-2015 Elias Ku
*
* 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.plugins.haxe.ide.hierarchy.call;
import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.call.CallReferenceProcessor;
import com.intellij.ide.hierarchy.call.JavaCallHierarchyData;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.plugins.haxe.ide.hierarchy.HaxeHierarchyTimeoutHandler;
import com.intellij.plugins.haxe.lang.psi.HaxeNewExpression;
import com.intellij.plugins.haxe.lang.psi.HaxeReferenceExpression;
import com.intellij.plugins.haxe.lang.psi.HaxeSuperExpression;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.Set;
/**
* Created by ebishton on 1/9/15. Lifted from JavaCallReferenceProcessor.
*/
public class HaxeCallReferenceProcessor implements CallReferenceProcessor {
private static final Logger LOG = Logger.getInstance("#com.intellij.plugins.haxe.ide.hierarchy.call.HaxeCallReferenceProcessor");
public static class CallData extends JavaCallHierarchyData {
HaxeHierarchyTimeoutHandler myTimeoutHandler;
public CallData(PsiClass originalClass,
PsiMethod methodToFind,
PsiClassType originalType,
PsiMethod method,
Set<PsiMethod> methodsToFind,
NodeDescriptor nodeDescriptor,
Map<PsiMember, NodeDescriptor> resultMap,
Project project,
HaxeHierarchyTimeoutHandler timeoutHandler) {
super(originalClass, methodToFind, originalType, method, methodsToFind, nodeDescriptor, resultMap, project);
myTimeoutHandler = timeoutHandler;
}
public HaxeHierarchyTimeoutHandler getTimeoutHandler() {
return myTimeoutHandler;
}
}
@Override
public boolean process(@NotNull PsiReference reference, @NotNull JavaCallHierarchyData jchdata) {
if (!(jchdata instanceof CallData)) {
String msg = "Internal error, unexpected call data type passed in.";
LOG.error(msg);
throw new UnsupportedOperationException(msg);
}
CallData data = (CallData)jchdata;
PsiClass originalClass = data.getOriginalClass();
PsiMethod method = data.getMethod();
Set<PsiMethod> methodsToFind = data.getMethodsToFind();
PsiMethod methodToFind = data.getMethodToFind();
PsiClassType originalType = data.getOriginalType();
Map<PsiMember, NodeDescriptor> methodToDescriptorMap = data.getResultMap();
Project myProject = data.getProject();
HaxeHierarchyTimeoutHandler timeoutHandler = data.getTimeoutHandler();
// All done, if we time out.
if (timeoutHandler.checkAndCancelIfNecessary()) {
return false;
}
if (reference instanceof HaxeReferenceExpression) {
final PsiElement qualifierElement = ((HaxeReferenceExpression)reference).getQualifier();
final HaxeReferenceExpression qualifier = (HaxeReferenceExpression) qualifierElement;
if (qualifier instanceof HaxeSuperExpression) { // filter super.foo() call inside foo() and similar cases (bug 8411)
final PsiClass superClass = PsiUtil.resolveClassInType(qualifier.getPsiType());
if (superClass == null || originalClass.isInheritor(superClass, true)) {
return true;
}
}
if (qualifier != null && !methodToFind.hasModifierProperty(PsiModifier.STATIC)) {
final PsiType qualifierType = qualifier.getPsiType();
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 PsiElement)) {
return true;
}
final PsiElement parent = ((PsiElement)reference).getParent();
// If the parent is a 'new x' expression, but the reference isn't the primary
// expression, then keep looking.
if (parent instanceof HaxeNewExpression) {
if (((HaxeNewExpression)parent).getType().getReferenceExpression() != reference) {
return true;
}
}
// If the reference isn't the primary expression of an anonymous class, then keep looking?
else if (parent instanceof PsiAnonymousClass) {
// XXX: This appears to be an optimization, knowing that an anonymous class can't be
// referenced from outside of itself, there's no need to load the class by
// calling for the base reference (the first child of the class). The haxe
// PSI doesn't appear to be built in that fashion any way...
// if (((PsiAnonymousClass)parent).getBaseClassReference() != reference) {
// return true;
// }
// let it be processed.
}
else {
return true;
}
}
final PsiElement element = reference.getElement();
final PsiMember key = CallHierarchyNodeDescriptor.getEnclosingElement(element);
synchronized (methodToDescriptorMap) {
CallHierarchyNodeDescriptor d = (CallHierarchyNodeDescriptor)methodToDescriptorMap.get(key);
if (d == null) {
d = new CallHierarchyNodeDescriptor(myProject, (CallHierarchyNodeDescriptor)data.getNodeDescriptor(), element, false, true);
methodToDescriptorMap.put(key, d);
}
else if (!d.hasReference(reference)) {
d.incrementUsageCount();
}
d.addReference(reference);
}
return false;
}
}