package org.elixir_lang.structure_view.element.modular; import com.intellij.navigation.ItemPresentation; import com.intellij.psi.ElementDescriptionLocation; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; import com.intellij.usageView.UsageViewTypeLocation; import org.elixir_lang.psi.*; import org.elixir_lang.psi.call.Call; import org.elixir_lang.psi.impl.ElixirPsiImplUtil; import org.elixir_lang.structure_view.element.CallDefinitionClause; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import static org.elixir_lang.psi.call.name.Function.DEFIMPL; import static org.elixir_lang.psi.call.name.Function.FOR; import static org.elixir_lang.psi.call.name.Module.KERNEL; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.stripAccessExpression; public class Implementation extends Module { /* * * Static Methods * */ /* * Public Static Methods */ public static String elementDescription(Call call, ElementDescriptionLocation location) { String elementDescription = null; if (location == UsageViewTypeLocation.INSTANCE) { elementDescription = "implementation"; } return elementDescription; } @Nullable private static Collection<String> forNameCollection(@NotNull ElixirAccessExpression forNameElement) { return forNameCollection(forNameElement.getChildren()); } @Nullable private static Collection<String> forNameCollection(@NotNull ElixirList forNameElement) { return forNameCollection(forNameElement.getChildren()); } @Nullable public static Collection<String> forNameCollection(@NotNull PsiElement forNameElement) { Collection<String> forNameCollection; if (forNameElement instanceof ElixirAccessExpression) { forNameCollection = forNameCollection((ElixirAccessExpression) forNameElement); } else if (forNameElement instanceof ElixirList) { forNameCollection = forNameCollection((ElixirList) forNameElement); } else if (forNameElement instanceof QualifiableAlias) { forNameCollection = forNameCollection((QualifiableAlias) forNameElement); } else if (forNameElement instanceof PsiNamedElement) { forNameCollection = forNameCollection((PsiNamedElement) forNameElement); } else { forNameCollection = Collections.singletonList(forNameElement.getText()); } return forNameCollection; } @Nullable private static Collection<String> forNameCollection(@NotNull PsiElement[] children) { Collection<String> forNameCollection = new ArrayList<String>(children.length); for (PsiElement child : children) { Collection<String> childForNameCollection = forNameCollection(child); if (childForNameCollection != null) { forNameCollection.addAll(childForNameCollection); } } return forNameCollection; } @Nullable private static Collection<String> forNameCollection(@NotNull PsiNamedElement forNameElement) { String forName = forNameElement.getName(); Collection<String> forNameCollection = null; if (forName != null) { forNameCollection = Collections.singletonList(forName); } return forNameCollection; } @Nullable private static Collection<String> forNameCollection(@NotNull QualifiableAlias forNameElement) { Collection<String> forNameCollection = null; String forName = forNameElement.getName(); if (forName != null) { forNameCollection = Collections.singletonList(forName); } return forNameCollection; } @Nullable public static Collection<String> forNameCollection(@Nullable Modular enclosingModular, @NotNull Call call) { PsiElement forNameElement = forNameElement(call); Collection<String> forNameCollection = null; if (forNameElement != null) { forNameCollection = forNameCollection(forNameElement); } else if (enclosingModular != null) { org.elixir_lang.navigation.item_presentation.Parent parentPresentation = (org.elixir_lang.navigation.item_presentation.Parent) enclosingModular.getPresentation(); forNameCollection = Collections.singletonList(parentPresentation.getLocatedPresentableText()); } return forNameCollection; } @Nullable public static PsiElement forNameElement(@NotNull Call call) { PsiElement[] finalArguments = ElixirPsiImplUtil.finalArguments(call); PsiElement forNameElement = null; if (finalArguments != null && finalArguments.length > 0) { PsiElement finalArgument = finalArguments[finalArguments.length - 1]; if (finalArgument instanceof QuotableKeywordList) { QuotableKeywordList quotableKeywordList = (QuotableKeywordList) finalArgument; forNameElement = ElixirPsiImplUtil.keywordValue(quotableKeywordList, FOR); } } return forNameElement; } /** * The name of the {@link #navigationItem}. * * @return the {@link NamedElement#getName()} if {@link #navigationItem} is a {@link NamedElement}; otherwise, * {@code null}. */ @Nullable @Override public String getName() { String name = null; if (forNameOverride != null) { String protocolName = protocolName(navigationItem); if (protocolName != null) { name = protocolName + "." + forNameOverride; } } else if (navigationItem instanceof NamedElement) { NamedElement namedElement = (NamedElement) navigationItem; name = namedElement.getName(); } return name; } public static boolean is(Call call) { return call.isCallingMacro(KERNEL, DEFIMPL, 2) || call.isCallingMacro(KERNEL, DEFIMPL, 3); } /** * @return {@code null} if protocol or module for the implementation cannot be derived or if the for argument is a * list. */ @Nullable public static String name(@NotNull Call call) { Collection<String> nameCollection = nameCollection(CallDefinitionClause.enclosingModular(call), call); String name = null; if (nameCollection != null && nameCollection.size() == 1) { name = nameCollection.iterator().next(); } // TODO Use CachedValueManager return name; } @Nullable public static Collection<String> nameCollection(@Nullable Modular enclosingModular, @NotNull Call call) { String protocolName = protocolName(call); Collection<String> forNameCollection = forNameCollection(enclosingModular, call); Collection<String> nameCollection = null; if (protocolName != null && forNameCollection != null) { nameCollection = new ArrayList<String>(); for (String forName : forNameCollection) { nameCollection.add(protocolName + "." + forName); } } return nameCollection; } @Nullable public static String protocolName(@NotNull Call call) { QualifiableAlias protocolNameElement = protocolNameElement(call); String protocolName = null; if (protocolNameElement != null) { protocolName = protocolName(protocolNameElement); } return protocolName; } @Nullable public static QualifiableAlias protocolNameElement(@NotNull Call call) { PsiElement[] finalArguments = ElixirPsiImplUtil.finalArguments(call); QualifiableAlias protocolNameElement = null; if (finalArguments != null && finalArguments.length > 0) { PsiElement firstFinalArgument = finalArguments[0]; if (firstFinalArgument instanceof ElixirAccessExpression) { PsiElement accessExpressionChild = stripAccessExpression(firstFinalArgument); if (accessExpressionChild instanceof QualifiableAlias) { protocolNameElement = (QualifiableAlias) accessExpressionChild; } } else if (firstFinalArgument instanceof QualifiableAlias) { protocolNameElement = (QualifiableAlias) firstFinalArgument; } } return protocolNameElement; } /* * Private Static Methods */ @Nullable private static String protocolName(QualifiableAlias qualifiableAlias) { String fullyQualifiedName = qualifiableAlias.fullyQualifiedName(); String protocolName = null; if (fullyQualifiedName != null) { // strip the `Elixir.` because no other presentation shows it protocolName = fullyQualifiedName.replace("Elixir.", ""); } return protocolName; } /* * Fields */ @Nullable private final String forNameOverride; /* * Constructors */ public Implementation(@NotNull Call call) { this(null, call); } public Implementation(@Nullable Modular parent, @NotNull Call call) { super(parent, call); this.forNameOverride = null; } /** * Implementation that presents as having a single {@code forName} when the {@code defimpl} has a list as the * keyword argument of {@code for:}. * * @param parent enclosing modular * @param call the {@code defimpl} call * @param forNameOverride The forName to use when the {@code call} has a list for its {@code for:} value. Needed so * that rendered named in the presentation uses {@code forName} for * {@link org.elixir_lang.navigation.GotoSymbolContributor}'s lookup menu */ public Implementation(@Nullable Modular parent, @NotNull Call call, @NotNull String forNameOverride) { super(parent, call); this.forNameOverride = forNameOverride; } /* * Instance Methods */ /** * The name of the module the protocol is for as derived from the PSI tree * * @return the {@link #parent} fully-qualified name if no `:for` keyword argument is given; otherwise, the * `:for` keyword argument. */ @NotNull private String derivedForName() { String forName; PsiElement[] finalArguments = ElixirPsiImplUtil.finalArguments(navigationItem); assert finalArguments != null; if (finalArguments.length > 1) { PsiElement finalArgument = finalArguments[finalArguments.length - 1]; if (finalArgument instanceof QuotableKeywordList) { QuotableKeywordList quotableKeywordList = (QuotableKeywordList) finalArgument; PsiElement keywordValue = ElixirPsiImplUtil.keywordValue(quotableKeywordList, FOR); forName = keywordValue.getText(); } else { forName = "?"; } } else if (parent != null) { org.elixir_lang.navigation.item_presentation.Parent parentPresentation = (org.elixir_lang.navigation.item_presentation.Parent) parent.getPresentation(); forName = parentPresentation.getLocatedPresentableText(); } else { forName = "?"; } return forName; } /** * The name of the module the protocol is for. * * @return the {@link #forNameOverride}; the {@link #parent} fully-qualified name if no `:for` keyword argument is * given; otherwise, the `:for` keyword argument. */ @NotNull private String forName() { String forName; if (forNameOverride != null) { forName = forNameOverride; } else { forName = derivedForName(); } return forName; } /** * Returns the presentation of the tree element. * * @return the element presentation. */ @NotNull @Override public ItemPresentation getPresentation() { return new org.elixir_lang.navigation.item_presentation.Implementation( protocolName(), forName() ); } /** * Unlike {@link #protocolName(Call)}, will return "?" when the protocol name can't be derived from the call. */ @NotNull public String protocolName() { String protocolName = protocolName(navigationItem); if (protocolName == null) { protocolName = "?"; } return protocolName; } }