package org.elixir_lang.structure_view.element.modular; import com.intellij.ide.util.treeView.smartTree.TreeElement; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.util.Pair; import com.intellij.psi.ElementDescriptionLocation; import com.intellij.psi.ElementDescriptionUtil; import com.intellij.psi.PsiElement; import com.intellij.usageView.UsageViewLongNameLocation; import com.intellij.usageView.UsageViewShortNameLocation; import com.intellij.usageView.UsageViewTypeLocation; import org.apache.commons.lang.math.IntRange; import org.elixir_lang.navigation.item_presentation.Parent; import org.elixir_lang.psi.call.Call; import org.elixir_lang.psi.impl.ElixirPsiImplUtil; import org.elixir_lang.structure_view.element.*; import org.elixir_lang.structure_view.element.CallDefinition; import org.elixir_lang.structure_view.element.CallDefinitionClause; import org.elixir_lang.structure_view.element.Delegation; import org.elixir_lang.structure_view.element.Exception; import org.elixir_lang.structure_view.element.Overridable; import org.elixir_lang.structure_view.element.Quote; import org.elixir_lang.structure_view.element.call_definition_by_name_arity.FunctionByNameArity; import org.elixir_lang.structure_view.element.call_definition_by_name_arity.MacroByNameArity; import org.elixir_lang.structure_view.element.structure.Structure; import org.elixir_lang.structure_view.node_provider.Used; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import static com.intellij.openapi.util.Pair.pair; import static org.elixir_lang.psi.call.name.Function.DEFMODULE; import static org.elixir_lang.psi.call.name.Module.KERNEL; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.enclosingMacroCall; public class Module extends Element<Call> implements Modular { /* * Fields */ @Nullable protected final Modular parent; /* * * Static Methods * */ /* * Public Static Methods */ public static void addClausesToCallDefinition( @NotNull Call call, @NotNull Map<Pair<String, Integer>, CallDefinition> callDefinitionByNameArity, @NotNull Modular modular, @NotNull Timed.Time time, @NotNull Inserter<CallDefinition> callDefinitionInserter ) { Pair<String, IntRange> nameArityRange = CallDefinitionClause.nameArityRange(call); if (nameArityRange != null) { String name = nameArityRange.first; IntRange arityRange = nameArityRange.second; addClausesToCallDefinition( call, name, arityRange, callDefinitionByNameArity, modular, time, callDefinitionInserter ); } } public static void addClausesToCallDefinition( @NotNull Call call, @NotNull String name, @NotNull IntRange arityRange, @NotNull Map<Pair<String, Integer>, CallDefinition> callDefinitionByNameArity, @NotNull Modular modular, @NotNull Timed.Time time, @NotNull Inserter<CallDefinition> callDefinitionInserter ) { for (int arity = arityRange.getMinimumInteger(); arity <= arityRange.getMaximumInteger(); arity++) { Pair<String, Integer> nameArity = pair(name, arity); CallDefinition callDefinition = callDefinitionByNameArity.get(nameArity); if (callDefinition == null) { callDefinition = new CallDefinition( modular, time, name, arity ); callDefinitionByNameArity.put(nameArity, callDefinition); callDefinitionInserter.insert(callDefinition); } callDefinition.clause(call); } } @NotNull public static TreeElement[] callChildren(@NotNull Modular modular, @NotNull Call call) { Call[] childCalls = ElixirPsiImplUtil.macroChildCalls(call); TreeElement[] children = childCallTreeElements(modular, childCalls); if (children == null) { children = new TreeElement[0]; } return children; } @Nullable public static String elementDescription(Call call, ElementDescriptionLocation location) { String elementDescription = null; if (location == UsageViewLongNameLocation.INSTANCE) { Call enclosingCall = enclosingMacroCall(call); // indirect recursion through ElementDescriptionUtil.getElementDescription because it is @NotNull and will // default to element text when not implemented, so a bug, but not an error will result. String relative = ElementDescriptionUtil.getElementDescription(call, UsageViewShortNameLocation.INSTANCE); if (enclosingCall != null) { String qualified = ElementDescriptionUtil.getElementDescription(enclosingCall, location); elementDescription = qualified + "." + relative; } else { elementDescription = relative; } } else if (location == UsageViewShortNameLocation.INSTANCE) { elementDescription = call.getName(); } else if (location == UsageViewTypeLocation.INSTANCE) { elementDescription = "module"; } return elementDescription; } public static boolean is(Call call) { return call.isCallingMacro(KERNEL, DEFMODULE, 2); } public static PsiElement nameIdentifier(Call call) { PsiElement[] primaryArguments = call.primaryArguments(); PsiElement nameIdentifier = null; if (primaryArguments != null && primaryArguments.length > 0) { nameIdentifier = primaryArguments[0]; } return nameIdentifier; } /* * Private Static Methods */ @Contract(pure = true) @Nullable private static TreeElement[] childCallTreeElements(@NotNull Modular modular, Call[] childCalls) { TreeElement[] treeElements = null; if (childCalls != null) { int length = childCalls.length; final List<TreeElement> treeElementList = new ArrayList<TreeElement>(length); FunctionByNameArity functionByNameArity = new FunctionByNameArity(length, treeElementList, modular); MacroByNameArity macroByNameArity = new MacroByNameArity(length, treeElementList, modular); Set<Overridable> overridableSet = new HashSet<Overridable>(); Set<org.elixir_lang.structure_view.element.Use> useSet = new HashSet<org.elixir_lang.structure_view.element.Use>(); for (Call childCall : childCalls) { if (Callback.is(childCall)) { treeElementList.add(new Callback(modular, childCall)); } else if (Delegation.is(childCall)) { functionByNameArity.addDelegationToTreeElementList(childCall); } else if (Exception.is(childCall)) { functionByNameArity.setException(new Exception(modular, childCall)); } else if (CallDefinitionClause.isFunction(childCall)) { functionByNameArity.addClausesToCallDefinition(childCall); } else if (CallDefinitionSpecification.is(childCall)) { functionByNameArity.addSpecificationToCallDefinition(childCall); } else if (Implementation.is(childCall)) { treeElementList.add(new Implementation(modular, childCall)); } else if (CallDefinitionClause.isMacro(childCall)) { macroByNameArity.addClausesToCallDefinition(childCall); } else if (Module.is(childCall)) { treeElementList.add(new Module(modular, childCall)); } else if (Overridable.is(childCall)) { Overridable overridable = new Overridable(modular, childCall); overridableSet.add(overridable); treeElementList.add(overridable); } else if (Protocol.is(childCall)) { treeElementList.add(new Protocol(modular, childCall)); } else if (org.elixir_lang.structure_view.element.Quote.is(childCall)) { treeElementList.add(new Quote(modular, childCall)); } else if (Structure.is(childCall)) { treeElementList.add(new Structure(modular, childCall)); } else if (Type.is(childCall)) { treeElementList.add(Type.fromCall(modular, childCall)); } else if (org.elixir_lang.structure_view.element.Use.is(childCall)) { org.elixir_lang.structure_view.element.Use use = new org.elixir_lang.structure_view.element.Use(modular, childCall); useSet.add(use); treeElementList.add(use); } else if (Unknown.is(childCall)) { // Should always be last since it will match all macro calls treeElementList.add(new Unknown(modular, childCall)); } } for (Overridable overridable : overridableSet) { for (TreeElement treeElement : overridable.getChildren()) { CallReference callReference = (CallReference) treeElement; Integer arity = callReference.arity(); if (arity != null) { String name = callReference.name(); CallDefinition function = functionByNameArity.get(pair(name, arity)); if (function != null) { function.setOverridable(true); } } } } Collection<TreeElement> useCollection = new HashSet<TreeElement>(useSet.size()); useCollection.addAll(useSet); Collection<TreeElement> nodesFromUses = Used.provideNodesFromChildren(useCollection); Map<Pair<String, Integer>, CallDefinition> useFunctionByNameArity = Used.functionByNameArity(nodesFromUses); for (Map.Entry<Pair<String, Integer>, CallDefinition> useNameArityFunction : useFunctionByNameArity.entrySet()) { CallDefinition useFunction = useNameArityFunction.getValue(); if (useFunction.isOverridable()) { Pair<String, Integer> useNameArity = useNameArityFunction.getKey(); CallDefinition function = functionByNameArity.get(useNameArity); if (function != null) { function.setOverride(true); } } } treeElements = treeElementList.toArray(new TreeElement[treeElementList.size()]); } return treeElements; } /* * Constructors */ public Module(@NotNull Call call) { this(null, call); } /** * * @param parent the parent {@link Module} or {@link org.elixir_lang.structure_view.element.Quote} that scopes * {@code call}. * @param call the {@code Kernel.defmodule/2} call nested in {@code parent}. */ public Module(@Nullable Modular parent, @NotNull Call call) { super(call); this.parent = parent; } /* * Public Instance Methods */ @NotNull @Override public TreeElement[] getChildren() { return callChildren(this, navigationItem); } /** * Returns the presentation of the tree element. * * @return the element presentation. */ @NotNull @Override public ItemPresentation getPresentation() { return new org.elixir_lang.navigation.item_presentation.modular.Module(location(), navigationItem); } /* * Protected Instanc Methods */ @Nullable protected String location() { String location = null; if (parent != null) { ItemPresentation itemPresentation = parent.getPresentation(); if (itemPresentation instanceof Parent) { Parent parentPresentation = (Parent) itemPresentation; location = parentPresentation.getLocatedPresentableText(); } } return location; } }