b9cbbaec50618a867fae6fd2144a7321288e8b6b
[idea/community.git] / python / psi-api / src / com / jetbrains / python / nameResolver / NameResolverTools.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.jetbrains.python.nameResolver;
17
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.PsiReference;
22 import com.intellij.psi.util.PsiCacheKey;
23 import com.intellij.psi.util.PsiModificationTracker;
24 import com.intellij.psi.util.PsiTreeUtil;
25 import com.intellij.util.Function;
26 import com.jetbrains.python.psi.*;
27 import com.jetbrains.python.psi.resolve.PyResolveContext;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.List;
34
35 /**
36  * @author Ilya.Kazakevich
37  */
38 public final class NameResolverTools {
39   /**
40    * Cache: pair [qualified element name, class name (may be null)] by any psi element.
41    */
42   private static final PsiCacheKey<Pair<String, String>, PyElement> QUALIFIED_AND_CLASS_NAME =
43     PsiCacheKey.create(NameResolverTools.class.getName(), new QualifiedAndClassNameObtainer(), PsiModificationTracker.MODIFICATION_COUNT);
44
45   private NameResolverTools() {
46
47   }
48
49   /**
50    * For each provided element checks if FQ element name is one of provided names
51    *
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
55    */
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)) {
60         return true;
61       }
62     }
63     return false;
64   }
65
66   /**
67    * Checks if FQ element name is one of provided names
68    *
69    * @param element        element to check
70    * @param namesProviders some enum that has one or more names
71    * @return true if element's fqn is one of names, provided by provider
72    */
73   public static boolean isName(@NotNull final PyElement element, @NotNull final FQNamesProvider... namesProviders) {
74     final Pair<String, String> qualifiedAndClassName = QUALIFIED_AND_CLASS_NAME.getValue(element);
75     final String qualifiedName = qualifiedAndClassName.first;
76     final String className = qualifiedAndClassName.second;
77
78     for (final FQNamesProvider provider : namesProviders) {
79       final List<String> names = Arrays.asList(provider.getNames());
80       if (qualifiedName != null && names.contains(qualifiedName)) {
81         return true;
82       }
83       if (className != null && provider.isClass() && names.contains(className)) {
84         return true;
85       }
86     }
87     return false;
88   }
89
90   /**
91    * Looks for parent call of certain function
92    *
93    * @param anchor       element to look parent for
94    * @param functionName function to find
95    * @return parent call or null if not found
96    */
97   @Nullable
98   public static PyCallExpression findCallExpParent(@NotNull final PsiElement anchor, @NotNull final FQNamesProvider functionName) {
99     final PsiElement parent = PsiTreeUtil.findFirstParent(anchor, new MyFunctionCondition(functionName));
100     if (parent instanceof PyCallExpression) {
101       return (PyCallExpression)parent;
102     }
103     return null;
104   }
105
106   /**
107    * Looks for call of some function
108    */
109   private static class MyFunctionCondition implements Condition<PsiElement> {
110     @NotNull
111     private final FQNamesProvider myNameToSearch;
112
113     MyFunctionCondition(@NotNull final FQNamesProvider name) {
114       myNameToSearch = name;
115     }
116
117     @Override
118     public boolean value(final PsiElement element) {
119       if (element instanceof PyCallExpression) {
120         return ((PyCallExpression)element).isCallee(myNameToSearch);
121       }
122       return false;
123     }
124   }
125
126   /**
127    * Returns pair [qualified name, class name (may be null)] by psi element
128    */
129   private static class QualifiedAndClassNameObtainer implements Function<PyElement, Pair<String, String>> {
130     @Override
131     @NotNull
132     public Pair<String, String> fun(@NotNull final PyElement param) {
133       PyElement elementToCheck = param;
134
135       // Trying to use no implicit context if possible...
136       final PsiReference reference;
137       if (param instanceof PyReferenceOwner) {
138         reference = ((PyReferenceOwner)param).getReference(PyResolveContext.noImplicits());
139       }
140       else {
141         reference = param.getReference();
142       }
143
144       if (reference != null) {
145         final PsiElement resolvedElement = reference.resolve();
146         if (resolvedElement instanceof PyElement) {
147           elementToCheck = (PyElement)resolvedElement;
148         }
149       }
150       String qualifiedName = null;
151       if (elementToCheck instanceof PyQualifiedNameOwner) {
152         qualifiedName = ((PyQualifiedNameOwner)elementToCheck).getQualifiedName();
153       }
154       String className = null;
155       if (elementToCheck instanceof PyFunction) {
156         final PyClass aClass = ((PyFunction)elementToCheck).getContainingClass();
157         if (aClass != null) {
158           className = aClass.getQualifiedName();
159         }
160       }
161       return Pair.create(qualifiedName, className);
162     }
163   }
164 }