/*
* 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.icons.AllIcons;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.JavaHierarchyUtil;
import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ui.util.CompositeAppearance;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.LayeredIcon;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import javax.swing.*;
import java.awt.*;
import java.util.HashSet;
import java.util.Set;
public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor implements Navigatable {
private int usageCount = 0;
private final Set<PsiReference> references = new HashSet<PsiReference>();
private final CallHierarchyNodeDescriptor javaDelegate;
public KotlinCallHierarchyNodeDescriptor(@NotNull Project project,
HierarchyNodeDescriptor parentDescriptor,
@NotNull PsiElement element,
boolean isBase,
boolean navigateToReference) {
super(project, parentDescriptor, element, isBase);
this.javaDelegate = new CallHierarchyNodeDescriptor(myProject, null, element, isBase, navigateToReference);
}
public final CallHierarchyNodeDescriptor getJavaDelegate() {
return javaDelegate;
}
public final void addReference(PsiReference reference) {
if (references.add(reference)) {
usageCount++;
}
javaDelegate.addReference(reference);
}
public final PsiElement getTargetElement(){
return getPsiElement();
}
@Override
public final boolean isValid(){
//noinspection ConstantConditions
PsiElement myElement = getPsiElement();
return myElement != null && myElement.isValid();
}
@Override
public final boolean update(){
CompositeAppearance oldText = myHighlightedText;
Icon oldIcon = getIcon();
int flags = Iconable.ICON_FLAG_VISIBILITY;
if (isMarkReadOnly()) {
flags |= Iconable.ICON_FLAG_READ_STATUS;
}
boolean changes = super.update();
PsiElement targetElement = getTargetElement();
String elementText = renderElement(targetElement);
if (elementText == null) {
String invalidPrefix = IdeBundle.message("node.hierarchy.invalid");
if (!myHighlightedText.getText().startsWith(invalidPrefix)) {
myHighlightedText.getBeginning().addText(invalidPrefix, HierarchyNodeDescriptor.getInvalidPrefixAttributes());
}
return true;
}
Icon newIcon = targetElement.getIcon(flags);
if (changes && myIsBase) {
LayeredIcon icon = new LayeredIcon(2);
icon.setIcon(newIcon, 0);
icon.setIcon(AllIcons.Hierarchy.Base, 1, -AllIcons.Hierarchy.Base.getIconWidth() / 2, 0);
newIcon = icon;
}
setIcon(newIcon);
myHighlightedText = new CompositeAppearance();
TextAttributes mainTextAttributes = null;
if (myColor != null) {
mainTextAttributes = new TextAttributes(myColor, null, null, null, Font.PLAIN);
}
String packageName = null;
if (targetElement instanceof KtElement) {
packageName = KtPsiUtil.getPackageName((KtElement) targetElement);
}
else {
PsiClass enclosingClass = PsiTreeUtil.getParentOfType(targetElement, PsiClass.class, false);
if (enclosingClass != null) {
packageName = JavaHierarchyUtil.getPackageName(enclosingClass);
}
}
myHighlightedText.getEnding().addText(elementText, mainTextAttributes);
if (usageCount > 1) {
myHighlightedText.getEnding().addText(
IdeBundle.message("node.call.hierarchy.N.usages", usageCount),
HierarchyNodeDescriptor.getUsageCountPrefixAttributes()
);
}
if (packageName == null) {
packageName = "";
}
myHighlightedText.getEnding().addText(" (" + packageName + ")", HierarchyNodeDescriptor.getPackageNameAttributes());
myName = myHighlightedText.getText();
if (!(Comparing.equal(myHighlightedText, oldText) && Comparing.equal(getIcon(), oldIcon))) {
changes = true;
}
return changes;
}
@Nullable
private static String renderElement(@Nullable PsiElement element) {
String elementText;
String containerText = null;
if (element instanceof KtFile) {
elementText = ((KtFile) element).getName();
}
else if (element instanceof KtNamedDeclaration) {
BindingContext bindingContext = ResolutionUtils.analyze((KtElement) element, BodyResolveMode.FULL);
DeclarationDescriptor descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element);
if (descriptor == null) return null;
if (element instanceof KtClassOrObject) {
if (element instanceof KtObjectDeclaration && ((KtObjectDeclaration) element).isCompanion()) {
descriptor = descriptor.getContainingDeclaration();
if (!(descriptor instanceof ClassDescriptor)) return null;
elementText = renderClassOrObject((ClassDescriptor) descriptor);
}
else if (element instanceof KtEnumEntry) {
elementText = ((KtEnumEntry) element).getName();
}
else {
if (((KtClassOrObject) element).getName() != null) {
elementText = renderClassOrObject((ClassDescriptor) descriptor);
}
else {
elementText = "[anonymous]";
}
}
}
else if (element instanceof KtNamedFunction || element instanceof KtSecondaryConstructor) {
elementText = renderNamedFunction((FunctionDescriptor) descriptor);
}
else if (element instanceof KtProperty) {
elementText = ((KtProperty) element).getName();
}
else return null;
DeclarationDescriptor containerDescriptor = descriptor.getContainingDeclaration();
while (containerDescriptor != null) {
String name = containerDescriptor.getName().asString();
if (!name.startsWith("<")) {
containerText = name;
break;
}
containerDescriptor = containerDescriptor.getContainingDeclaration();
}
}
else return null;
if (elementText == null) return null;
return containerText != null ? containerText + "." + elementText : elementText;
}
public static String renderNamedFunction(FunctionDescriptor descriptor) {
DeclarationDescriptor descriptorForName = descriptor instanceof ConstructorDescriptor
? descriptor.getContainingDeclaration()
: descriptor;
String name = descriptorForName.getName().asString();
String paramTypes = StringUtil.join(
descriptor.getValueParameters(),
new Function<ValueParameterDescriptor, String>() {
@Override
public String fun(ValueParameterDescriptor descriptor) {
return DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(descriptor.getType());
}
},
", "
);
return name + "(" + paramTypes + ")";
}
private static String renderClassOrObject(ClassDescriptor descriptor) {
return descriptor.getName().asString();
}
@Override
public void navigate(boolean requestFocus) {
javaDelegate.navigate(requestFocus);
}
@Override
public boolean canNavigate() {
return javaDelegate.canNavigate();
}
@Override
public boolean canNavigateToSource() {
return javaDelegate.canNavigateToSource();
}
}