Django forms and formset support added
[idea/community.git] / python / psi-api / src / com / jetbrains / python / codeInsight / PyCustomMember.java
1 /*
2  * Copyright 2000-2015 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.codeInsight;
17
18 import com.google.common.base.Preconditions;
19 import com.intellij.extapi.psi.ASTWrapperPsiElement;
20 import com.intellij.icons.AllIcons;
21 import com.intellij.openapi.util.Key;
22 import com.intellij.openapi.util.UserDataHolderBase;
23 import com.intellij.psi.PsiElement;
24 import com.intellij.psi.PsiReference;
25 import com.intellij.psi.util.*;
26 import com.intellij.util.Function;
27 import com.jetbrains.python.psi.PyClass;
28 import com.jetbrains.python.psi.PyFunction;
29 import com.jetbrains.python.psi.PyPsiFacade;
30 import com.jetbrains.python.psi.PyTypedElement;
31 import com.jetbrains.python.psi.types.PyClassType;
32 import com.jetbrains.python.psi.types.PyType;
33 import com.jetbrains.python.psi.types.TypeEvalContext;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import javax.swing.*;
38
39 /**
40  * Note: if you use {@link #myTypeName} to override real field, be sure to use
41  * {@link com.jetbrains.python.psi.types.PyOverridingClassMembersProvider}
42  *
43  * @author Dennis.Ushakov
44  */
45 public class PyCustomMember extends UserDataHolderBase {
46   private static final Key<ParameterizedCachedValue<PyClass, PsiElement>>
47     RESOLVE = Key.create("resolve");
48   private final String myName;
49   private final boolean myResolveToInstance;
50   private final Function<PsiElement, PyType> myTypeCallback;
51   @Nullable
52   private final String myTypeName;
53
54   private final PsiElement myTarget;
55   private PyPsiPath myPsiPath;
56
57   boolean myFunction = false;
58
59   /**
60    * Force resolving to {@link MyInstanceElement} even if element is function
61    */
62   private boolean myAlwaysResolveToCustomElement;
63   private Icon myIcon = AllIcons.Nodes.Method;
64   private PyCustomMemberTypeInfo<?> myCustomTypeInfo;
65
66   public PyCustomMember(@NotNull final String name, @Nullable final String type, final boolean resolveToInstance) {
67     myName = name;
68     myResolveToInstance = resolveToInstance;
69     myTypeName = type;
70
71     myTarget = null;
72     myTypeCallback = null;
73   }
74
75   public PyCustomMember(@NotNull final String name) {
76     myName = name;
77     myResolveToInstance = false;
78     myTypeName = null;
79
80     myTarget = null;
81     myTypeCallback = null;
82   }
83
84   public PyCustomMember(@NotNull final String name,
85                         @Nullable final String type,
86                         final Function<PsiElement, PyType> typeCallback) {
87     myName = name;
88
89     myResolveToInstance = false;
90     myTypeName = type;
91
92     myTarget = null;
93     myTypeCallback = typeCallback;
94   }
95
96   public PyCustomMember(@NotNull final String name, @Nullable final PsiElement target, @Nullable String typeName) {
97     myName = name;
98     myTarget = target;
99     myResolveToInstance = false;
100     myTypeName = typeName;
101     myTypeCallback = null;
102   }
103
104   public PyCustomMember(@NotNull final String name, @Nullable final PsiElement target) {
105     this(name, target, null);
106   }
107
108   public PyCustomMember resolvesTo(String moduleQName) {
109     myPsiPath = new PyPsiPath.ToFile(moduleQName);
110     return this;
111   }
112
113   public PyCustomMember resolvesToClass(String classQName) {
114     myPsiPath = new PyPsiPath.ToClassQName(classQName);
115     return this;
116   }
117
118   /**
119    * Force resolving to {@link MyInstanceElement} even if element is function
120    */
121   @NotNull
122   public final PyCustomMember alwaysResolveToCustomElement() {
123     myAlwaysResolveToCustomElement = true;
124     return this;
125   }
126
127   public PyCustomMember toClass(String name) {
128     myPsiPath = new PyPsiPath.ToClass(myPsiPath, name);
129     return this;
130   }
131
132   public PyCustomMember toFunction(String name) {
133     myPsiPath = new PyPsiPath.ToFunction(myPsiPath, name);
134     return this;
135   }
136
137   public PyCustomMember toFunctionRecursive(String name) {
138     myPsiPath = new PyPsiPath.ToFunctionRecursive(myPsiPath, name);
139     return this;
140   }
141
142   public PyCustomMember toClassAttribute(String name) {
143     myPsiPath = new PyPsiPath.ToClassAttribute(myPsiPath, name);
144     return this;
145   }
146
147   public PyCustomMember toCall(String name, String... args) {
148     myPsiPath = new PyPsiPath.ToCall(myPsiPath, name, args);
149     return this;
150   }
151
152   public PyCustomMember toAssignment(String assignee) {
153     myPsiPath = new PyPsiPath.ToAssignment(myPsiPath, assignee);
154     return this;
155   }
156
157   public PyCustomMember toPsiElement(final PsiElement psiElement) {
158     myPsiPath = new PyPsiPath() {
159
160       @Override
161       public PsiElement resolve(PsiElement module) {
162         return psiElement;
163       }
164     };
165     return this;
166   }
167
168   public String getName() {
169     return myName;
170   }
171
172   public Icon getIcon() {
173     if (myTarget != null) {
174       return myTarget.getIcon(0);
175     }
176     return myIcon;
177   }
178
179   @Nullable
180   public PsiElement resolve(@NotNull final PsiElement context) {
181
182     if (myTarget != null) {
183       return myTarget;
184     }
185
186     PyClass targetClass = null;
187     if (myTypeName != null) {
188
189       final ParameterizedCachedValueProvider<PyClass, PsiElement> provider = new ParameterizedCachedValueProvider<PyClass, PsiElement>() {
190         @Nullable
191         @Override
192         public CachedValueProvider.Result<PyClass> compute(
193           final PsiElement param) {
194           final PyClass result = PyPsiFacade.getInstance(param.getProject()).createClassByQName(myTypeName, param);
195           return CachedValueProvider.Result.create(result, PsiModificationTracker.MODIFICATION_COUNT);
196         }
197       };
198       targetClass = CachedValuesManager.getManager(context.getProject()).getParameterizedCachedValue(this, RESOLVE,
199                                                                                                      provider, false, context);
200     }
201     final PsiElement resolveTarget = findResolveTarget(context);
202     if (resolveTarget instanceof PyFunction && !myAlwaysResolveToCustomElement) {
203       return resolveTarget;
204     }
205     if (resolveTarget != null || targetClass != null) {
206       return new MyInstanceElement(targetClass, context, resolveTarget);
207     }
208     return null;
209   }
210
211   @Nullable
212   private PsiElement findResolveTarget(@NotNull PsiElement context) {
213     if (myPsiPath != null) {
214       return myPsiPath.resolve(context);
215     }
216     return null;
217   }
218
219   @Nullable
220   public String getShortType() {
221     if (myTypeName == null) {
222       return null;
223     }
224     int pos = myTypeName.lastIndexOf('.');
225     return myTypeName.substring(pos + 1);
226   }
227
228   public PyCustomMember asFunction() {
229     myFunction = true;
230     return this;
231   }
232
233   public boolean isFunction() {
234     return myFunction;
235   }
236
237   /**
238    * Checks if some reference points to this element
239    *
240    * @param reference reference to check
241    * @return true if reference points to it
242    */
243   public final boolean isReferenceToMe(@NotNull final PsiReference reference) {
244     final PsiElement element = reference.resolve();
245     if (!(element instanceof MyInstanceElement)) {
246       return false;
247     }
248     return ((MyInstanceElement)element).getThis().equals(this);
249   }
250
251   /**
252    * @param icon icon to use (will be used method icon otherwise)
253    */
254   public PyCustomMember withIcon(@NotNull final Icon icon) {
255     myIcon = icon;
256     return this;
257   }
258
259   /**
260    * Adds custom info to type if class has {@link #myTypeName} set.
261    * Info could be later obtained by key.
262    *
263    * @param customInfo custom info to add
264    */
265   public PyCustomMember withCustomTypeInfo(@NotNull final PyCustomMemberTypeInfo<?> customInfo) {
266     Preconditions.checkState(myTypeName != null, "Cant add custom type info if no type provided");
267     myCustomTypeInfo = customInfo;
268     return this;
269   }
270
271   private class MyInstanceElement extends ASTWrapperPsiElement implements PyTypedElement {
272     private final PyClass myClass;
273     private final PsiElement myContext;
274
275     public MyInstanceElement(PyClass clazz, PsiElement context, PsiElement resolveTarget) {
276       super(resolveTarget != null ? resolveTarget.getNode() : clazz.getNode());
277       myClass = clazz;
278       myContext = context;
279     }
280
281     private PyCustomMember getThis() {
282       return PyCustomMember.this;
283     }
284
285     public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
286       if (myTypeCallback != null) {
287         return myTypeCallback.fun(myContext);
288       }
289       else if (myClass != null) {
290         final PyClassType type = PyPsiFacade.getInstance(getProject()).createClassType(myClass, !myResolveToInstance);
291         if (myCustomTypeInfo != null) {
292           myCustomTypeInfo.fill(type);
293         }
294         return type;
295       }
296       return null;
297     }
298   }
299 }