Django forms and formset support added
[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.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 com.jetbrains.python.psi.types.TypeEvalContext;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33
34 import java.util.*;
35
36 /**
37  * @author Ilya.Kazakevich
38  */
39 public final class NameResolverTools {
40   /**
41    * Cache: pair [qualified element name, class name (may be null)] by any psi element.
42    */
43   private static final PsiCacheKey<Pair<String, String>, PyElement> QUALIFIED_AND_CLASS_NAME =
44     PsiCacheKey.create(NameResolverTools.class.getName(), new QualifiedAndClassNameObtainer(), PsiModificationTracker.MODIFICATION_COUNT);
45
46   private NameResolverTools() {
47
48   }
49
50   /**
51    * For each provided element checks if FQ element name is one of provided names
52    *
53    * @param elements       element to check
54    * @param namesProviders some enum that has one or more names
55    * @return true if element's fqn is one of names, provided by provider
56    */
57   public static boolean isElementWithName(@NotNull final Collection<? extends PyElement> elements,
58                                           @NotNull final FQNamesProvider... namesProviders) {
59     for (final PyElement element : elements) {
60       if (isName(element, namesProviders)) {
61         return true;
62       }
63     }
64     return false;
65   }
66
67   /**
68    * Checks if FQ element name is one of provided names. May be <strong>heavy</strong>.
69    * It is always better to use less accurate but lighter {@link #isCalleeShortCut(PyCallExpression, FQNamesProvider)}
70    *
71    * @param element        element to check
72    * @param namesProviders some enum that has one or more names
73    * @return true if element's fqn is one of names, provided by provider
74    */
75   public static boolean isName(@NotNull final PyElement element, @NotNull final FQNamesProvider... namesProviders) {
76     final Pair<String, String> qualifiedAndClassName = QUALIFIED_AND_CLASS_NAME.getValue(element);
77     final String qualifiedName = qualifiedAndClassName.first;
78     final String className = qualifiedAndClassName.second;
79
80     for (final FQNamesProvider provider : namesProviders) {
81       final List<String> names = Arrays.asList(provider.getNames());
82       if (qualifiedName != null && names.contains(qualifiedName)) {
83         return true;
84       }
85       if (className != null && provider.isClass() && names.contains(className)) {
86         return true;
87       }
88     }
89     return false;
90   }
91
92   /**
93    * Looks for parent call of certain function
94    *
95    * @param anchor       element to look parent for
96    * @param functionName function to find
97    * @return parent call or null if not found
98    */
99   @Nullable
100   public static PyCallExpression findCallExpParent(@NotNull final PsiElement anchor, @NotNull final FQNamesProvider functionName) {
101     final PsiElement parent = PsiTreeUtil.findFirstParent(anchor, new MyFunctionCondition(functionName));
102     if (parent instanceof PyCallExpression) {
103       return (PyCallExpression)parent;
104     }
105     return null;
106   }
107
108   /**
109    * Same as {@link #isName(PyElement, FQNamesProvider...)} for call expr, but first checks name.
110    * Aliases not supported, but much lighter that way
111    *
112    * @param call     expr
113    * @param function names to check
114    * @return true if callee is correct
115    */
116   public static boolean isCalleeShortCut(@NotNull final PyCallExpression call,
117                                          @NotNull final FQNamesProvider function) {
118     final PyExpression callee = call.getCallee();
119     if (callee == null) {
120       return false;
121     }
122
123     final String callableName = callee.getName();
124
125     final Collection<String> possibleNames = new LinkedList<String>();
126     for (final String lastComponent : getLastComponents(function)) {
127       possibleNames.add(lastComponent);
128     }
129     return possibleNames.contains(callableName) && call.isCallee(function);
130   }
131
132   @NotNull
133   private static List<String> getLastComponents(@NotNull final FQNamesProvider provider) {
134     final List<String> result = new ArrayList<String>();
135     for (final String name : provider.getNames()) {
136       final String component = QualifiedName.fromDottedString(name).getLastComponent();
137       if (component != null) {
138         result.add(component);
139       }
140     }
141     return result;
142   }
143
144   /**
145    * Checks if some string contains last component one of name
146    *
147    * @param text  test to check
148    * @param names
149    */
150   public static boolean isContainsName(@NotNull final String text, @NotNull final FQNamesProvider names) {
151     for (final String lastComponent : getLastComponents(names)) {
152       if (text.contains(lastComponent)) {
153         return true;
154       }
155     }
156     return false;
157   }
158
159   /**
160    * Checks if some file contains last component one of name
161    *
162    * @param file  file to check
163    * @param names
164    */
165   public static boolean isContainsName(@NotNull final PsiFile file, @NotNull final FQNamesProvider names) {
166     return isContainsName(file.getText(), names);
167   }
168
169   /**
170    * Check if class has parent with some name
171    * @param child class to check
172    */
173   public static boolean isSubclass(@NotNull final PyClass child,
174                                    @NotNull final FQNamesProvider parentName,
175                                    @NotNull final TypeEvalContext context) {
176     for (final String nameToCheck : parentName.getNames()) {
177       if (child.isSubclass(nameToCheck, context)) {
178         return true;
179       }
180     }
181     return false;
182   }
183
184   /**
185    * Looks for call of some function
186    */
187   private static class MyFunctionCondition implements Condition<PsiElement> {
188     @NotNull
189     private final FQNamesProvider myNameToSearch;
190
191     MyFunctionCondition(@NotNull final FQNamesProvider name) {
192       myNameToSearch = name;
193     }
194
195     @Override
196     public boolean value(final PsiElement element) {
197       if (element instanceof PyCallExpression) {
198         return ((PyCallExpression)element).isCallee(myNameToSearch);
199       }
200       return false;
201     }
202   }
203
204   /**
205    * Returns pair [qualified name, class name (may be null)] by psi element
206    */
207   private static class QualifiedAndClassNameObtainer implements Function<PyElement, Pair<String, String>> {
208     @Override
209     @NotNull
210     public Pair<String, String> fun(@NotNull final PyElement param) {
211       PyElement elementToCheck = param;
212
213       // Trying to use no implicit context if possible...
214       final PsiReference reference;
215       if (param instanceof PyReferenceOwner) {
216         reference = ((PyReferenceOwner)param).getReference(PyResolveContext.noImplicits());
217       }
218       else {
219         reference = param.getReference();
220       }
221
222       if (reference != null) {
223         final PsiElement resolvedElement = reference.resolve();
224         if (resolvedElement instanceof PyElement) {
225           elementToCheck = (PyElement)resolvedElement;
226         }
227       }
228       String qualifiedName = null;
229       if (elementToCheck instanceof PyQualifiedNameOwner) {
230         qualifiedName = ((PyQualifiedNameOwner)elementToCheck).getQualifiedName();
231       }
232       String className = null;
233       if (elementToCheck instanceof PyFunction) {
234         final PyClass aClass = ((PyFunction)elementToCheck).getContainingClass();
235         if (aClass != null) {
236           className = aClass.getQualifiedName();
237         }
238       }
239       return Pair.create(qualifiedName, className);
240     }
241   }
242 }