/*
* Copyright 2013 Vladimir Rudev
*
* 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 ru.crazyproger.plugins.webtoper.nls.codeinsight;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.LineMarkerProvider;
import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder;
import com.intellij.icons.AllIcons;
import com.intellij.lang.properties.IProperty;
import com.intellij.lang.properties.psi.Property;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.refactoring.psi.SearchUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.crazyproger.plugins.webtoper.nls.psi.NlsFileImpl;
import javax.swing.Icon;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import static com.google.common.collect.Collections2.filter;
import static com.google.common.collect.Collections2.transform;
import static com.google.common.collect.Sets.newHashSet;
import static ru.crazyproger.plugins.webtoper.WebtoperBundle.message;
import static ru.crazyproger.plugins.webtoper.nls.psi.NlsFileImpl.Property2PsiElementFunction;
import static ru.crazyproger.plugins.webtoper.nls.psi.NlsFileImpl.PropertyKeyEqualsPredicate;
public class NlsLineMarkerProvider implements LineMarkerProvider {
@Nullable
@Override
public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
return null;
}
@Override
public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
for (PsiElement element : elements) {
if (element instanceof Property) {
collectOverridingLineMarkers((Property) element, result);
collectOverriddenLineMarkers((Property) element, result);
}
}
}
private void collectOverridingLineMarkers(final Property element, Collection<LineMarkerInfo> result) {
final String key = element.getKey();
if (key == null) return;
NlsFileImpl currentFile = (NlsFileImpl) element.getContainingFile();
Collection<NlsFileImpl> includedFiles = currentFile.getIncludedFiles();
if (includedFiles.isEmpty()) return;
Set<IProperty> parentProperties = new HashSet<IProperty>();
for (NlsFileImpl parent : includedFiles) {
Collection<IProperty> sameKey = findOverriddenProperties(key, parent, Sets.<PsiFile>newHashSet(currentFile));
parentProperties.addAll(sameKey);
}
if (parentProperties.isEmpty()) return;
MarkerInfo info = new MarkerInfo(element,
parentProperties,
result,
AllIcons.General.OverridingMethod,
"nls.lineMarker.overrides.popupTitle",
"nls.lineMarker.overrides.tooltip.multiple",
"nls.lineMarker.overrides.tooltip.oneBundle");
fillLineMarkers(info);
}
@NotNull
private Collection<IProperty> findOverriddenProperties(@NotNull final String key, @NotNull NlsFileImpl rootFile, @NotNull Collection<PsiFile> excludes) {
if (excludes.contains(rootFile)) {
return Collections.emptyList();
}
List<IProperty> fileProperties = rootFile.getProperties();
Collection<IProperty> withSameKey = filter(fileProperties, new PropertyKeyEqualsPredicate(key));
if (!withSameKey.isEmpty()) {
return withSameKey;
}
Collection<NlsFileImpl> includedFiles = rootFile.getIncludedFiles();
if (includedFiles.isEmpty()) return Collections.emptyList();
Collection<IProperty> result = new LinkedList<IProperty>();
Set<PsiFile> newExcludes = newHashSet(excludes);
newExcludes.add(rootFile);
for (NlsFileImpl nlsFile : includedFiles) {
result.addAll(findOverriddenProperties(key, nlsFile, newExcludes));
}
return result;
}
private void collectOverriddenLineMarkers(Property element, Collection<LineMarkerInfo> result) {
final String key = element.getKey();
if (key == null) return;
NlsFileImpl currentFile = (NlsFileImpl) element.getContainingFile();
Collection<NlsFileImpl> referencing = getReferencingFiles(currentFile);
Set<IProperty> properties = newHashSet();
for (NlsFileImpl child : referencing) {
Collection<IProperty> overridingProperties = findOverridingProperties(key, child, Sets.<PsiFile>newHashSet(currentFile));
properties.addAll(overridingProperties);
}
if (properties.isEmpty()) return;
MarkerInfo info = new MarkerInfo(element,
properties,
result,
AllIcons.General.OverridenMethod,
"nls.lineMarker.overridden.popupTitle",
"nls.lineMarker.overridden.tooltip.multiple",
"nls.lineMarker.overridden.tooltip.oneBundle");
fillLineMarkers(info);
}
private Collection<NlsFileImpl> getReferencingFiles(NlsFileImpl currentFile) {
Iterable<PsiReference> references = SearchUtils.findAllReferences(currentFile);
Collection<NlsFileImpl> directChilds = transform(Lists.<PsiReference>newArrayList(references), new Reference2ContainedFileFunction());
return filter(directChilds, Predicates.notNull());
}
private Collection<IProperty> findOverridingProperties(String key, NlsFileImpl currentFile, Set<PsiFile> excludes) {
if (excludes.contains(currentFile)) {
return Collections.emptyList();
}
List<IProperty> properties = currentFile.getProperties();
List<IProperty> result = new LinkedList<IProperty>();
Collection<IProperty> withSameKey = filter(properties, new PropertyKeyEqualsPredicate(key));
result.addAll(withSameKey);
excludes.add(currentFile);
Collection<NlsFileImpl> children = getReferencingFiles(currentFile);
for (NlsFileImpl child : children) {
result.addAll(findOverridingProperties(key, child, excludes));
}
return result;
}
private void fillLineMarkers(MarkerInfo info) {
Collection<PsiElement> targets = transform(info.getNavigationTargets(), new Property2PsiElementFunction());
NavigationGutterIconBuilder<PsiElement> builder = NavigationGutterIconBuilder.create(info.getIcon());
targets = filter(targets, Predicates.notNull());
builder.setTargets(targets);
String tooltipText;
if (targets.size() > 1) { // todo #WT-39
tooltipText = message(info.getMultiBundleKey());
} else {
PsiFile psiFile = targets.iterator().next().getContainingFile();
assert psiFile instanceof NlsFileImpl;
tooltipText = message(info.getOneBundleKey(), ((NlsFileImpl) psiFile).getNlsName());
}
builder.setTooltipText(tooltipText);
builder.setPopupTitle(message(info.getToolTipKey()));
info.getResult().add(builder.createLineMarkerInfo(info.getTarget()));
}
/**
* parameter object
*/
private static class MarkerInfo {
private final Property target;
private final Collection<IProperty> navigationTargets;
private final Collection<LineMarkerInfo> result;
private final Icon icon;
private final String toolTipKey;
private final String multiBundleKey;
private final String oneBundleKey;
private MarkerInfo(Property target, Collection<IProperty> navigationTargets, Collection<LineMarkerInfo> result, Icon icon, String toolTipKey, String multiBundleKey, String oneBundleKey) {
this.target = target;
this.navigationTargets = navigationTargets;
this.result = result;
this.icon = icon;
this.toolTipKey = toolTipKey;
this.multiBundleKey = multiBundleKey;
this.oneBundleKey = oneBundleKey;
}
public Property getTarget() {
return target;
}
public Collection<IProperty> getNavigationTargets() {
return navigationTargets;
}
public Collection<LineMarkerInfo> getResult() {
return result;
}
public Icon getIcon() {
return icon;
}
public String getToolTipKey() {
return toolTipKey;
}
public String getMultiBundleKey() {
return multiBundleKey;
}
public String getOneBundleKey() {
return oneBundleKey;
}
}
public static class Reference2ContainedFileFunction implements Function<PsiReference, NlsFileImpl> {
@Override
public NlsFileImpl apply(@Nullable PsiReference psiReference) {
if (psiReference != null) {
PsiElement element = psiReference.getElement();
if (element != null && element.isValid()) {
PsiFile psiFile = element.getContainingFile();
if (psiFile instanceof NlsFileImpl) {
return (NlsFileImpl) psiFile;
}
}
}
return null;
}
}
}