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