/* * Copyright 2010-2015 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 org.jetbrains.kotlin.idea.hierarchy.calls; 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.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiReference; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.HashMap; import kotlin.collections.CollectionsKt; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.asJava.LightClassUtil; import org.jetbrains.kotlin.asJava.LightClassUtilsKt; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; import java.util.Map; import static org.jetbrains.kotlin.asJava.LightClassUtilsKt.toLightClass; public abstract class KotlinCallTreeStructure extends HierarchyTreeStructure { protected final String scopeType; public KotlinCallTreeStructure(@NotNull Project project, PsiElement element, String scopeType) { super(project, createNodeDescriptor(project, element, null, false, false)); this.scopeType = scopeType; } protected static KtElement getEnclosingElementForLocalDeclaration(PsiElement element) { return element instanceof KtNamedDeclaration ? KtPsiUtil.getEnclosingElementForLocalDeclaration((KtNamedDeclaration) element) : null; } @Nullable private static HierarchyNodeDescriptor createNodeDescriptor( Project project, PsiElement element, HierarchyNodeDescriptor parent, boolean navigateToReference, boolean wrapAsLightElements ) { PsiElement nodeElement = element; if (wrapAsLightElements && element instanceof KtElement) { nodeElement = CollectionsKt.firstOrNull(LightClassUtilsKt.toLightElements((KtElement) element)); } if (nodeElement == null) return null; boolean root = (parent == null); return nodeElement instanceof KtElement ? new KotlinCallHierarchyNodeDescriptor(project, parent, nodeElement, root, navigateToReference) : new CallHierarchyNodeDescriptor(project, parent, nodeElement, root, navigateToReference); } protected static PsiElement getTargetElement(HierarchyNodeDescriptor descriptor) { return descriptor instanceof CallHierarchyNodeDescriptor ? ((CallHierarchyNodeDescriptor) descriptor).getEnclosingElement() : ((KotlinCallHierarchyNodeDescriptor)descriptor).getTargetElement(); } private static final Function1<PsiElement, Boolean> IS_NON_LOCAL_DECLARATION = new Function1<PsiElement, Boolean>() { @Override public Boolean invoke(@javax.annotation.Nullable PsiElement input) { return input instanceof PsiMethod || ((input instanceof KtNamedFunction || input instanceof KtClassOrObject || input instanceof KtProperty) && !KtPsiUtil.isLocal((KtNamedDeclaration) input)); } }; @Nullable protected static PsiMethod getRepresentativePsiMethod(PsiElement element) { while (true) { element = PsiUtilsKt.getParentOfTypesAndPredicate(element, false, ArrayUtil.EMPTY_CLASS_ARRAY, IS_NON_LOCAL_DECLARATION); if (element == null) return null; PsiMethod method = getRepresentativePsiMethodForNonLocalDeclaration(element); if (method != null) return method; element = element.getParent(); } } private static PsiMethod getRepresentativePsiMethodForNonLocalDeclaration(PsiElement element) { if (element instanceof PsiMethod) { return (PsiMethod) element; } if (element instanceof KtNamedFunction || element instanceof KtSecondaryConstructor) { return LightClassUtil.INSTANCE.getLightClassMethod((KtFunction) element); } if (element instanceof KtProperty) { LightClassUtil.PropertyAccessorsPsiMethods propertyMethods = LightClassUtil.INSTANCE.getLightClassPropertyMethods((KtProperty) element); return (propertyMethods.getGetter() != null) ? propertyMethods.getGetter() : propertyMethods.getSetter(); } if (element instanceof KtClassOrObject) { PsiClass psiClass = toLightClass((KtClassOrObject) element); if (psiClass == null) return null; PsiMethod[] constructors = psiClass.getConstructors(); if (constructors.length > 0) return constructors[0]; } return null; } protected static CallHierarchyNodeDescriptor getJavaNodeDescriptor(HierarchyNodeDescriptor originalDescriptor) { if (originalDescriptor instanceof CallHierarchyNodeDescriptor) return (CallHierarchyNodeDescriptor) originalDescriptor; assert originalDescriptor instanceof KotlinCallHierarchyNodeDescriptor; return ((KotlinCallHierarchyNodeDescriptor) originalDescriptor).getJavaDelegate(); } protected Object[] collectNodeDescriptors( HierarchyNodeDescriptor descriptor, Map<PsiReference, PsiElement> referencesToCalleeElements, PsiClass basePsiClass ) { HashMap<PsiElement, HierarchyNodeDescriptor> declarationToDescriptorMap = new HashMap<PsiElement, HierarchyNodeDescriptor>(); for (Map.Entry<PsiReference, PsiElement> refToCallee : referencesToCalleeElements.entrySet()) { PsiReference ref = refToCallee.getKey(); PsiElement callee = refToCallee.getValue(); if (basePsiClass != null && !isInScope(basePsiClass, callee, scopeType)) continue; addNodeDescriptorForElement(ref, callee, declarationToDescriptorMap, descriptor, false); } return declarationToDescriptorMap.values().toArray(new Object[declarationToDescriptorMap.size()]); } protected static void addNodeDescriptorForElement( PsiReference reference, PsiElement element, Map<PsiElement, HierarchyNodeDescriptor> declarationToDescriptorMap, HierarchyNodeDescriptor descriptor, boolean wrapAsLightElements ) { HierarchyNodeDescriptor d = declarationToDescriptorMap.get(element); if (d == null) { d = createNodeDescriptor(element.getProject(), element, descriptor, true, wrapAsLightElements); if (d == null) return; declarationToDescriptorMap.put(element, d); } else if (d instanceof CallHierarchyNodeDescriptor) { ((CallHierarchyNodeDescriptor) d).incrementUsageCount(); } if (d instanceof CallHierarchyNodeDescriptor) { ((CallHierarchyNodeDescriptor) d).addReference(reference); } else if (d instanceof KotlinCallHierarchyNodeDescriptor) { ((KotlinCallHierarchyNodeDescriptor) d).addReference(reference); } } @Override public boolean isAlwaysShowPlus() { return true; } }