2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.jetbrains.python.nameResolver;
18 import com.intellij.openapi.util.Condition;
19 import com.intellij.openapi.util.Pair;
20 import com.intellij.psi.PsiElement;
21 import com.intellij.psi.PsiFile;
22 import com.intellij.psi.PsiReference;
23 import com.intellij.psi.util.PsiCacheKey;
24 import com.intellij.psi.util.PsiModificationTracker;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.psi.util.QualifiedName;
27 import com.intellij.util.Function;
28 import com.jetbrains.python.psi.*;
29 import com.jetbrains.python.psi.resolve.PyResolveContext;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
36 * @author Ilya.Kazakevich
38 public final class NameResolverTools {
40 * Cache: pair [qualified element name, class name (may be null)] by any psi element.
42 private static final PsiCacheKey<Pair<String, String>, PyElement> QUALIFIED_AND_CLASS_NAME =
43 PsiCacheKey.create(NameResolverTools.class.getName(), new QualifiedAndClassNameObtainer(), PsiModificationTracker.MODIFICATION_COUNT);
45 private NameResolverTools() {
50 * For each provided element checks if FQ element name is one of provided names
52 * @param elements element to check
53 * @param namesProviders some enum that has one or more names
54 * @return true if element's fqn is one of names, provided by provider
56 public static boolean isElementWithName(@NotNull final Collection<? extends PyElement> elements,
57 @NotNull final FQNamesProvider... namesProviders) {
58 for (final PyElement element : elements) {
59 if (isName(element, namesProviders)) {
67 * Checks if FQ element name is one of provided names. May be <strong>heavy</strong>.
68 * It is always better to use less accurate but lighter {@link #isCalleeShortCut(PyCallExpression, FQNamesProvider)}
70 * @param element element to check
71 * @param namesProviders some enum that has one or more names
72 * @return true if element's fqn is one of names, provided by provider
74 public static boolean isName(@NotNull final PyElement element, @NotNull final FQNamesProvider... namesProviders) {
75 final Pair<String, String> qualifiedAndClassName = QUALIFIED_AND_CLASS_NAME.getValue(element);
76 final String qualifiedName = qualifiedAndClassName.first;
77 final String className = qualifiedAndClassName.second;
79 for (final FQNamesProvider provider : namesProviders) {
80 final List<String> names = Arrays.asList(provider.getNames());
81 if (qualifiedName != null && names.contains(qualifiedName)) {
84 if (className != null && provider.isClass() && names.contains(className)) {
92 * Looks for parent call of certain function
94 * @param anchor element to look parent for
95 * @param functionName function to find
96 * @return parent call or null if not found
99 public static PyCallExpression findCallExpParent(@NotNull final PsiElement anchor, @NotNull final FQNamesProvider functionName) {
100 final PsiElement parent = PsiTreeUtil.findFirstParent(anchor, new MyFunctionCondition(functionName));
101 if (parent instanceof PyCallExpression) {
102 return (PyCallExpression)parent;
108 * Same as {@link #isName(PyElement, FQNamesProvider...)} for call expr, but first checks name.
109 * Aliases not supported, but much lighter that way
112 * @param function names to check
113 * @return true if callee is correct
115 public static boolean isCalleeShortCut(@NotNull final PyCallExpression call,
116 @NotNull final FQNamesProvider function) {
117 final PyExpression callee = call.getCallee();
118 if (callee == null) {
122 final String callableName = callee.getName();
124 final Collection<String> possibleNames = new LinkedList<String>();
125 for (final String lastComponent : getLastComponents(function)) {
126 possibleNames.add(lastComponent);
128 return possibleNames.contains(callableName) && call.isCallee(function);
132 private static List<String> getLastComponents(@NotNull final FQNamesProvider provider) {
133 final List<String> result = new ArrayList<String>();
134 for (final String name : provider.getNames()) {
135 final String component = QualifiedName.fromDottedString(name).getLastComponent();
136 if (component != null) {
137 result.add(component);
144 * Checks if some string contains last component one of name
145 * @param text test to check
148 public static boolean isContainsName(@NotNull final String text, @NotNull final FQNamesProvider names) {
149 for (final String lastComponent : getLastComponents(names)) {
150 if (text.contains(lastComponent)) {
157 * Checks if some file contains last component one of name
158 * @param file file to check
161 public static boolean isContainsName(@NotNull final PsiFile file, @NotNull final FQNamesProvider names) {
162 return isContainsName(file.getText(), names);
166 * Looks for call of some function
168 private static class MyFunctionCondition implements Condition<PsiElement> {
170 private final FQNamesProvider myNameToSearch;
172 MyFunctionCondition(@NotNull final FQNamesProvider name) {
173 myNameToSearch = name;
177 public boolean value(final PsiElement element) {
178 if (element instanceof PyCallExpression) {
179 return ((PyCallExpression)element).isCallee(myNameToSearch);
186 * Returns pair [qualified name, class name (may be null)] by psi element
188 private static class QualifiedAndClassNameObtainer implements Function<PyElement, Pair<String, String>> {
191 public Pair<String, String> fun(@NotNull final PyElement param) {
192 PyElement elementToCheck = param;
194 // Trying to use no implicit context if possible...
195 final PsiReference reference;
196 if (param instanceof PyReferenceOwner) {
197 reference = ((PyReferenceOwner)param).getReference(PyResolveContext.noImplicits());
200 reference = param.getReference();
203 if (reference != null) {
204 final PsiElement resolvedElement = reference.resolve();
205 if (resolvedElement instanceof PyElement) {
206 elementToCheck = (PyElement)resolvedElement;
209 String qualifiedName = null;
210 if (elementToCheck instanceof PyQualifiedNameOwner) {
211 qualifiedName = ((PyQualifiedNameOwner)elementToCheck).getQualifiedName();
213 String className = null;
214 if (elementToCheck instanceof PyFunction) {
215 final PyClass aClass = ((PyFunction)elementToCheck).getContainingClass();
216 if (aClass != null) {
217 className = aClass.getQualifiedName();
220 return Pair.create(qualifiedName, className);