/*
* Copyright 2000-2012 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 com.intellij.codeInsight.navigation;
import com.intellij.codeHighlighting.Pass;
import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo;
import com.intellij.ide.TypePresentationService;
import com.intellij.ide.util.DefaultPsiElementCellRenderer;
import com.intellij.ide.util.PsiElementListCellRenderer;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.navigation.GotoRelatedItem;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.psi.PsiElement;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.util.ConstantFunction;
import com.intellij.util.NotNullFunction;
import com.intellij.util.NullableFunction;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.text.MessageFormat;
import java.util.*;
/**
* DOM-specific builder for {@link com.intellij.openapi.editor.markup.GutterIconRenderer}
* and {@link com.intellij.codeInsight.daemon.LineMarkerInfo}.
*
* @author peter
*/
public class NavigationGutterIconBuilder<T> {
@NonNls private static final String PATTERN = " {0}";
protected static final NotNullFunction<PsiElement,Collection<? extends PsiElement>> DEFAULT_PSI_CONVERTOR = new NotNullFunction<PsiElement, Collection<? extends PsiElement>>() {
@NotNull
public Collection<? extends PsiElement> fun(final PsiElement element) {
return ContainerUtil.createMaybeSingletonList(element);
}
};
protected static final NullableFunction DEFAULT_NAMER = new NullableFunction<Object, String>() {
@Nullable
@Override
public String fun(Object dom) {
return TypePresentationService.getInstance().getTypePresentableName(dom.getClass());
}
};
private final Icon myIcon;
private final NotNullFunction<T,Collection<? extends PsiElement>> myConverter;
protected NotNullLazyValue<Collection<? extends T>> myTargets;
protected boolean myLazy;
private String myTooltipText;
private String myPopupTitle;
private String myEmptyText;
private String myTooltipTitle;
private GutterIconRenderer.Alignment myAlignment = GutterIconRenderer.Alignment.CENTER;
private Computable<PsiElementListCellRenderer> myCellRenderer;
private NullableFunction<T,String> myNamer = createDefaultNamer();
private final NotNullFunction<T, Collection<? extends GotoRelatedItem>> myGotoRelatedItemProvider;
public static final NotNullFunction<PsiElement, Collection<? extends GotoRelatedItem>> PSI_GOTO_RELATED_ITEM_PROVIDER = new NotNullFunction<PsiElement, Collection<? extends GotoRelatedItem>>() {
@NotNull
@Override
public Collection<? extends GotoRelatedItem> fun(PsiElement dom) {
return Collections.singletonList(new GotoRelatedItem(dom, "XML"));
}
};
protected NavigationGutterIconBuilder(@NotNull final Icon icon, @NotNull NotNullFunction<T, Collection<? extends PsiElement>> converter) {
this(icon, converter, null);
}
protected NavigationGutterIconBuilder(@NotNull final Icon icon,
@NotNull NotNullFunction<T, Collection<? extends PsiElement>> converter,
final @Nullable NotNullFunction<T, Collection<? extends GotoRelatedItem>> gotoRelatedItemProvider) {
myIcon = icon;
myConverter = converter;
myGotoRelatedItemProvider = gotoRelatedItemProvider;
}
public static NavigationGutterIconBuilder<PsiElement> create(@NotNull final Icon icon) {
return create(icon, DEFAULT_PSI_CONVERTOR, PSI_GOTO_RELATED_ITEM_PROVIDER);
}
public static <T> NavigationGutterIconBuilder<T> create(@NotNull final Icon icon,
@NotNull NotNullFunction<T, Collection<? extends PsiElement>> converter) {
return create(icon, converter, null);
}
public static <T> NavigationGutterIconBuilder<T> create(@NotNull final Icon icon,
@NotNull NotNullFunction<T, Collection<? extends PsiElement>> converter,
final @Nullable NotNullFunction<T, Collection<? extends GotoRelatedItem>> gotoRelatedItemProvider) {
return new NavigationGutterIconBuilder<T>(icon, converter, gotoRelatedItemProvider);
}
public NavigationGutterIconBuilder<T> setTarget(@Nullable T target) {
return setTargets(ContainerUtil.createMaybeSingletonList(target));
}
public NavigationGutterIconBuilder<T> setTargets(@NotNull T... targets) {
return setTargets(Arrays.asList(targets));
}
public NavigationGutterIconBuilder<T> setTargets(@NotNull final NotNullLazyValue<Collection<? extends T>> targets) {
myTargets = targets;
myLazy = true;
return this;
}
public NavigationGutterIconBuilder<T> setTargets(@NotNull final Collection<? extends T> targets) {
myTargets = new NotNullLazyValue<Collection<? extends T>>() {
@NotNull
public Collection<? extends T> compute() {
return targets;
}
};
return this;
}
public NavigationGutterIconBuilder<T> setTooltipText(@NotNull String tooltipText) {
myTooltipText = tooltipText;
return this;
}
public NavigationGutterIconBuilder<T> setAlignment(@NotNull final GutterIconRenderer.Alignment alignment) {
myAlignment = alignment;
return this;
}
public NavigationGutterIconBuilder<T> setPopupTitle(@NotNull String popupTitle) {
myPopupTitle = popupTitle;
return this;
}
public NavigationGutterIconBuilder<T> setEmptyPopupText(@NotNull String emptyText) {
myEmptyText = emptyText;
return this;
}
public NavigationGutterIconBuilder<T> setTooltipTitle(@NotNull final String tooltipTitle) {
myTooltipTitle = tooltipTitle;
return this;
}
public NavigationGutterIconBuilder<T> setNamer(@NotNull NullableFunction<T,String> namer) {
myNamer = namer;
return this;
}
public NavigationGutterIconBuilder<T> setCellRenderer(@NotNull final PsiElementListCellRenderer cellRenderer) {
myCellRenderer = new Computable.PredefinedValueComputable<PsiElementListCellRenderer>(cellRenderer);
return this;
}
protected NullableFunction<T,String> createDefaultNamer() {
return DEFAULT_NAMER;
}
@Nullable
public Annotation install(@NotNull AnnotationHolder holder, @Nullable PsiElement element) {
if (!myLazy && myTargets.getValue().isEmpty() || element == null) return null;
return doInstall(holder.createInfoAnnotation(element, null), element.getProject());
}
protected Annotation doInstall(@NotNull Annotation annotation, @NotNull Project project) {
final MyNavigationGutterIconRenderer renderer = createGutterIconRenderer(project);
annotation.setGutterIconRenderer(renderer);
annotation.setNeedsUpdateOnTyping(false);
return annotation;
}
public RelatedItemLineMarkerInfo<PsiElement> createLineMarkerInfo(@NotNull PsiElement element) {
final MyNavigationGutterIconRenderer renderer = createGutterIconRenderer(element.getProject());
final String tooltip = renderer.getTooltipText();
NotNullLazyValue<Collection<? extends GotoRelatedItem>> gotoTargets = new NotNullLazyValue<Collection<? extends GotoRelatedItem>>() {
@NotNull
@Override
protected Collection<? extends GotoRelatedItem> compute() {
if (myGotoRelatedItemProvider != null) {
return ContainerUtil.concat(myTargets.getValue(), myGotoRelatedItemProvider);
}
return Collections.emptyList();
}
};
return new RelatedItemLineMarkerInfo<PsiElement>(element, element.getTextRange(), renderer.getIcon(), Pass.LINE_MARKERS,
tooltip == null ? null : new ConstantFunction<PsiElement, String>(tooltip),
renderer.isNavigateAction() ? renderer : null, renderer.getAlignment(),
gotoTargets);
}
private void checkBuilt() {
assert myTargets != null : "Must have called .setTargets() before calling create()";
}
private MyNavigationGutterIconRenderer createGutterIconRenderer(@NotNull Project project) {
checkBuilt();
final SmartPointerManager manager = SmartPointerManager.getInstance(project);
NotNullLazyValue<List<SmartPsiElementPointer>> pointers = new NotNullLazyValue<List<SmartPsiElementPointer>>() {
@NotNull
public List<SmartPsiElementPointer> compute() {
Set<PsiElement> elements = new THashSet<PsiElement>();
Collection<? extends T> targets = myTargets.getValue();
final List<SmartPsiElementPointer> list = new ArrayList<SmartPsiElementPointer>(targets.size());
for (final T target : targets) {
for (final PsiElement psiElement : myConverter.fun(target)) {
if (elements.add(psiElement) && psiElement.isValid()) {
list.add(manager.createSmartPsiElementPointer(psiElement));
}
}
}
return list;
}
};
final boolean empty = isEmpty();
if (myTooltipText == null && !myLazy) {
final SortedSet<String> names = new TreeSet<String>();
for (T t : myTargets.getValue()) {
final String text = myNamer.fun(t);
if (text != null) {
names.add(MessageFormat.format(PATTERN, text));
}
}
@NonNls StringBuilder sb = new StringBuilder("<html><body>");
if (myTooltipTitle != null) {
sb.append(myTooltipTitle).append("<br>");
}
for (String name : names) {
sb.append(name).append("<br>");
}
sb.append("</body></html>");
myTooltipText = sb.toString();
}
Computable<PsiElementListCellRenderer> renderer =
myCellRenderer == null ? new Computable<PsiElementListCellRenderer>() {
@Override
public PsiElementListCellRenderer compute() {
return new DefaultPsiElementCellRenderer();
}
} : myCellRenderer;
return new MyNavigationGutterIconRenderer(this, myAlignment, myIcon, myTooltipText, pointers, renderer, empty);
}
private boolean isEmpty() {
if (myLazy) {
return false;
}
Set<PsiElement> elements = new THashSet<PsiElement>();
Collection<? extends T> targets = myTargets.getValue();
for (final T target : targets) {
for (final PsiElement psiElement : myConverter.fun(target)) {
if (elements.add(psiElement)) {
return false;
}
}
}
return true;
}
private static class MyNavigationGutterIconRenderer extends NavigationGutterIconRenderer {
private final Alignment myAlignment;
private final Icon myIcon;
private final String myTooltipText;
private final boolean myEmpty;
public MyNavigationGutterIconRenderer(@NotNull NavigationGutterIconBuilder builder,
final Alignment alignment,
final Icon icon,
@Nullable final String tooltipText,
@NotNull NotNullLazyValue<List<SmartPsiElementPointer>> pointers,
Computable<PsiElementListCellRenderer> cellRenderer,
boolean empty) {
super(builder.myPopupTitle, builder.myEmptyText, cellRenderer, pointers);
myAlignment = alignment;
myIcon = icon;
myTooltipText = tooltipText;
myEmpty = empty;
}
@Override
public boolean isNavigateAction() {
return !myEmpty;
}
@NotNull
public Icon getIcon() {
return myIcon;
}
@Nullable
public String getTooltipText() {
return myTooltipText;
}
public Alignment getAlignment() {
return myAlignment;
}
public boolean equals(final Object o) {
if (this == o) return true;
if (!super.equals(o)) return false;
final MyNavigationGutterIconRenderer that = (MyNavigationGutterIconRenderer)o;
if (myAlignment != that.myAlignment) return false;
if (myIcon != null ? !myIcon.equals(that.myIcon) : that.myIcon != null) return false;
if (myTooltipText != null ? !myTooltipText.equals(that.myTooltipText) : that.myTooltipText != null) return false;
return true;
}
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (myAlignment != null ? myAlignment.hashCode() : 0);
result = 31 * result + (myIcon != null ? myIcon.hashCode() : 0);
result = 31 * result + (myTooltipText != null ? myTooltipText.hashCode() : 0);
return result;
}
}
}