Merge branch 'vlan/pyi'
[idea/community.git] / python / src / com / jetbrains / python / psi / PyUtil.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.psi;
17
18 import com.google.common.collect.Collections2;
19 import com.google.common.collect.Maps;
20 import com.intellij.codeInsight.FileModificationService;
21 import com.intellij.codeInsight.completion.PrioritizedLookupElement;
22 import com.intellij.codeInsight.lookup.LookupElement;
23 import com.intellij.codeInsight.lookup.LookupElementBuilder;
24 import com.intellij.ide.fileTemplates.FileTemplate;
25 import com.intellij.ide.fileTemplates.FileTemplateManager;
26 import com.intellij.ide.scratch.ScratchRootType;
27 import com.intellij.injected.editor.VirtualFileWindow;
28 import com.intellij.lang.ASTFactory;
29 import com.intellij.lang.ASTNode;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.editor.Document;
32 import com.intellij.openapi.extensions.Extensions;
33 import com.intellij.openapi.module.Module;
34 import com.intellij.openapi.module.ModuleManager;
35 import com.intellij.openapi.module.ModuleUtilCore;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.projectRoots.Sdk;
38 import com.intellij.openapi.roots.ModuleRootManager;
39 import com.intellij.openapi.ui.MessageType;
40 import com.intellij.openapi.ui.popup.Balloon;
41 import com.intellij.openapi.ui.popup.JBPopupFactory;
42 import com.intellij.openapi.util.TextRange;
43 import com.intellij.openapi.util.io.FileUtil;
44 import com.intellij.openapi.util.io.FileUtilRt;
45 import com.intellij.openapi.util.text.StringUtil;
46 import com.intellij.openapi.vfs.LocalFileSystem;
47 import com.intellij.openapi.vfs.VirtualFile;
48 import com.intellij.openapi.wm.WindowManager;
49 import com.intellij.psi.*;
50 import com.intellij.psi.codeStyle.CodeStyleSettings;
51 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
52 import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions;
53 import com.intellij.psi.stubs.StubElement;
54 import com.intellij.psi.util.PsiTreeUtil;
55 import com.intellij.psi.util.QualifiedName;
56 import com.intellij.ui.awt.RelativePoint;
57 import com.intellij.util.*;
58 import com.intellij.util.containers.ContainerUtil;
59 import com.jetbrains.NotNullPredicate;
60 import com.jetbrains.python.PyBundle;
61 import com.jetbrains.python.PyNames;
62 import com.jetbrains.python.PyTokenTypes;
63 import com.jetbrains.python.codeInsight.completion.OverwriteEqualsInsertHandler;
64 import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
65 import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
66 import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType;
67 import com.jetbrains.python.formatter.PyCodeStyleSettings;
68 import com.jetbrains.python.magicLiteral.PyMagicLiteralTools;
69 import com.jetbrains.python.psi.impl.PyBuiltinCache;
70 import com.jetbrains.python.psi.impl.PyPsiUtils;
71 import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl;
72 import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
73 import com.jetbrains.python.psi.resolve.PyResolveContext;
74 import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
75 import com.jetbrains.python.psi.resolve.RatedResolveResult;
76 import com.jetbrains.python.psi.stubs.PySetuptoolsNamespaceIndex;
77 import com.jetbrains.python.psi.types.*;
78 import com.jetbrains.python.refactoring.classes.PyDependenciesComparator;
79 import com.jetbrains.python.refactoring.classes.extractSuperclass.PyExtractSuperclassHelper;
80 import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
81 import com.jetbrains.python.sdk.PythonSdkType;
82 import org.jetbrains.annotations.Contract;
83 import org.jetbrains.annotations.NonNls;
84 import org.jetbrains.annotations.NotNull;
85 import org.jetbrains.annotations.Nullable;
86
87 import javax.swing.*;
88 import java.awt.*;
89 import java.io.File;
90 import java.io.FileFilter;
91 import java.io.IOException;
92 import java.util.*;
93 import java.util.List;
94
95 import static com.jetbrains.python.psi.PyFunction.Modifier.CLASSMETHOD;
96 import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD;
97
98 public class PyUtil {
99
100   private PyUtil() {
101   }
102
103   @NotNull
104   public static <T extends PyElement> T[] getAllChildrenOfType(@NotNull PsiElement element, @NotNull Class<T> aClass) {
105     List<T> result = new SmartList<T>();
106     for (PsiElement child : element.getChildren()) {
107       if (instanceOf(child, aClass)) {
108         //noinspection unchecked
109         result.add((T)child);
110       }
111       else {
112         ContainerUtil.addAll(result, getAllChildrenOfType(child, aClass));
113       }
114     }
115     return ArrayUtil.toObjectArray(result, aClass);
116   }
117
118   /**
119    * @see PyUtil#flattenedParensAndTuples
120    */
121   protected static List<PyExpression> _unfoldParenExprs(PyExpression[] targets, List<PyExpression> receiver,
122                                                         boolean unfoldListLiterals, boolean unfoldStarExpressions) {
123     // NOTE: this proliferation of instanceofs is not very beautiful. Maybe rewrite using a visitor.
124     for (PyExpression exp : targets) {
125       if (exp instanceof PyParenthesizedExpression) {
126         final PyParenthesizedExpression parex = (PyParenthesizedExpression)exp;
127         _unfoldParenExprs(new PyExpression[]{parex.getContainedExpression()}, receiver, unfoldListLiterals, unfoldStarExpressions);
128       }
129       else if (exp instanceof PyTupleExpression) {
130         final PyTupleExpression tupex = (PyTupleExpression)exp;
131         _unfoldParenExprs(tupex.getElements(), receiver, unfoldListLiterals, unfoldStarExpressions);
132       }
133       else if (exp instanceof PyListLiteralExpression && unfoldListLiterals) {
134         final PyListLiteralExpression listLiteral = (PyListLiteralExpression)exp;
135         _unfoldParenExprs(listLiteral.getElements(), receiver, unfoldListLiterals, unfoldStarExpressions);
136       }
137       else if (exp instanceof PyStarExpression && unfoldStarExpressions) {
138         _unfoldParenExprs(new PyExpression[]{((PyStarExpression)exp).getExpression()}, receiver, unfoldListLiterals, unfoldStarExpressions);
139       }
140       else if (exp != null) {
141         receiver.add(exp);
142       }
143     }
144     return receiver;
145   }
146
147   // Poor man's catamorhpism :)
148
149   /**
150    * Flattens the representation of every element in targets, and puts all results together.
151    * Elements of every tuple nested in target item are brought to the top level: (a, (b, (c, d))) -> (a, b, c, d)
152    * Typical usage: <code>flattenedParensAndTuples(some_tuple.getExpressions())</code>.
153    *
154    * @param targets target elements.
155    * @return the list of flattened expressions.
156    */
157   @NotNull
158   public static List<PyExpression> flattenedParensAndTuples(PyExpression... targets) {
159     return _unfoldParenExprs(targets, new ArrayList<PyExpression>(targets.length), false, false);
160   }
161
162   @NotNull
163   public static List<PyExpression> flattenedParensAndLists(PyExpression... targets) {
164     return _unfoldParenExprs(targets, new ArrayList<PyExpression>(targets.length), true, true);
165   }
166
167   @NotNull
168   public static List<PyExpression> flattenedParensAndStars(PyExpression... targets) {
169     return _unfoldParenExprs(targets, new ArrayList<PyExpression>(targets.length), false, true);
170   }
171
172   // Poor man's filter
173   // TODO: move to a saner place
174
175   public static boolean instanceOf(Object obj, Class... possibleClasses) {
176     if (obj == null || possibleClasses == null) return false;
177     for (Class cls : possibleClasses) {
178       if (cls.isInstance(obj)) return true;
179     }
180     return false;
181   }
182
183
184   /**
185    * Produce a reasonable representation of a PSI element, good for debugging.
186    *
187    * @param elt      element to represent; nulls and invalid nodes are ok.
188    * @param cutAtEOL if true, representation stops at nearest EOL inside the element.
189    * @return the representation.
190    */
191   @NotNull
192   @NonNls
193   public static String getReadableRepr(PsiElement elt, final boolean cutAtEOL) {
194     if (elt == null) return "null!";
195     ASTNode node = elt.getNode();
196     if (node == null) {
197       return "null";
198     }
199     else {
200       String s = node.getText();
201       int cut_pos;
202       if (cutAtEOL) {
203         cut_pos = s.indexOf('\n');
204       }
205       else {
206         cut_pos = -1;
207       }
208       if (cut_pos < 0) cut_pos = s.length();
209       return s.substring(0, Math.min(cut_pos, s.length()));
210     }
211   }
212
213   @Nullable
214   public static PyClass getContainingClassOrSelf(final PsiElement element) {
215     PsiElement current = element;
216     while (current != null && !(current instanceof PyClass)) {
217       current = current.getParent();
218     }
219     return (PyClass)current;
220   }
221
222   /**
223    * @param element for which to obtain the file
224    * @return PyFile, or null, if there's no containing file, or it is not a PyFile.
225    */
226   @Nullable
227   public static PyFile getContainingPyFile(PyElement element) {
228     final PsiFile containingFile = element.getContainingFile();
229     return containingFile instanceof PyFile ? (PyFile)containingFile : null;
230   }
231
232   /**
233    * Shows an information balloon in a reasonable place at the top right of the window.
234    *
235    * @param project     our project
236    * @param message     the text, HTML markup allowed
237    * @param messageType message type, changes the icon and the background.
238    */
239   // TODO: move to a better place
240   public static void showBalloon(Project project, String message, MessageType messageType) {
241     // ripped from com.intellij.openapi.vcs.changes.ui.ChangesViewBalloonProblemNotifier
242     final JFrame frame = WindowManager.getInstance().getFrame(project.isDefault() ? null : project);
243     if (frame == null) return;
244     final JComponent component = frame.getRootPane();
245     if (component == null) return;
246     final Rectangle rect = component.getVisibleRect();
247     final Point p = new Point(rect.x + rect.width - 10, rect.y + 10);
248     final RelativePoint point = new RelativePoint(component, p);
249
250     JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(message, messageType.getDefaultIcon(), messageType.getPopupBackground(), null)
251       .setShowCallout(false).setCloseButtonEnabled(true)
252       .createBalloon().show(point, Balloon.Position.atLeft);
253   }
254
255   @NonNls
256   /**
257    * Returns a quoted string representation, or "null".
258    */
259   public static String nvl(Object s) {
260     if (s != null) {
261       return "'" + s.toString() + "'";
262     }
263     else {
264       return "null";
265     }
266   }
267
268   /**
269    * Adds an item into a comma-separated list in a PSI tree. E.g. can turn "foo, bar" into "foo, bar, baz", adding commas as needed.
270    *
271    * @param parent     the element to represent the list; we're adding a child to it.
272    * @param newItem    the element we're inserting (the "baz" in the example).
273    * @param beforeThis node to mark the insertion point inside the list; must belong to a child of target. Set to null to add first element.
274    * @param isFirst    true if we don't need a comma before the element we're adding.
275    * @param isLast     true if we don't need a comma after the element we're adding.
276    */
277   public static void addListNode(PsiElement parent, PsiElement newItem, ASTNode beforeThis,
278                                  boolean isFirst, boolean isLast, boolean addWhitespace) {
279     if (!FileModificationService.getInstance().preparePsiElementForWrite(parent)) {
280       return;
281     }
282     ASTNode node = parent.getNode();
283     assert node != null;
284     ASTNode itemNode = newItem.getNode();
285     assert itemNode != null;
286     Project project = parent.getProject();
287     PyElementGenerator gen = PyElementGenerator.getInstance(project);
288     if (!isFirst) node.addChild(gen.createComma(), beforeThis);
289     node.addChild(itemNode, beforeThis);
290     if (!isLast) node.addChild(gen.createComma(), beforeThis);
291     if (addWhitespace) node.addChild(ASTFactory.whitespace(" "), beforeThis);
292   }
293
294   /**
295    * Removes an element from a a comma-separated list in a PSI tree. E.g. can turn "foo, bar, baz" into "foo, baz",
296    * removing commas as needed. It removes a trailing comma if it results from deletion.
297    *
298    * @param item what to remove. Its parent is considered the list, and commas must be its peers.
299    */
300   public static void removeListNode(PsiElement item) {
301     PsiElement parent = item.getParent();
302     if (!FileModificationService.getInstance().preparePsiElementForWrite(parent)) {
303       return;
304     }
305     // remove comma after the item
306     ASTNode binder = parent.getNode();
307     assert binder != null : "parent node is null, ensureWritable() lied";
308     boolean got_comma_after = eraseWhitespaceAndComma(binder, item, false);
309     if (!got_comma_after) {
310       // there was not a comma after the item; remove a comma before the item
311       eraseWhitespaceAndComma(binder, item, true);
312     }
313     // finally
314     item.delete();
315   }
316
317   /**
318    * Removes whitespace and comma(s) that are siblings of the item, up to the first non-whitespace and non-comma.
319    *
320    * @param parent_node node of the parent of item.
321    * @param item        starting point; we erase left or right of it, but not it.
322    * @param backwards   true to erase prev siblings, false to erase next siblings.
323    * @return true       if a comma was found and removed.
324    */
325   public static boolean eraseWhitespaceAndComma(ASTNode parent_node, PsiElement item, boolean backwards) {
326     // we operate on AST, PSI won't let us delete whitespace easily.
327     boolean is_comma;
328     boolean got_comma = false;
329     ASTNode current = item.getNode();
330     ASTNode candidate;
331     boolean have_skipped_the_item = false;
332     while (current != null) {
333       candidate = current;
334       current = backwards ? current.getTreePrev() : current.getTreeNext();
335       if (have_skipped_the_item) {
336         is_comma = ",".equals(candidate.getText());
337         got_comma |= is_comma;
338         if (is_comma || candidate.getElementType() == TokenType.WHITE_SPACE) {
339           parent_node.removeChild(candidate);
340         }
341         else {
342           break;
343         }
344       }
345       else {
346         have_skipped_the_item = true;
347       }
348     }
349     return got_comma;
350   }
351
352   /**
353    * Collects superclasses of a class all the way up the inheritance chain. The order is <i>not</i> necessarily the MRO.
354    */
355   @NotNull
356   public static List<PyClass> getAllSuperClasses(@NotNull PyClass pyClass) {
357     List<PyClass> superClasses = new ArrayList<PyClass>();
358     for (PyClass ancestor : pyClass.getAncestorClasses(null)) {
359       if (!PyNames.FAKE_OLD_BASE.equals(ancestor.getName())) {
360         superClasses.add(ancestor);
361       }
362     }
363     return superClasses;
364   }
365
366
367   // TODO: move to a more proper place?
368
369   /**
370    * Determine the type of a special attribute. Currently supported: {@code __class__} and {@code __dict__}.
371    *
372    * @param ref reference to a possible attribute; only qualified references make sense.
373    * @return type, or null (if type cannot be determined, reference is not to a known attribute, etc.)
374    */
375   @Nullable
376   public static PyType getSpecialAttributeType(@Nullable PyReferenceExpression ref, TypeEvalContext context) {
377     if (ref != null) {
378       PyExpression qualifier = ref.getQualifier();
379       if (qualifier != null) {
380         String attr_name = ref.getReferencedName();
381         if (PyNames.__CLASS__.equals(attr_name)) {
382           PyType qualifierType = context.getType(qualifier);
383           if (qualifierType instanceof PyClassType) {
384             return new PyClassTypeImpl(((PyClassType)qualifierType).getPyClass(), true); // always as class, never instance
385           }
386         }
387         else if (PyNames.DICT.equals(attr_name)) {
388           PyType qualifierType = context.getType(qualifier);
389           if (qualifierType instanceof PyClassType && ((PyClassType)qualifierType).isDefinition()) {
390             return PyBuiltinCache.getInstance(ref).getDictType();
391           }
392         }
393       }
394     }
395     return null;
396   }
397
398   /**
399    * Makes sure that 'thing' is not null; else throws an {@link IncorrectOperationException}.
400    *
401    * @param thing what we check.
402    * @return thing, if not null.
403    */
404   @NotNull
405   public static <T> T sure(T thing) {
406     if (thing == null) throw new IncorrectOperationException();
407     return thing;
408   }
409
410   /**
411    * Makes sure that the 'thing' is true; else throws an {@link IncorrectOperationException}.
412    *
413    * @param thing what we check.
414    */
415   public static void sure(boolean thing) {
416     if (!thing) throw new IncorrectOperationException();
417   }
418
419   public static boolean isAttribute(PyTargetExpression ex) {
420     return isInstanceAttribute(ex) || isClassAttribute(ex);
421   }
422
423   public static boolean isInstanceAttribute(PyExpression target) {
424     if (!(target instanceof PyTargetExpression)) {
425       return false;
426     }
427     final ScopeOwner owner = ScopeUtil.getScopeOwner(target);
428     if (owner instanceof PyFunction) {
429       final PyFunction method = (PyFunction)owner;
430       if (method.getContainingClass() != null) {
431         if (method.getStub() != null) {
432           return true;
433         }
434         final PyParameter[] params = method.getParameterList().getParameters();
435         if (params.length > 0) {
436           final PyTargetExpression targetExpr = (PyTargetExpression)target;
437           final PyExpression qualifier = targetExpr.getQualifier();
438           return qualifier != null && qualifier.getText().equals(params[0].getName());
439         }
440       }
441     }
442     return false;
443   }
444
445   public static boolean isClassAttribute(PsiElement element) {
446     return element instanceof PyTargetExpression && ScopeUtil.getScopeOwner(element) instanceof PyClass;
447   }
448
449   public static boolean isIfNameEqualsMain(PyIfStatement ifStatement) {
450     final PyExpression condition = ifStatement.getIfPart().getCondition();
451     return isNameEqualsMain(condition);
452   }
453
454   private static boolean isNameEqualsMain(PyExpression condition) {
455     if (condition instanceof PyParenthesizedExpression) {
456       return isNameEqualsMain(((PyParenthesizedExpression)condition).getContainedExpression());
457     }
458     if (condition instanceof PyBinaryExpression) {
459       PyBinaryExpression binaryExpression = (PyBinaryExpression)condition;
460       if (binaryExpression.getOperator() == PyTokenTypes.OR_KEYWORD) {
461         return isNameEqualsMain(binaryExpression.getLeftExpression()) || isNameEqualsMain(binaryExpression.getRightExpression());
462       }
463       final PyExpression rhs = binaryExpression.getRightExpression();
464       return binaryExpression.getOperator() == PyTokenTypes.EQEQ &&
465              binaryExpression.getLeftExpression().getText().equals(PyNames.NAME) &&
466              rhs != null && rhs.getText().contains("__main__");
467     }
468     return false;
469   }
470
471   /**
472    * Searhes for a method wrapping given element.
473    *
474    * @param start element presumably inside a method
475    * @param deep  if true, allow 'start' to be inside functions nested in a method; else, 'start' must be directly inside a method.
476    * @return if not 'deep', [0] is the method and [1] is the class; if 'deep', first several elements may be the nested functions,
477    * the last but one is the method, and the last is the class.
478    */
479   @Nullable
480   public static List<PsiElement> searchForWrappingMethod(PsiElement start, boolean deep) {
481     PsiElement seeker = start;
482     List<PsiElement> ret = new ArrayList<PsiElement>(2);
483     while (seeker != null) {
484       PyFunction func = PsiTreeUtil.getParentOfType(seeker, PyFunction.class, true, PyClass.class);
485       if (func != null) {
486         PyClass cls = func.getContainingClass();
487         if (cls != null) {
488           ret.add(func);
489           ret.add(cls);
490           return ret;
491         }
492         else if (deep) {
493           ret.add(func);
494           seeker = func;
495         }
496         else {
497           return null; // no immediate class
498         }
499       }
500       else {
501         return null; // no function
502       }
503     }
504     return null;
505   }
506
507   public static boolean inSameFile(@NotNull PsiElement e1, @NotNull PsiElement e2) {
508     final PsiFile f1 = e1.getContainingFile();
509     final PsiFile f2 = e2.getContainingFile();
510     if (f1 == null || f2 == null) {
511       return false;
512     }
513     return f1 == f2;
514   }
515
516   public static boolean onSameLine(@NotNull PsiElement e1, @NotNull PsiElement e2) {
517     final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(e1.getProject());
518     final Document document = documentManager.getDocument(e1.getContainingFile());
519     if (document == null || document != documentManager.getDocument(e2.getContainingFile())) {
520       return false;
521     }
522     return document.getLineNumber(e1.getTextOffset()) == document.getLineNumber(e2.getTextOffset());
523   }
524
525   public static boolean isTopLevel(@NotNull PsiElement element) {
526     if (element instanceof StubBasedPsiElement) {
527       final StubElement stub = ((StubBasedPsiElement)element).getStub();
528       if (stub != null) {
529         final StubElement parentStub = stub.getParentStub();
530         if (parentStub != null) {
531           return parentStub.getPsi() instanceof PsiFile;
532         }
533       }
534     }
535     return ScopeUtil.getScopeOwner(element) instanceof PsiFile;
536   }
537
538   public static void deletePycFiles(String pyFilePath) {
539     if (pyFilePath.endsWith(".py")) {
540       List<File> filesToDelete = new ArrayList<File>();
541       File pyc = new File(pyFilePath + "c");
542       if (pyc.exists()) {
543         filesToDelete.add(pyc);
544       }
545       File pyo = new File(pyFilePath + "o");
546       if (pyo.exists()) {
547         filesToDelete.add(pyo);
548       }
549       final File file = new File(pyFilePath);
550       File pycache = new File(file.getParentFile(), PyNames.PYCACHE);
551       if (pycache.isDirectory()) {
552         final String shortName = FileUtil.getNameWithoutExtension(file);
553         Collections.addAll(filesToDelete, pycache.listFiles(new FileFilter() {
554           @Override
555           public boolean accept(File pathname) {
556             if (!FileUtilRt.extensionEquals(pathname.getName(), "pyc")) return false;
557             String nameWithMagic = FileUtil.getNameWithoutExtension(pathname);
558             return FileUtil.getNameWithoutExtension(nameWithMagic).equals(shortName);
559           }
560         }));
561       }
562       FileUtil.asyncDelete(filesToDelete);
563     }
564   }
565
566   public static String getElementNameWithoutExtension(PsiNamedElement psiNamedElement) {
567     return psiNamedElement instanceof PyFile
568            ? FileUtil.getNameWithoutExtension(((PyFile)psiNamedElement).getName())
569            : psiNamedElement.getName();
570   }
571
572   public static boolean hasUnresolvedAncestors(@NotNull PyClass cls, @NotNull TypeEvalContext context) {
573     for (PyClassLikeType type : cls.getAncestorTypes(context)) {
574       if (type == null) {
575         return true;
576       }
577     }
578     return false;
579   }
580
581   @NotNull
582   public static AccessDirection getPropertyAccessDirection(@NotNull PyFunction function) {
583     final Property property = function.getProperty();
584     if (property != null) {
585       if (property.getGetter().valueOrNull() == function) {
586         return AccessDirection.READ;
587       }
588       if (property.getSetter().valueOrNull() == function) {
589         return AccessDirection.WRITE;
590       }
591       else if (property.getDeleter().valueOrNull() == function) {
592         return AccessDirection.DELETE;
593       }
594     }
595     return AccessDirection.READ;
596   }
597
598   public static boolean deleteParameter(@NotNull final PyFunction problemFunction, int index) {
599     final PyParameterList parameterList = problemFunction.getParameterList();
600     final PyParameter[] parameters = parameterList.getParameters();
601     if (parameters.length <= 0) return false;
602
603     PsiElement first = parameters[index];
604     PsiElement last = parameters.length > index + 1 ? parameters[index + 1] : parameterList.getLastChild();
605     PsiElement prevSibling = last.getPrevSibling() != null ? last.getPrevSibling() : parameters[index];
606
607     parameterList.deleteChildRange(first, prevSibling);
608     return true;
609   }
610
611   public static void removeQualifier(@NotNull final PyReferenceExpression element) {
612     final PyExpression qualifier = element.getQualifier();
613     if (qualifier == null) return;
614
615     if (qualifier instanceof PyCallExpression) {
616       final StringBuilder newElement = new StringBuilder(element.getLastChild().getText());
617       final PyExpression callee = ((PyCallExpression)qualifier).getCallee();
618       if (callee instanceof PyReferenceExpression) {
619         final PyExpression calleeQualifier = ((PyReferenceExpression)callee).getQualifier();
620         if (calleeQualifier != null) {
621           newElement.insert(0, calleeQualifier.getText() + ".");
622         }
623       }
624       final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(element.getProject());
625       final PyExpression expression = elementGenerator.createExpressionFromText(LanguageLevel.forElement(element), newElement.toString());
626       element.replace(expression);
627     }
628     else {
629       final PsiElement dot = qualifier.getNextSibling();
630       if (dot != null) dot.delete();
631       qualifier.delete();
632     }
633   }
634
635   /**
636    * Returns string that represents element in string search.
637    *
638    * @param element element to search
639    * @return string that represents element
640    */
641   @NotNull
642   public static String computeElementNameForStringSearch(@NotNull final PsiElement element) {
643     if (element instanceof PyFile) {
644       return FileUtil.getNameWithoutExtension(((PyFile)element).getName());
645     }
646     if (element instanceof PsiDirectory) {
647       return ((PsiDirectory)element).getName();
648     }
649     // Magic literals are always represented by their string values
650     if ((element instanceof PyStringLiteralExpression) && PyMagicLiteralTools.isMagicLiteral(element)) {
651       final String name = ((StringLiteralExpression)element).getStringValue();
652       if (name != null) {
653         return name;
654       }
655     }
656     if (element instanceof PyElement) {
657       final String name = ((PyElement)element).getName();
658       if (name != null) {
659         return name;
660       }
661     }
662     return element.getNode().getText();
663   }
664
665   public static boolean isOwnScopeComprehension(@NotNull PyComprehensionElement comprehension) {
666     final boolean isAtLeast30 = LanguageLevel.forElement(comprehension).isAtLeast(LanguageLevel.PYTHON30);
667     final boolean isListComprehension = comprehension instanceof PyListCompExpression;
668     return !isListComprehension || isAtLeast30;
669   }
670
671   public static boolean hasCustomDecorators(@NotNull PyDecoratable decoratable) {
672     return PyKnownDecoratorUtil.hasNonBuiltinDecorator(decoratable, TypeEvalContext.codeInsightFallback(null));
673   }
674
675   public static boolean isDecoratedAsAbstract(@NotNull final PyDecoratable decoratable) {
676     return PyKnownDecoratorUtil.hasAbstractDecorator(decoratable, TypeEvalContext.codeInsightFallback(null));
677   }
678
679   public static ASTNode createNewName(PyElement element, String name) {
680     return PyElementGenerator.getInstance(element.getProject()).createNameIdentifier(name, LanguageLevel.forElement(element));
681   }
682
683   /**
684    * Finds element declaration by resolving its references top the top but not further than file (to prevent unstubing)
685    *
686    * @param element element to resolve
687    * @return its declaration
688    */
689   @NotNull
690   public static PsiElement resolveToTheTop(@NotNull final PsiElement elementToResolve) {
691     PsiElement currentElement = elementToResolve;
692     while (true) {
693       final PsiReference reference = currentElement.getReference();
694       if (reference == null) {
695         break;
696       }
697       final PsiElement resolve = reference.resolve();
698       if ((resolve == null) || resolve.equals(currentElement) || !inSameFile(resolve, currentElement)) {
699         break;
700       }
701       currentElement = resolve;
702     }
703     return currentElement;
704   }
705
706   /**
707    * Note that returned list may contain {@code null} items, e.g. for unresolved import elements, originally wrapped
708    * in {@link com.jetbrains.python.psi.resolve.ImportedResolveResult}.
709    */
710   @NotNull
711   public static List<PsiElement> multiResolveTopPriority(@NotNull PsiElement element, @NotNull PyResolveContext resolveContext) {
712     if (element instanceof PyReferenceOwner) {
713       final PsiPolyVariantReference ref = ((PyReferenceOwner)element).getReference(resolveContext);
714       return filterTopPriorityResults(ref.multiResolve(false));
715     }
716     else {
717       final PsiReference reference = element.getReference();
718       return reference != null ? Collections.singletonList(reference.resolve()) : Collections.<PsiElement>emptyList();
719     }
720   }
721
722   @NotNull
723   public static List<PsiElement> multiResolveTopPriority(@NotNull PsiPolyVariantReference reference) {
724     return filterTopPriorityResults(reference.multiResolve(false));
725   }
726
727   @NotNull
728   private static List<PsiElement> filterTopPriorityResults(@NotNull ResolveResult[] resolveResults) {
729     if (resolveResults.length == 0) {
730       return Collections.emptyList();
731     }
732     final List<PsiElement> filtered = new ArrayList<PsiElement>();
733     final int maxRate = getMaxRate(resolveResults);
734     for (ResolveResult resolveResult : resolveResults) {
735       final int rate = resolveResult instanceof RatedResolveResult ? ((RatedResolveResult)resolveResult).getRate() : 0;
736       if (rate >= maxRate) {
737         filtered.add(resolveResult.getElement());
738       }
739     }
740     return filtered;
741   }
742
743   private static int getMaxRate(@NotNull ResolveResult[] resolveResults) {
744     int maxRate = Integer.MIN_VALUE;
745     for (ResolveResult resolveResult : resolveResults) {
746       if (resolveResult instanceof RatedResolveResult) {
747         final int rate = ((RatedResolveResult)resolveResult).getRate();
748         if (rate > maxRate) {
749           maxRate = rate;
750         }
751       }
752     }
753     return maxRate;
754   }
755
756   /**
757    * Gets class init method
758    *
759    * @param pyClass class where to find init
760    * @return class init method if any
761    */
762   @Nullable
763   public static PyFunction getInitMethod(@NotNull final PyClass pyClass) {
764     return pyClass.findMethodByName(PyNames.INIT, false);
765   }
766
767   /**
768    * Returns Python language level for a virtual file.
769    *
770    * @see {@link LanguageLevel#forElement}
771    */
772   @NotNull
773   public static LanguageLevel getLanguageLevelForVirtualFile(@NotNull Project project,
774                                                              @NotNull VirtualFile virtualFile) {
775     if (virtualFile instanceof VirtualFileWindow) {
776       virtualFile = ((VirtualFileWindow)virtualFile).getDelegate();
777     }
778
779     // Most of the cases should be handled by this one, PyLanguageLevelPusher pushes folders only
780     final VirtualFile folder = virtualFile.getParent();
781     if (folder != null) {
782       LanguageLevel level = folder.getUserData(LanguageLevel.KEY);
783       if (level == null) level = PythonLanguageLevelPusher.getFileLanguageLevel(project, virtualFile);
784       return level;
785     }
786     else {
787       // However this allows us to setup language level per file manually
788       // in case when it is LightVirtualFile
789       final LanguageLevel level = virtualFile.getUserData(LanguageLevel.KEY);
790       if (level != null) return level;
791
792       if (ApplicationManager.getApplication().isUnitTestMode()) {
793         final LanguageLevel languageLevel = LanguageLevel.FORCE_LANGUAGE_LEVEL;
794         if (languageLevel != null) {
795           return languageLevel;
796         }
797       }
798       return guessLanguageLevelWithCaching(project);
799     }
800   }
801
802   public static void invalidateLanguageLevelCache(@NotNull Project project) {
803     project.putUserData(PythonLanguageLevelPusher.PYTHON_LANGUAGE_LEVEL, null);
804   }
805
806   @NotNull
807   public static LanguageLevel guessLanguageLevelWithCaching(@NotNull Project project) {
808     LanguageLevel languageLevel = project.getUserData(PythonLanguageLevelPusher.PYTHON_LANGUAGE_LEVEL);
809     if (languageLevel == null) {
810       languageLevel = guessLanguageLevel(project);
811       project.putUserData(PythonLanguageLevelPusher.PYTHON_LANGUAGE_LEVEL, languageLevel);
812     }
813
814     return languageLevel;
815   }
816
817   @NotNull
818   public static LanguageLevel guessLanguageLevel(@NotNull Project project) {
819     final ModuleManager moduleManager = ModuleManager.getInstance(project);
820     if (moduleManager != null) {
821       LanguageLevel maxLevel = null;
822       for (Module projectModule : moduleManager.getModules()) {
823         final Sdk sdk = PythonSdkType.findPythonSdk(projectModule);
824         if (sdk != null) {
825           final LanguageLevel level = PythonSdkType.getLanguageLevelForSdk(sdk);
826           if (maxLevel == null || maxLevel.isOlderThan(level)) {
827             maxLevel = level;
828           }
829         }
830       }
831       if (maxLevel != null) {
832         return maxLevel;
833       }
834     }
835     return LanguageLevel.getDefault();
836   }
837
838   /**
839    * Clone of C# "as" operator.
840    * Checks if expression has correct type and casts it if it has. Returns null otherwise.
841    * It saves coder from "instanceof / cast" chains.
842    *
843    * @param expression expression to check
844    * @param clazz      class to cast
845    * @param <T>        class to cast
846    * @return expression casted to appropriate type (if could be casted). Null otherwise.
847    */
848   @Nullable
849   @SuppressWarnings("unchecked")
850   public static <T> T as(@Nullable final Object expression, @NotNull final Class<T> clazz) {
851     return ObjectUtils.tryCast(expression, clazz);
852   }
853
854   // TODO: Move to PsiElement?
855
856   /**
857    * Searches for references injected to element with certain type
858    *
859    * @param element       element to search injected references for
860    * @param expectedClass expected type of element reference resolved to
861    * @param <T>           expected type of element reference resolved to
862    * @return resolved element if found or null if not found
863    */
864   @Nullable
865   public static <T extends PsiElement> T findReference(@NotNull final PsiElement element, @NotNull final Class<T> expectedClass) {
866     for (final PsiReference reference : element.getReferences()) {
867       final T result = as(reference.resolve(), expectedClass);
868       if (result != null) {
869         return result;
870       }
871     }
872     return null;
873   }
874
875
876   /**
877    * Converts collection to list of certain type
878    *
879    * @param expression   expression of collection type
880    * @param elementClass expected element type
881    * @param <T>          expected element type
882    * @return list of elements of expected element type
883    */
884   @NotNull
885   public static <T> List<T> asList(@Nullable final Collection<?> expression, @NotNull final Class<T> elementClass) {
886     if ((expression == null) || expression.isEmpty()) {
887       return Collections.emptyList();
888     }
889     final List<T> result = new ArrayList<T>();
890     for (final Object element : expression) {
891       final T toAdd = as(element, elementClass);
892       if (toAdd != null) {
893         result.add(toAdd);
894       }
895     }
896     return result;
897   }
898
899   public static class KnownDecoratorProviderHolder {
900     public static PyKnownDecoratorProvider[] KNOWN_DECORATOR_PROVIDERS = Extensions.getExtensions(PyKnownDecoratorProvider.EP_NAME);
901
902     private KnownDecoratorProviderHolder() {
903     }
904   }
905
906   /**
907    * If argument is a PsiDirectory, turn it into a PsiFile that points to __init__.py in that directory.
908    * If there's no __init__.py there, null is returned, there's no point to resolve to a dir which is not a package.
909    * Alas, resolve() and multiResolve() can't return anything but a PyFile or PsiFileImpl.isPsiUpToDate() would fail.
910    * This is because isPsiUpToDate() relies on identity of objects returned by FileViewProvider.getPsi().
911    * If we ever need to exactly tell a dir from __init__.py, that logic has to change.
912    *
913    * @param target a resolve candidate.
914    * @return a PsiFile if target was a PsiDirectory, or null, or target unchanged.
915    */
916   @Nullable
917   public static PsiElement turnDirIntoInit(@Nullable PsiElement target) {
918     if (target instanceof PsiDirectory) {
919       final PsiDirectory dir = (PsiDirectory)target;
920       final PsiFile file = dir.findFile(PyNames.INIT_DOT_PY);
921       if (file != null) {
922         return file; // ResolveImportUtil will extract directory part as needed, everyone else are better off with a file.
923       }
924       else {
925         return null;
926       } // dir without __init__.py does not resolve
927     }
928     else {
929       return target;
930     } // don't touch non-dirs
931   }
932
933   /**
934    * If directory is a PsiDirectory, that is also a valid Python package, return PsiFile that points to __init__.py,
935    * if such file exists, or directory itself (i.e. namespace package). Otherwise, return {@code null}.
936    * Unlike {@link #turnDirIntoInit(PsiElement)} this function handles namespace packages and
937    * accepts only PsiDirectories as target.
938    *
939    * @param directory directory to check
940    * @param anchor optional PSI element to determine language level as for {@link #isPackage(PsiDirectory, PsiElement)}
941    * @return PsiFile or PsiDirectory, if target is a Python package and {@code null} null otherwise
942    */
943   @Nullable
944   public static PsiElement getPackageElement(@NotNull PsiDirectory directory, @Nullable PsiElement anchor) {
945     if (isPackage(directory, anchor)) {
946       final PsiElement init = turnDirIntoInit(directory);
947       if (init != null) {
948         return init;
949       }
950       return directory;
951     }
952     return null;
953   }
954
955   /**
956    * If target is a Python module named __init__.py file, return its directory. Otherwise return target unchanged.
957    * @param target PSI element to check
958    * @return PsiDirectory or target unchanged
959    */
960   @Contract("null -> null; !null -> !null")
961   @Nullable
962   public static PsiElement turnInitIntoDir(@Nullable PsiElement target) {
963     if (target instanceof PyFile && isPackage((PsiFile)target)) {
964       return ((PsiFile)target).getContainingDirectory();
965     }
966     return target;
967   }
968
969   /**
970    * @see #isPackage(PsiDirectory, boolean, PsiElement)
971    */
972   public static boolean isPackage(@NotNull PsiDirectory directory, @Nullable PsiElement anchor) {
973     return isPackage(directory, true, anchor);
974   }
975
976   /**
977    * Checks that given PsiDirectory can be treated as Python package, i.e. it's either contains __init__.py or it's a namespace package
978    * (effectively any directory in Python 3.3 and above). Setuptools namespace packages can be checked as well, but it requires access to
979    * {@link PySetuptoolsNamespaceIndex} and may slow things down during update of project indexes.
980    * Also note that this method does not check that directory itself and its parents have valid importable names,
981    * use {@link PyNames#isIdentifier(String)} for this purpose.
982    *
983    * @param directory PSI directory to check
984    * @param checkSetupToolsPackages whether setuptools namespace packages should be considered as well
985    * @param anchor    optional anchor element to determine language level
986    * @return whether given directory is Python package
987    *
988    * @see PyNames#isIdentifier(String)
989    */
990   public static boolean isPackage(@NotNull PsiDirectory directory, boolean checkSetupToolsPackages, @Nullable PsiElement anchor) {
991     if (directory.findFile(PyNames.INIT_DOT_PY) != null) {
992       return true;
993     }
994     final LanguageLevel level = anchor != null ?
995                                 LanguageLevel.forElement(anchor) :
996                                 getLanguageLevelForVirtualFile(directory.getProject(), directory.getVirtualFile());
997     if (level.isAtLeast(LanguageLevel.PYTHON33)) {
998       return true;
999     }
1000     return checkSetupToolsPackages && isSetuptoolsNamespacePackage(directory);
1001   }
1002
1003   public static boolean isPackage(@NotNull PsiFile file) {
1004     return PyNames.INIT_DOT_PY.equals(file.getName());
1005   }
1006
1007   private static boolean isSetuptoolsNamespacePackage(@NotNull PsiDirectory directory) {
1008     final String packagePath = getPackagePath(directory);
1009     return packagePath != null && !PySetuptoolsNamespaceIndex.find(packagePath, directory.getProject()).isEmpty();
1010   }
1011
1012   @Nullable
1013   private static String getPackagePath(@NotNull PsiDirectory directory) {
1014     final QualifiedName name = QualifiedNameFinder.findShortestImportableQName(directory);
1015     return name != null ? name.toString() : null;
1016   }
1017
1018   /**
1019    * Counts initial underscores of an identifier.
1020    *
1021    * @param name identifier
1022    * @return 0 if no initial underscores found, 1 if there's only one underscore, 2 if there's two or more initial underscores.
1023    */
1024   public static int getInitialUnderscores(String name) {
1025     if (name == null) {
1026       return 0;
1027     }
1028     int underscores = 0;
1029     if (name.startsWith("__")) {
1030       underscores = 2;
1031     }
1032     else if (name.startsWith("_")) underscores = 1;
1033     return underscores;
1034   }
1035
1036   /**
1037    * Tries to find nearest parent that conceals names defined inside it. Such elements are 'class' and 'def':
1038    * anything defined within it does not seep to the namespace below them, but is concealed within.
1039    *
1040    * @param elt starting point of search.
1041    * @return 'class' or 'def' element, or null if not found.
1042    * @deprecated Use {@link ScopeUtil#getScopeOwner} instead.
1043    */
1044   @Deprecated
1045   @Nullable
1046   public static PsiElement getConcealingParent(PsiElement elt) {
1047     if (elt == null || elt instanceof PsiFile) {
1048       return null;
1049     }
1050     PsiElement parent = PsiTreeUtil.getStubOrPsiParent(elt);
1051     boolean jump_over = false;
1052     while (parent != null) {
1053       if (parent instanceof PyClass || parent instanceof PyCallable) {
1054         if (jump_over) {
1055           jump_over = false;
1056         }
1057         else {
1058           return parent;
1059         }
1060       }
1061       else if (parent instanceof PyDecoratorList) {
1062         // decorators PSI is inside decorated things but their namespace is outside
1063         jump_over = true;
1064       }
1065       else if (parent instanceof PsiFileSystemItem) {
1066         break;
1067       }
1068       parent = PsiTreeUtil.getStubOrPsiParent(parent);
1069     }
1070     return null;
1071   }
1072
1073   /**
1074    * @param name
1075    * @return true iff the name looks like a class-private one, starting with two underscores but not ending with two underscores.
1076    */
1077   public static boolean isClassPrivateName(String name) {
1078     return name.startsWith("__") && !name.endsWith("__");
1079   }
1080
1081   public static boolean isSpecialName(@NotNull String name) {
1082     return name.length() > 4 && name.startsWith("__") && name.endsWith("__");
1083   }
1084
1085   /**
1086    * Constructs new lookup element for completion of keyword argument with equals sign appended.
1087    *
1088    * @param name    name of the parameter
1089    * @param project project instance to check code style settings and surround equals sign with spaces if necessary
1090    * @return lookup element
1091    */
1092   @NotNull
1093   public static LookupElement createNamedParameterLookup(@NotNull String name, @Nullable Project project) {
1094     final String suffix;
1095     if (CodeStyleSettingsManager.getSettings(project).getCustomSettings(PyCodeStyleSettings.class).SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT) {
1096       suffix = " = ";
1097     }
1098     else {
1099       suffix = "=";
1100     }
1101     LookupElementBuilder lookupElementBuilder = LookupElementBuilder.create(name + suffix).withIcon(PlatformIcons.PARAMETER_ICON);
1102     lookupElementBuilder = lookupElementBuilder.withInsertHandler(OverwriteEqualsInsertHandler.INSTANCE);
1103     return PrioritizedLookupElement.withGrouping(lookupElementBuilder, 1);
1104   }
1105
1106   /**
1107    * Peels argument expression of parentheses and of keyword argument wrapper
1108    *
1109    * @param expr an item of getArguments() array
1110    * @return expression actually passed as argument
1111    */
1112   @Nullable
1113   public static PyExpression peelArgument(PyExpression expr) {
1114     while (expr instanceof PyParenthesizedExpression) expr = ((PyParenthesizedExpression)expr).getContainedExpression();
1115     if (expr instanceof PyKeywordArgument) expr = ((PyKeywordArgument)expr).getValueExpression();
1116     return expr;
1117   }
1118
1119   public static String getFirstParameterName(PyFunction container) {
1120     String selfName = PyNames.CANONICAL_SELF;
1121     if (container != null) {
1122       final PyParameter[] params = container.getParameterList().getParameters();
1123       if (params.length > 0) {
1124         final PyNamedParameter named = params[0].getAsNamed();
1125         if (named != null) {
1126           selfName = named.getName();
1127         }
1128       }
1129     }
1130     return selfName;
1131   }
1132
1133   /**
1134    * @return Source roots <strong>and</strong> content roots for element's project
1135    */
1136   @NotNull
1137   public static Collection<VirtualFile> getSourceRoots(@NotNull PsiElement foothold) {
1138     final Module module = ModuleUtilCore.findModuleForPsiElement(foothold);
1139     if (module != null) {
1140       return getSourceRoots(module);
1141     }
1142     return Collections.emptyList();
1143   }
1144
1145   /**
1146    * @return Source roots <strong>and</strong> content roots for module
1147    */
1148   @NotNull
1149   public static Collection<VirtualFile> getSourceRoots(@NotNull Module module) {
1150     final Set<VirtualFile> result = new LinkedHashSet<VirtualFile>();
1151     final ModuleRootManager manager = ModuleRootManager.getInstance(module);
1152     Collections.addAll(result, manager.getSourceRoots());
1153     Collections.addAll(result, manager.getContentRoots());
1154     return result;
1155   }
1156
1157   @Nullable
1158   public static VirtualFile findInRoots(Module module, String path) {
1159     if (module != null) {
1160       for (VirtualFile root : getSourceRoots(module)) {
1161         VirtualFile file = root.findFileByRelativePath(path);
1162         if (file != null) {
1163           return file;
1164         }
1165       }
1166     }
1167     return null;
1168   }
1169
1170   @Nullable
1171   public static List<String> getStringListFromTargetExpression(PyTargetExpression attr) {
1172     return strListValue(attr.findAssignedValue());
1173   }
1174
1175   @Nullable
1176   public static List<String> strListValue(PyExpression value) {
1177     while (value instanceof PyParenthesizedExpression) {
1178       value = ((PyParenthesizedExpression)value).getContainedExpression();
1179     }
1180     if (value instanceof PySequenceExpression) {
1181       final PyExpression[] elements = ((PySequenceExpression)value).getElements();
1182       List<String> result = new ArrayList<String>(elements.length);
1183       for (PyExpression element : elements) {
1184         if (!(element instanceof PyStringLiteralExpression)) {
1185           return null;
1186         }
1187         result.add(((PyStringLiteralExpression)element).getStringValue());
1188       }
1189       return result;
1190     }
1191     return null;
1192   }
1193
1194   @NotNull
1195   public static Map<String, PyExpression> dictValue(@NotNull PyDictLiteralExpression dict) {
1196     Map<String, PyExpression> result = Maps.newLinkedHashMap();
1197     for (PyKeyValueExpression keyValue : dict.getElements()) {
1198       PyExpression key = keyValue.getKey();
1199       PyExpression value = keyValue.getValue();
1200       if (key instanceof PyStringLiteralExpression) {
1201         result.put(((PyStringLiteralExpression)key).getStringValue(), value);
1202       }
1203     }
1204     return result;
1205   }
1206
1207   /**
1208    * @param what     thing to search for
1209    * @param variants things to search among
1210    * @return true iff what.equals() one of the variants.
1211    */
1212   public static <T> boolean among(@NotNull T what, T... variants) {
1213     for (T s : variants) {
1214       if (what.equals(s)) return true;
1215     }
1216     return false;
1217   }
1218
1219   @Nullable
1220   public static String getKeywordArgumentString(PyCallExpression expr, String keyword) {
1221     return PyPsiUtils.strValue(expr.getKeywordArgument(keyword));
1222   }
1223
1224   public static boolean isExceptionClass(PyClass pyClass) {
1225     if (isBaseException(pyClass.getQualifiedName())) {
1226       return true;
1227     }
1228     for (PyClassLikeType type : pyClass.getAncestorTypes(TypeEvalContext.codeInsightFallback(pyClass.getProject()))) {
1229       if (type != null && isBaseException(type.getClassQName())) {
1230         return true;
1231       }
1232     }
1233     return false;
1234   }
1235
1236   private static boolean isBaseException(String name) {
1237     return name != null && (name.contains("BaseException") || name.startsWith("exceptions."));
1238   }
1239
1240   public static class MethodFlags {
1241
1242     private boolean myIsStaticMethod;
1243     private boolean myIsMetaclassMethod;
1244     private boolean myIsSpecialMetaclassMethod;
1245     private boolean myIsClassMethod;
1246
1247     /**
1248      * @return true iff the method belongs to a metaclass (an ancestor of 'type').
1249      */
1250     public boolean isMetaclassMethod() {
1251       return myIsMetaclassMethod;
1252     }
1253
1254     /**
1255      * @return iff isMetaclassMethod and the method is either __init__ or __call__.
1256      */
1257     public boolean isSpecialMetaclassMethod() {
1258       return myIsSpecialMetaclassMethod;
1259     }
1260
1261     public boolean isStaticMethod() {
1262       return myIsStaticMethod;
1263     }
1264
1265     public boolean isClassMethod() {
1266       return myIsClassMethod;
1267     }
1268
1269     private MethodFlags(boolean isClassMethod, boolean isStaticMethod, boolean isMetaclassMethod, boolean isSpecialMetaclassMethod) {
1270       myIsClassMethod = isClassMethod;
1271       myIsStaticMethod = isStaticMethod;
1272       myIsMetaclassMethod = isMetaclassMethod;
1273       myIsSpecialMetaclassMethod = isSpecialMetaclassMethod;
1274     }
1275
1276     /**
1277      * @param node a function
1278      * @return a new flags object, or null if the function is not a method
1279      */
1280     @Nullable
1281     public static MethodFlags of(@NotNull PyFunction node) {
1282       PyClass cls = node.getContainingClass();
1283       if (cls != null) {
1284         PyFunction.Modifier modifier = node.getModifier();
1285         boolean isMetaclassMethod = false;
1286         PyClass type_cls = PyBuiltinCache.getInstance(node).getClass("type");
1287         for (PyClass ancestor_cls : cls.getAncestorClasses(null)) {
1288           if (ancestor_cls == type_cls) {
1289             isMetaclassMethod = true;
1290             break;
1291           }
1292         }
1293         final String method_name = node.getName();
1294         boolean isSpecialMetaclassMethod = isMetaclassMethod && method_name != null && among(method_name, PyNames.INIT, "__call__");
1295         return new MethodFlags(modifier == CLASSMETHOD, modifier == STATICMETHOD, isMetaclassMethod, isSpecialMetaclassMethod);
1296       }
1297       return null;
1298     }
1299
1300     //TODO: Doc
1301     public boolean isInstanceMethod() {
1302       return !(myIsClassMethod || myIsStaticMethod);
1303     }
1304   }
1305
1306   public static boolean isSuperCall(@NotNull PyCallExpression node) {
1307     PyClass klass = PsiTreeUtil.getParentOfType(node, PyClass.class);
1308     if (klass == null) return false;
1309     PyExpression callee = node.getCallee();
1310     if (callee == null) return false;
1311     String name = callee.getName();
1312     if (PyNames.SUPER.equals(name)) {
1313       PsiReference reference = callee.getReference();
1314       if (reference == null) return false;
1315       PsiElement resolved = reference.resolve();
1316       PyBuiltinCache cache = PyBuiltinCache.getInstance(node);
1317       if (resolved != null && cache.isBuiltin(resolved)) {
1318         PyExpression[] args = node.getArguments();
1319         if (args.length > 0) {
1320           String firstArg = args[0].getText();
1321           if (firstArg.equals(klass.getName()) || firstArg.equals(PyNames.CANONICAL_SELF + "." + PyNames.__CLASS__)) {
1322             return true;
1323           }
1324           for (PyClass s : klass.getAncestorClasses(null)) {
1325             if (firstArg.equals(s.getName())) {
1326               return true;
1327             }
1328           }
1329         }
1330         else {
1331           return true;
1332         }
1333       }
1334     }
1335     return false;
1336   }
1337
1338   @NotNull
1339   public static PyFile getOrCreateFile(String path, Project project) {
1340     final VirtualFile vfile = LocalFileSystem.getInstance().findFileByIoFile(new File(path));
1341     final PsiFile psi;
1342     if (vfile == null) {
1343       final File file = new File(path);
1344       try {
1345         final VirtualFile baseDir = project.getBaseDir();
1346         final FileTemplateManager fileTemplateManager = FileTemplateManager.getInstance(project);
1347         final FileTemplate template = fileTemplateManager.getInternalTemplate("Python Script");
1348         final Properties properties = fileTemplateManager.getDefaultProperties();
1349         properties.setProperty("NAME", FileUtil.getNameWithoutExtension(file.getName()));
1350         final String content = (template != null) ? template.getText(properties) : null;
1351         psi = PyExtractSuperclassHelper.placeFile(project,
1352                                                   StringUtil.notNullize(
1353                                                     file.getParent(),
1354                                                     baseDir != null ? baseDir
1355                                                       .getPath() : "."
1356                                                   ),
1357                                                   file.getName(),
1358                                                   content
1359         );
1360       }
1361       catch (IOException e) {
1362         throw new IncorrectOperationException(String.format("Cannot create file '%s'", path));
1363       }
1364     }
1365     else {
1366       psi = PsiManager.getInstance(project).findFile(vfile);
1367     }
1368     if (!(psi instanceof PyFile)) {
1369       throw new IncorrectOperationException(PyBundle.message(
1370         "refactoring.move.module.members.error.cannot.place.elements.into.nonpython.file"));
1371     }
1372     return (PyFile)psi;
1373   }
1374
1375   /**
1376    * counts elements in iterable
1377    *
1378    * @param expression to count containing elements (iterable)
1379    * @return element count
1380    */
1381   public static int getElementsCount(PyExpression expression, TypeEvalContext evalContext) {
1382     int valuesLength = -1;
1383     PyType type = evalContext.getType(expression);
1384     if (type instanceof PyTupleType) {
1385       valuesLength = ((PyTupleType)type).getElementCount();
1386     }
1387     else if (type instanceof PyNamedTupleType) {
1388       valuesLength = ((PyNamedTupleType)type).getElementCount();
1389     }
1390     else if (expression instanceof PySequenceExpression) {
1391       valuesLength = ((PySequenceExpression)expression).getElements().length;
1392     }
1393     else if (expression instanceof PyStringLiteralExpression) {
1394       valuesLength = ((PyStringLiteralExpression)expression).getStringValue().length();
1395     }
1396     else if (expression instanceof PyNumericLiteralExpression) {
1397       valuesLength = 1;
1398     }
1399     else if (expression instanceof PyCallExpression) {
1400       PyCallExpression call = (PyCallExpression)expression;
1401       if (call.isCalleeText("dict")) {
1402         valuesLength = call.getArguments().length;
1403       }
1404       else if (call.isCalleeText("tuple")) {
1405         PyExpression[] arguments = call.getArguments();
1406         if (arguments.length > 0 && arguments[0] instanceof PySequenceExpression) {
1407           valuesLength = ((PySequenceExpression)arguments[0]).getElements().length;
1408         }
1409       }
1410     }
1411     return valuesLength;
1412   }
1413
1414   @Nullable
1415   public static PsiElement findPrevAtOffset(PsiFile psiFile, int caretOffset, Class... toSkip) {
1416     PsiElement element;
1417     if (caretOffset < 0) {
1418       return null;
1419     }
1420     int lineStartOffset = 0;
1421     final Document document = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile);
1422     if (document != null) {
1423       int lineNumber = document.getLineNumber(caretOffset);
1424       lineStartOffset = document.getLineStartOffset(lineNumber);
1425     }
1426     do {
1427       caretOffset--;
1428       element = psiFile.findElementAt(caretOffset);
1429     }
1430     while (caretOffset >= lineStartOffset && instanceOf(element, toSkip));
1431     return instanceOf(element, toSkip) ? null : element;
1432   }
1433
1434   @Nullable
1435   public static PsiElement findNonWhitespaceAtOffset(PsiFile psiFile, int caretOffset) {
1436     PsiElement element = findNextAtOffset(psiFile, caretOffset, PsiWhiteSpace.class);
1437     if (element == null) {
1438       element = findPrevAtOffset(psiFile, caretOffset - 1, PsiWhiteSpace.class);
1439     }
1440     return element;
1441   }
1442
1443   @Nullable
1444   public static PsiElement findElementAtOffset(PsiFile psiFile, int caretOffset) {
1445     PsiElement element = findPrevAtOffset(psiFile, caretOffset);
1446     if (element == null) {
1447       element = findNextAtOffset(psiFile, caretOffset);
1448     }
1449     return element;
1450   }
1451
1452   @Nullable
1453   public static PsiElement findNextAtOffset(@NotNull final PsiFile psiFile, int caretOffset, Class... toSkip) {
1454     PsiElement element = psiFile.findElementAt(caretOffset);
1455     if (element == null) {
1456       return null;
1457     }
1458
1459     final Document document = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile);
1460     int lineEndOffset = 0;
1461     if (document != null) {
1462       int lineNumber = document.getLineNumber(caretOffset);
1463       lineEndOffset = document.getLineEndOffset(lineNumber);
1464     }
1465     while (caretOffset < lineEndOffset && instanceOf(element, toSkip)) {
1466       caretOffset++;
1467       element = psiFile.findElementAt(caretOffset);
1468     }
1469     return instanceOf(element, toSkip) ? null : element;
1470   }
1471
1472   /**
1473    * Adds element to statement list to the correct place according to its dependencies.
1474    *
1475    * @param element       to insert
1476    * @param statementList where element should be inserted
1477    * @return inserted element
1478    */
1479   public static <T extends PyElement> T addElementToStatementList(@NotNull final T element,
1480                                                                   @NotNull final PyStatementList statementList) {
1481     PsiElement before = null;
1482     PsiElement after = null;
1483     for (final PyStatement statement : statementList.getStatements()) {
1484       if (PyDependenciesComparator.depends(element, statement)) {
1485         after = statement;
1486       }
1487       else if (PyDependenciesComparator.depends(statement, element)) {
1488         before = statement;
1489       }
1490     }
1491     final PsiElement result;
1492     if (after != null) {
1493
1494       result = statementList.addAfter(element, after);
1495     }
1496     else if (before != null) {
1497       result = statementList.addBefore(element, before);
1498     }
1499     else {
1500       result = addElementToStatementList(element, statementList, true);
1501     }
1502     @SuppressWarnings("unchecked") // Inserted element can't have different type
1503     final T resultCasted = (T)result;
1504     return resultCasted;
1505   }
1506
1507   /**
1508    * Inserts specified element into the statement list either at the beginning or at its end. If new element is going to be
1509    * inserted at the beginning, any preceding docstrings and/or calls to super methods will be skipped.
1510    * Moreover if statement list previously didn't contain any statements, explicit new line and indentation will be inserted in
1511    * front of it.
1512    *
1513    * @param element        element to insert
1514    * @param statementList  statement list
1515    * @param toTheBeginning whether to insert element at the beginning or at the end of the statement list
1516    * @return actually inserted element as for {@link PsiElement#add(PsiElement)}
1517    */
1518   @NotNull
1519   public static PsiElement addElementToStatementList(@NotNull PsiElement element,
1520                                                      @NotNull PyStatementList statementList,
1521                                                      boolean toTheBeginning) {
1522     final boolean statementListWasEmpty = statementList.getStatements().length == 0;
1523     final PsiElement firstChild = statementList.getFirstChild();
1524     if (firstChild == statementList.getLastChild() && firstChild instanceof PyPassStatement) {
1525       element = firstChild.replace(element);
1526     }
1527     else {
1528       final PyStatement[] statements = statementList.getStatements();
1529       if (toTheBeginning && statements.length > 0) {
1530         final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(statementList, PyDocStringOwner.class);
1531         PyStatement anchor = statements[0];
1532         if (docStringOwner != null && anchor instanceof PyExpressionStatement &&
1533             ((PyExpressionStatement)anchor).getExpression() == docStringOwner.getDocStringExpression()) {
1534           final PyStatement next = PsiTreeUtil.getNextSiblingOfType(anchor, PyStatement.class);
1535           if (next == null) {
1536             return statementList.addAfter(element, anchor);
1537           }
1538           anchor = next;
1539         }
1540         while (anchor instanceof PyExpressionStatement) {
1541           final PyExpression expression = ((PyExpressionStatement)anchor).getExpression();
1542           if (expression instanceof PyCallExpression) {
1543             final PyExpression callee = ((PyCallExpression)expression).getCallee();
1544             if ((isSuperCall((PyCallExpression)expression) || (callee != null && PyNames.INIT.equals(callee.getName())))) {
1545               final PyStatement next = PsiTreeUtil.getNextSiblingOfType(anchor, PyStatement.class);
1546               if (next == null) {
1547                 return statementList.addAfter(element, anchor);
1548               }
1549               anchor = next;
1550               continue;
1551             }
1552           }
1553           break;
1554         }
1555         element = statementList.addBefore(element, anchor);
1556       }
1557       else {
1558         element = statementList.add(element);
1559       }
1560     }
1561     if (statementListWasEmpty) {
1562       final PsiElement parent = statementList.getParent();
1563       if (parent instanceof PyStatementListContainer) {
1564         final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(parent.getProject());
1565         final PsiFile pyFile = parent.getContainingFile();
1566         final Document document = documentManager.getDocument(pyFile);
1567         if (document != null && document.getLineNumber(parent.getTextOffset()) == document.getLineNumber(statementList.getTextOffset())) {
1568           final CodeStyleSettings codeStyleManager = CodeStyleSettingsManager.getSettings(parent.getProject());
1569           final IndentOptions indentOptions = codeStyleManager.getCommonSettings(pyFile.getLanguage()).getIndentOptions();
1570           final int indentSize = indentOptions.INDENT_SIZE;
1571           final String indentation = StringUtil.repeatSymbol(' ', PyPsiUtils.getElementIndentation(parent) + indentSize);
1572           documentManager.doPostponedOperationsAndUnblockDocument(document);
1573           document.insertString(statementList.getTextOffset(), "\n" + indentation);
1574           documentManager.commitDocument(document);
1575         }
1576       }
1577     }
1578     return element;
1579   }
1580
1581   @NotNull
1582   public static List<List<PyParameter>> getOverloadedParametersSet(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
1583     final List<List<PyParameter>> parametersSet = getOverloadedParametersSet(context.getType(callable), context);
1584     return parametersSet != null ? parametersSet : Collections.singletonList(Arrays.asList(callable.getParameterList().getParameters()));
1585   }
1586
1587   @Nullable
1588   private static List<PyParameter> getParametersOfCallableType(@NotNull PyCallableType type, @NotNull TypeEvalContext context) {
1589     final List<PyCallableParameter> callableTypeParameters = type.getParameters(context);
1590     if (callableTypeParameters != null) {
1591       boolean allParametersDefined = true;
1592       final List<PyParameter> parameters = new ArrayList<PyParameter>();
1593       for (PyCallableParameter callableParameter : callableTypeParameters) {
1594         final PyParameter parameter = callableParameter.getParameter();
1595         if (parameter == null) {
1596           allParametersDefined = false;
1597           break;
1598         }
1599         parameters.add(parameter);
1600       }
1601       if (allParametersDefined) {
1602         return parameters;
1603       }
1604     }
1605     return null;
1606   }
1607
1608   @Nullable
1609   private static List<List<PyParameter>> getOverloadedParametersSet(@Nullable PyType type, @NotNull TypeEvalContext context) {
1610     if (type instanceof PyUnionType) {
1611       type = ((PyUnionType)type).excludeNull(context);
1612     }
1613
1614     if (type instanceof PyCallableType) {
1615       final List<PyParameter> results = getParametersOfCallableType((PyCallableType)type, context);
1616       if (results != null) {
1617         return Collections.singletonList(results);
1618       }
1619     }
1620     else if (type instanceof PyUnionType) {
1621       final List<List<PyParameter>> results = new ArrayList<List<PyParameter>>();
1622       final Collection<PyType> members = ((PyUnionType)type).getMembers();
1623       for (PyType member : members) {
1624         if (member instanceof PyCallableType) {
1625           final List<PyParameter> parameters = getParametersOfCallableType((PyCallableType)member, context);
1626           if (parameters != null) {
1627             results.add(parameters);
1628           }
1629         }
1630       }
1631       if (!results.isEmpty()) {
1632         return results;
1633       }
1634     }
1635
1636     return null;
1637   }
1638
1639   @NotNull
1640   public static List<PyParameter> getParameters(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
1641     final List<List<PyParameter>> parametersSet = getOverloadedParametersSet(callable, context);
1642     assert !parametersSet.isEmpty();
1643     return parametersSet.get(0);
1644   }
1645
1646   public static boolean isSignatureCompatibleTo(@NotNull PyCallable callable, @NotNull PyCallable otherCallable,
1647                                                 @NotNull TypeEvalContext context) {
1648     final List<PyParameter> parameters = getParameters(callable, context);
1649     final List<PyParameter> otherParameters = getParameters(otherCallable, context);
1650     final int optionalCount = optionalParametersCount(parameters);
1651     final int otherOptionalCount = optionalParametersCount(otherParameters);
1652     final int requiredCount = requiredParametersCount(callable, parameters);
1653     final int otherRequiredCount = requiredParametersCount(otherCallable, otherParameters);
1654     if (hasPositionalContainer(otherParameters) || hasKeywordContainer(otherParameters)) {
1655       if (otherParameters.size() == specialParametersCount(otherCallable, otherParameters)) {
1656         return true;
1657       }
1658     }
1659     if (hasPositionalContainer(parameters) || hasKeywordContainer(parameters)) {
1660       return requiredCount <= otherRequiredCount;
1661     }
1662     return requiredCount <= otherRequiredCount && parameters.size() >= otherParameters.size() && optionalCount >= otherOptionalCount;
1663   }
1664
1665   private static int optionalParametersCount(@NotNull List<PyParameter> parameters) {
1666     int n = 0;
1667     for (PyParameter parameter : parameters) {
1668       if (parameter.hasDefaultValue()) {
1669         n++;
1670       }
1671     }
1672     return n;
1673   }
1674
1675   private static int requiredParametersCount(@NotNull PyCallable callable, @NotNull List<PyParameter> parameters) {
1676     return parameters.size() - optionalParametersCount(parameters) - specialParametersCount(callable, parameters);
1677   }
1678
1679   private static int specialParametersCount(@NotNull PyCallable callable, @NotNull List<PyParameter> parameters) {
1680     int n = 0;
1681     if (hasPositionalContainer(parameters)) {
1682       n++;
1683     }
1684     if (hasKeywordContainer(parameters)) {
1685       n++;
1686     }
1687     if (callable.asMethod() != null) {
1688       n++;
1689     }
1690     else {
1691       if (parameters.size() > 0) {
1692         final PyParameter first = parameters.get(0);
1693         if (PyNames.CANONICAL_SELF.equals(first.getName())) {
1694           n++;
1695         }
1696       }
1697     }
1698     return n;
1699   }
1700
1701   private static boolean hasPositionalContainer(@NotNull List<PyParameter> parameters) {
1702     for (PyParameter parameter : parameters) {
1703       if (parameter instanceof PyNamedParameter && ((PyNamedParameter)parameter).isPositionalContainer()) {
1704         return true;
1705       }
1706     }
1707     return false;
1708   }
1709
1710   private static boolean hasKeywordContainer(@NotNull List<PyParameter> parameters) {
1711     for (PyParameter parameter : parameters) {
1712       if (parameter instanceof PyNamedParameter && ((PyNamedParameter)parameter).isKeywordContainer()) {
1713         return true;
1714       }
1715     }
1716     return false;
1717   }
1718
1719   public static boolean isInit(@NotNull final PyFunction function) {
1720     return PyNames.INIT.equals(function.getName());
1721   }
1722
1723   /**
1724    * Filters out {@link PyMemberInfo}
1725    * that should not be displayed in this refactoring (like object)
1726    *
1727    * @param pyMemberInfos collection to sort
1728    * @return sorted collection
1729    */
1730   @NotNull
1731   public static Collection<PyMemberInfo<PyElement>> filterOutObject(@NotNull final Collection<PyMemberInfo<PyElement>> pyMemberInfos) {
1732     return Collections2.filter(pyMemberInfos, new ObjectPredicate(false));
1733   }
1734
1735   public static boolean isStarImportableFrom(@NotNull String name, @NotNull PyFile file) {
1736     final List<String> dunderAll = file.getDunderAll();
1737     return dunderAll != null ? dunderAll.contains(name) : !name.startsWith("_");
1738   }
1739
1740   /**
1741    * Filters only pyclass object (new class)
1742    */
1743   public static class ObjectPredicate extends NotNullPredicate<PyMemberInfo<PyElement>> {
1744     private final boolean myAllowObjects;
1745
1746     /**
1747      * @param allowObjects allows only objects if true. Allows all but objects otherwise.
1748      */
1749     public ObjectPredicate(final boolean allowObjects) {
1750       myAllowObjects = allowObjects;
1751     }
1752
1753     @Override
1754     public boolean applyNotNull(@NotNull final PyMemberInfo<PyElement> input) {
1755       return myAllowObjects == isObject(input);
1756     }
1757
1758     private static boolean isObject(@NotNull final PyMemberInfo<PyElement> classMemberInfo) {
1759       final PyElement element = classMemberInfo.getMember();
1760       return (element instanceof PyClass) && PyNames.OBJECT.equals(element.getName());
1761     }
1762   }
1763
1764   /**
1765    * Sometimes you do not know real FQN of some class, but you know class name and its package.
1766    * I.e. <code>django.apps.conf.AppConfig</code> is not documented, but you know
1767    * <code>AppConfig</code> and <code>django</code> package.
1768    *
1769    * @param symbol element to check (class or function)
1770    * @param expectedPackage package like "django"
1771    * @param expectedName expected name (i.e. AppConfig)
1772    * @return true if element in package
1773    */
1774   public static boolean isSymbolInPackage(@NotNull final PyQualifiedNameOwner symbol,
1775                                           @NotNull final String expectedPackage,
1776                                           @NotNull final String expectedName) {
1777     final String qualifiedNameString = symbol.getQualifiedName();
1778     if (qualifiedNameString == null) {
1779       return false;
1780     }
1781     final QualifiedName qualifiedName = QualifiedName.fromDottedString(qualifiedNameString);
1782     final String aPackage = qualifiedName.getFirstComponent();
1783     if (!(expectedPackage.equals(aPackage))) {
1784       return false;
1785     }
1786     final String symboldName = qualifiedName.getLastComponent();
1787     return expectedName.equals(symboldName);
1788   }
1789
1790   /**
1791    * Checks that given class is the root of class hierarchy, i.e. it's either {@code object} or
1792    * special {@link PyNames#FAKE_OLD_BASE} class for old-style classes.
1793    *
1794    * @param cls    Python class to check
1795    * @see PyBuiltinCache
1796    * @see PyNames#FAKE_OLD_BASE
1797    */
1798   public static boolean isObjectClass(@NotNull PyClass cls) {
1799     final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(cls);
1800     return cls == builtinCache.getClass(PyNames.OBJECT) || cls == builtinCache.getClass(PyNames.FAKE_OLD_BASE);
1801   }
1802
1803   /**
1804    * Checks that given type is the root of type hierarchy, i.e. it's type of either {@code object} or special
1805    * {@link PyNames#FAKE_OLD_BASE} class for old-style classes.
1806    *
1807    * @param type   Python class to check
1808    * @param anchor arbitrary PSI element to find appropriate SDK
1809    * @see PyBuiltinCache
1810    * @see PyNames#FAKE_OLD_BASE
1811    */
1812   public static boolean isObjectType(@NotNull PyType type, @NotNull PsiElement anchor) {
1813     final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(anchor);
1814     return type == builtinCache.getObjectType() || type == builtinCache.getOldstyleClassobjType();
1815   }
1816
1817   public static boolean isInScratchFile(@NotNull PsiElement element) {
1818     PsiFile file = element.getContainingFile();
1819     return file != null && ScratchRootType.getInstance().isScratchFile(file.getVirtualFile());
1820   }
1821
1822   /**
1823    * This helper class allows to collect various information about AST nodes composing {@link PyStringLiteralExpression}.
1824    */
1825   public static final class StringNodeInfo {
1826     private final ASTNode myNode;
1827     private final String myPrefix;
1828     private final String myQuote;
1829     private final TextRange myContentRange;
1830
1831     public StringNodeInfo(@NotNull ASTNode node) {
1832       if (!PyTokenTypes.STRING_NODES.contains(node.getElementType())) {
1833         throw new IllegalArgumentException("Node must be valid Python string literal token, but " + node.getElementType() + " was given");
1834       }
1835       myNode = node;
1836       final String nodeText = node.getText();
1837       final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(nodeText);
1838       myPrefix = nodeText.substring(0, prefixLength);
1839       myContentRange = PyStringLiteralExpressionImpl.getNodeTextRange(nodeText);
1840       myQuote = nodeText.substring(prefixLength, myContentRange.getStartOffset());
1841     }
1842
1843     public StringNodeInfo(@NotNull PsiElement element) {
1844       this(element.getNode());
1845     }
1846
1847     @NotNull
1848     public ASTNode getNode() {
1849       return myNode;
1850     }
1851
1852     /**
1853      * @return string prefix, e.g. "UR", "b" etc.
1854      */
1855     @NotNull
1856     public String getPrefix() {
1857       return myPrefix;
1858     }
1859
1860     /**
1861      * @return content of the string node between quotes
1862      */
1863     @NotNull
1864     public String getContent() {
1865       return myContentRange.substring(myNode.getText());
1866     }
1867
1868     /**
1869      * @return <em>relative</em> range of the content (excluding prefix and quotes)
1870      * @see #getAbsoluteContentRange()
1871      */
1872     @NotNull
1873     public TextRange getContentRange() {
1874       return myContentRange;
1875     }
1876
1877     /**
1878      * @return <em>absolute</em> content range that accounts offset of the {@link #getNode() node} in the document
1879      */
1880     @NotNull
1881     public TextRange getAbsoluteContentRange() {
1882       return getContentRange().shiftRight(myNode.getStartOffset());
1883     }
1884
1885     /**
1886      * @return the first character of {@link #getQuote()}
1887      */
1888     public char getSingleQuote() {
1889       return myQuote.charAt(0);
1890     }
1891
1892     @NotNull
1893     public String getQuote() {
1894       return myQuote;
1895     }
1896
1897     public boolean isTripleQuoted() {
1898       return myQuote.length() == 3;
1899     }
1900
1901     /**
1902      * @return true if string literal ends with starting quote
1903      */
1904     public boolean isTerminated() {
1905       final String text = myNode.getText();
1906       return text.length() - myPrefix.length() >= myQuote.length() * 2 && text.endsWith(myQuote);
1907     }
1908
1909     /**
1910      * @return true if given string node contains "u" or "U" prefix
1911      */
1912     public boolean isUnicode() {
1913       return StringUtil.containsIgnoreCase(myPrefix, "u");
1914     }
1915
1916     /**
1917      * @return true if given string node contains "r" or "R" prefix
1918      */
1919     public boolean isRaw() {
1920       return StringUtil.containsIgnoreCase(myPrefix, "r");
1921     }
1922
1923     /**
1924      * @return true if given string node contains "b" or "B" prefix
1925      */
1926     public boolean isBytes() {
1927       return StringUtil.containsIgnoreCase(myPrefix, "b");
1928     }
1929
1930     /**
1931      * @return true if other string node has the same decorations, i.e. quotes and prefix
1932      */
1933     @Override
1934     public boolean equals(Object o) {
1935       if (this == o) return true;
1936       if (o == null || getClass() != o.getClass()) return false;
1937
1938       StringNodeInfo info = (StringNodeInfo)o;
1939
1940       return getQuote().equals(info.getQuote()) &&
1941              isRaw() == info.isRaw() &&
1942              isUnicode() == info.isUnicode() &&
1943              isBytes() == info.isBytes();
1944     }
1945   }
1946 }