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