f04c0a08e142ed96592c33a9f91f0c76895dd1e5
[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 StringBuilder newElement = new StringBuilder(element.getLastChild().getText());
549       final PyExpression callee = ((PyCallExpression)qualifier).getCallee();
550       if (callee instanceof PyReferenceExpression) {
551         final PyExpression calleeQualifier = ((PyReferenceExpression)callee).getQualifier();
552         if (calleeQualifier != null) {
553           newElement.insert(0, calleeQualifier.getText() + ".");
554         }
555       }
556       final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(element.getProject());
557       final PyExpression expression = elementGenerator.createExpressionFromText(LanguageLevel.forElement(element), newElement.toString());
558       element.replace(expression);
559     }
560     else {
561       final PsiElement dot = qualifier.getNextSibling();
562       if (dot != null) dot.delete();
563       qualifier.delete();
564     }
565   }
566
567   /**
568    * Returns string that represents element in string search.
569    *
570    * @param element element to search
571    * @return string that represents element
572    */
573   @NotNull
574   public static String computeElementNameForStringSearch(@NotNull final PsiElement element) {
575     if (element instanceof PyFile) {
576       return FileUtil.getNameWithoutExtension(((PyFile)element).getName());
577     }
578     if (element instanceof PsiDirectory) {
579       return ((PsiDirectory)element).getName();
580     }
581     // Magic literals are always represented by their string values
582     if ((element instanceof PyStringLiteralExpression) && PyMagicLiteralTools.isMagicLiteral(element)) {
583       final String name = ((StringLiteralExpression)element).getStringValue();
584       if (name != null) {
585         return name;
586       }
587     }
588     if (element instanceof PyElement) {
589       final String name = ((PyElement)element).getName();
590       if (name != null) {
591         return name;
592       }
593     }
594     return element.getNode().getText();
595   }
596
597   public static boolean isOwnScopeComprehension(@NotNull PyComprehensionElement comprehension) {
598     final boolean isAtLeast30 = LanguageLevel.forElement(comprehension).isAtLeast(LanguageLevel.PYTHON30);
599     final boolean isListComprehension = comprehension instanceof PyListCompExpression;
600     return !isListComprehension || isAtLeast30;
601   }
602
603   public static boolean hasCustomDecorators(@NotNull PyDecoratable decoratable) {
604     return PyKnownDecoratorUtil.hasNonBuiltinDecorator(decoratable, TypeEvalContext.codeInsightFallback(null));
605   }
606
607   public static boolean isDecoratedAsAbstract(@NotNull final PyDecoratable decoratable) {
608     return PyKnownDecoratorUtil.hasAbstractDecorator(decoratable, TypeEvalContext.codeInsightFallback(null));
609   }
610
611   public static ASTNode createNewName(PyElement element, String name) {
612     return PyElementGenerator.getInstance(element.getProject()).createNameIdentifier(name, LanguageLevel.forElement(element));
613   }
614
615   /**
616    * Finds element declaration by resolving its references top the top but not further than file (to prevent unstubing)
617    *
618    * @param element element to resolve
619    * @return its declaration
620    */
621   @NotNull
622   public static PsiElement resolveToTheTop(@NotNull final PsiElement elementToResolve) {
623     PsiElement currentElement = elementToResolve;
624     while (true) {
625       final PsiReference reference = currentElement.getReference();
626       if (reference == null) {
627         break;
628       }
629       final PsiElement resolve = reference.resolve();
630       if ((resolve == null) || resolve.equals(currentElement) || !inSameFile(resolve, currentElement)) {
631         break;
632       }
633       currentElement = resolve;
634     }
635     return currentElement;
636   }
637
638   /**
639    * Note that returned list may contain {@code null} items, e.g. for unresolved import elements, originally wrapped
640    * in {@link com.jetbrains.python.psi.resolve.ImportedResolveResult}.
641    */
642   @NotNull
643   public static List<PsiElement> multiResolveTopPriority(@NotNull PsiElement element, @NotNull PyResolveContext resolveContext) {
644     if (element instanceof PyReferenceOwner) {
645       final PsiPolyVariantReference ref = ((PyReferenceOwner)element).getReference(resolveContext);
646       return filterTopPriorityResults(ref.multiResolve(false));
647     }
648     else {
649       final PsiReference reference = element.getReference();
650       return reference != null ? Collections.singletonList(reference.resolve()) : Collections.<PsiElement>emptyList();
651     }
652   }
653
654   @NotNull
655   public static List<PsiElement> multiResolveTopPriority(@NotNull PsiPolyVariantReference reference) {
656     return filterTopPriorityResults(reference.multiResolve(false));
657   }
658
659   @NotNull
660   private static List<PsiElement> filterTopPriorityResults(@NotNull ResolveResult[] resolveResults) {
661     if (resolveResults.length == 0) {
662       return Collections.emptyList();
663     }
664     final List<PsiElement> filtered = new ArrayList<PsiElement>();
665     final int maxRate = getMaxRate(resolveResults);
666     for (ResolveResult resolveResult : resolveResults) {
667       final int rate = resolveResult instanceof RatedResolveResult ? ((RatedResolveResult)resolveResult).getRate() : 0;
668       if (rate >= maxRate) {
669         filtered.add(resolveResult.getElement());
670       }
671     }
672     return filtered;
673   }
674
675   private static int getMaxRate(@NotNull ResolveResult[] resolveResults) {
676     int maxRate = Integer.MIN_VALUE;
677     for (ResolveResult resolveResult : resolveResults) {
678       if (resolveResult instanceof RatedResolveResult) {
679         final int rate = ((RatedResolveResult)resolveResult).getRate();
680         if (rate > maxRate) {
681           maxRate = rate;
682         }
683       }
684     }
685     return maxRate;
686   }
687
688   /**
689    * Gets class init method
690    *
691    * @param pyClass class where to find init
692    * @return class init method if any
693    */
694   @Nullable
695   public static PyFunction getInitMethod(@NotNull final PyClass pyClass) {
696     return pyClass.findMethodByName(PyNames.INIT, false);
697   }
698
699   /**
700    * Returns Python language level for a virtual file.
701    *
702    * @see {@link LanguageLevel#forElement}
703    */
704   @NotNull
705   public static LanguageLevel getLanguageLevelForVirtualFile(@NotNull Project project,
706                                                              @NotNull VirtualFile virtualFile) {
707     if (virtualFile instanceof VirtualFileWindow) {
708       virtualFile = ((VirtualFileWindow)virtualFile).getDelegate();
709     }
710
711     // Most of the cases should be handled by this one, PyLanguageLevelPusher pushes folders only
712     final VirtualFile folder = virtualFile.getParent();
713     if (folder != null) {
714       LanguageLevel level = folder.getUserData(LanguageLevel.KEY);
715       if (level == null) level = PythonLanguageLevelPusher.getFileLanguageLevel(project, virtualFile);
716       return level;
717     }
718     else {
719       // However this allows us to setup language level per file manually
720       // in case when it is LightVirtualFile
721       final LanguageLevel level = virtualFile.getUserData(LanguageLevel.KEY);
722       if (level != null) return level;
723
724       if (ApplicationManager.getApplication().isUnitTestMode()) {
725         final LanguageLevel languageLevel = LanguageLevel.FORCE_LANGUAGE_LEVEL;
726         if (languageLevel != null) {
727           return languageLevel;
728         }
729       }
730       return guessLanguageLevelWithCaching(project);
731     }
732   }
733
734   public static void invalidateLanguageLevelCache(@NotNull Project project) {
735     project.putUserData(PythonLanguageLevelPusher.PYTHON_LANGUAGE_LEVEL, null);
736   }
737
738   @NotNull
739   public static LanguageLevel guessLanguageLevelWithCaching(@NotNull Project project) {
740     LanguageLevel languageLevel = project.getUserData(PythonLanguageLevelPusher.PYTHON_LANGUAGE_LEVEL);
741     if (languageLevel == null) {
742       languageLevel = guessLanguageLevel(project);
743       project.putUserData(PythonLanguageLevelPusher.PYTHON_LANGUAGE_LEVEL, languageLevel);
744     }
745
746     return languageLevel;
747   }
748
749   @NotNull
750   public static LanguageLevel guessLanguageLevel(@NotNull Project project) {
751     final ModuleManager moduleManager = ModuleManager.getInstance(project);
752     if (moduleManager != null) {
753       LanguageLevel maxLevel = null;
754       for (Module projectModule : moduleManager.getModules()) {
755         final Sdk sdk = PythonSdkType.findPythonSdk(projectModule);
756         if (sdk != null) {
757           final LanguageLevel level = PythonSdkType.getLanguageLevelForSdk(sdk);
758           if (maxLevel == null || maxLevel.isOlderThan(level)) {
759             maxLevel = level;
760           }
761         }
762       }
763       if (maxLevel != null) {
764         return maxLevel;
765       }
766     }
767     return LanguageLevel.getDefault();
768   }
769
770   /**
771    * Clone of C# "as" operator.
772    * Checks if expression has correct type and casts it if it has. Returns null otherwise.
773    * It saves coder from "instanceof / cast" chains.
774    *
775    * @param expression expression to check
776    * @param clazz      class to cast
777    * @param <T>        class to cast
778    * @return expression casted to appropriate type (if could be casted). Null otherwise.
779    */
780   @Nullable
781   @SuppressWarnings("unchecked")
782   public static <T> T as(@Nullable final Object expression, @NotNull final Class<T> clazz) {
783     return ObjectUtils.tryCast(expression, clazz);
784   }
785
786   // TODO: Move to PsiElement?
787
788   /**
789    * Searches for references injected to element with certain type
790    *
791    * @param element       element to search injected references for
792    * @param expectedClass expected type of element reference resolved to
793    * @param <T>           expected type of element reference resolved to
794    * @return resolved element if found or null if not found
795    */
796   @Nullable
797   public static <T extends PsiElement> T findReference(@NotNull final PsiElement element, @NotNull final Class<T> expectedClass) {
798     for (final PsiReference reference : element.getReferences()) {
799       final T result = as(reference.resolve(), expectedClass);
800       if (result != null) {
801         return result;
802       }
803     }
804     return null;
805   }
806
807
808   /**
809    * Converts collection to list of certain type
810    *
811    * @param expression   expression of collection type
812    * @param elementClass expected element type
813    * @param <T>          expected element type
814    * @return list of elements of expected element type
815    */
816   @NotNull
817   public static <T> List<T> asList(@Nullable final Collection<?> expression, @NotNull final Class<T> elementClass) {
818     if ((expression == null) || expression.isEmpty()) {
819       return Collections.emptyList();
820     }
821     final List<T> result = new ArrayList<T>();
822     for (final Object element : expression) {
823       final T toAdd = as(element, elementClass);
824       if (toAdd != null) {
825         result.add(toAdd);
826       }
827     }
828     return result;
829   }
830
831   /**
832    * Force re-highlighting in all open editors that belong to specified project.
833    */
834   public static void rehighlightOpenEditors(final @NotNull Project project) {
835     ApplicationManager.getApplication().runWriteAction(new Runnable() {
836       @Override
837       public void run() {
838
839         for (Editor editor : EditorFactory.getInstance().getAllEditors()) {
840           if (editor instanceof EditorEx && editor.getProject() == project) {
841             final VirtualFile vFile = ((EditorEx)editor).getVirtualFile();
842             if (vFile != null) {
843               final EditorHighlighter highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(project, vFile);
844               ((EditorEx)editor).setHighlighter(highlighter);
845             }
846           }
847         }
848       }
849     });
850   }
851
852   public static class KnownDecoratorProviderHolder {
853     public static PyKnownDecoratorProvider[] KNOWN_DECORATOR_PROVIDERS = Extensions.getExtensions(PyKnownDecoratorProvider.EP_NAME);
854
855     private KnownDecoratorProviderHolder() {
856     }
857   }
858
859   /**
860    * If argument is a PsiDirectory, turn it into a PsiFile that points to __init__.py in that directory.
861    * If there's no __init__.py there, null is returned, there's no point to resolve to a dir which is not a package.
862    * Alas, resolve() and multiResolve() can't return anything but a PyFile or PsiFileImpl.isPsiUpToDate() would fail.
863    * This is because isPsiUpToDate() relies on identity of objects returned by FileViewProvider.getPsi().
864    * If we ever need to exactly tell a dir from __init__.py, that logic has to change.
865    *
866    * @param target a resolve candidate.
867    * @return a PsiFile if target was a PsiDirectory, or null, or target unchanged.
868    */
869   @Nullable
870   public static PsiElement turnDirIntoInit(@Nullable PsiElement target) {
871     if (target instanceof PsiDirectory) {
872       final PsiDirectory dir = (PsiDirectory)target;
873       final PsiFile file = dir.findFile(PyNames.INIT_DOT_PY);
874       if (file != null) {
875         return file; // ResolveImportUtil will extract directory part as needed, everyone else are better off with a file.
876       }
877       else {
878         return null;
879       } // dir without __init__.py does not resolve
880     }
881     else {
882       return target;
883     } // don't touch non-dirs
884   }
885
886   /**
887    * If directory is a PsiDirectory, that is also a valid Python package, return PsiFile that points to __init__.py,
888    * if such file exists, or directory itself (i.e. namespace package). Otherwise, return {@code null}.
889    * Unlike {@link #turnDirIntoInit(PsiElement)} this function handles namespace packages and
890    * accepts only PsiDirectories as target.
891    *
892    * @param directory directory to check
893    * @param anchor optional PSI element to determine language level as for {@link #isPackage(PsiDirectory, PsiElement)}
894    * @return PsiFile or PsiDirectory, if target is a Python package and {@code null} null otherwise
895    */
896   @Nullable
897   public static PsiElement getPackageElement(@NotNull PsiDirectory directory, @Nullable PsiElement anchor) {
898     if (isPackage(directory, anchor)) {
899       final PsiElement init = turnDirIntoInit(directory);
900       if (init != null) {
901         return init;
902       }
903       return directory;
904     }
905     return null;
906   }
907
908   /**
909    * If target is a Python module named __init__.py file, return its directory. Otherwise return target unchanged.
910    * @param target PSI element to check
911    * @return PsiDirectory or target unchanged
912    */
913   @Contract("null -> null; !null -> !null")
914   @Nullable
915   public static PsiElement turnInitIntoDir(@Nullable PsiElement target) {
916     if (target instanceof PyFile && isPackage((PsiFile)target)) {
917       return ((PsiFile)target).getContainingDirectory();
918     }
919     return target;
920   }
921
922   /**
923    * @see #isPackage(PsiDirectory, boolean, PsiElement)
924    */
925   public static boolean isPackage(@NotNull PsiDirectory directory, @Nullable PsiElement anchor) {
926     return isPackage(directory, true, anchor);
927   }
928
929   /**
930    * Checks that given PsiDirectory can be treated as Python package, i.e. it's either contains __init__.py or it's a namespace package
931    * (effectively any directory in Python 3.3 and above). Setuptools namespace packages can be checked as well, but it requires access to
932    * {@link PySetuptoolsNamespaceIndex} and may slow things down during update of project indexes.
933    * Also note that this method does not check that directory itself and its parents have valid importable names,
934    * use {@link PyNames#isIdentifier(String)} for this purpose.
935    *
936    * @param directory PSI directory to check
937    * @param checkSetupToolsPackages whether setuptools namespace packages should be considered as well
938    * @param anchor    optional anchor element to determine language level
939    * @return whether given directory is Python package
940    *
941    * @see PyNames#isIdentifier(String)
942    */
943   public static boolean isPackage(@NotNull PsiDirectory directory, boolean checkSetupToolsPackages, @Nullable PsiElement anchor) {
944     if (directory.findFile(PyNames.INIT_DOT_PY) != null) {
945       return true;
946     }
947     final LanguageLevel level = anchor != null ?
948                                 LanguageLevel.forElement(anchor) :
949                                 getLanguageLevelForVirtualFile(directory.getProject(), directory.getVirtualFile());
950     if (level.isAtLeast(LanguageLevel.PYTHON33)) {
951       return true;
952     }
953     return checkSetupToolsPackages && isSetuptoolsNamespacePackage(directory);
954   }
955
956   public static boolean isPackage(@NotNull PsiFile file) {
957     return PyNames.INIT_DOT_PY.equals(file.getName());
958   }
959
960   private static boolean isSetuptoolsNamespacePackage(@NotNull PsiDirectory directory) {
961     final String packagePath = getPackagePath(directory);
962     return packagePath != null && !PySetuptoolsNamespaceIndex.find(packagePath, directory.getProject()).isEmpty();
963   }
964
965   @Nullable
966   private static String getPackagePath(@NotNull PsiDirectory directory) {
967     final QualifiedName name = QualifiedNameFinder.findShortestImportableQName(directory);
968     return name != null ? name.toString() : null;
969   }
970
971   /**
972    * Counts initial underscores of an identifier.
973    *
974    * @param name identifier
975    * @return 0 if no initial underscores found, 1 if there's only one underscore, 2 if there's two or more initial underscores.
976    */
977   public static int getInitialUnderscores(String name) {
978     if (name == null) {
979       return 0;
980     }
981     int underscores = 0;
982     if (name.startsWith("__")) {
983       underscores = 2;
984     }
985     else if (name.startsWith("_")) underscores = 1;
986     return underscores;
987   }
988
989   /**
990    * Tries to find nearest parent that conceals names defined inside it. Such elements are 'class' and 'def':
991    * anything defined within it does not seep to the namespace below them, but is concealed within.
992    *
993    * @param elt starting point of search.
994    * @return 'class' or 'def' element, or null if not found.
995    * @deprecated Use {@link ScopeUtil#getScopeOwner} instead.
996    */
997   @Deprecated
998   @Nullable
999   public static PsiElement getConcealingParent(PsiElement elt) {
1000     if (elt == null || elt instanceof PsiFile) {
1001       return null;
1002     }
1003     PsiElement parent = PsiTreeUtil.getStubOrPsiParent(elt);
1004     boolean jump_over = false;
1005     while (parent != null) {
1006       if (parent instanceof PyClass || parent instanceof PyCallable) {
1007         if (jump_over) {
1008           jump_over = false;
1009         }
1010         else {
1011           return parent;
1012         }
1013       }
1014       else if (parent instanceof PyDecoratorList) {
1015         // decorators PSI is inside decorated things but their namespace is outside
1016         jump_over = true;
1017       }
1018       else if (parent instanceof PsiFileSystemItem) {
1019         break;
1020       }
1021       parent = PsiTreeUtil.getStubOrPsiParent(parent);
1022     }
1023     return null;
1024   }
1025
1026   /**
1027    * @param name
1028    * @return true iff the name looks like a class-private one, starting with two underscores but not ending with two underscores.
1029    */
1030   public static boolean isClassPrivateName(@NotNull String name) {
1031     return name.startsWith("__") && !name.endsWith("__");
1032   }
1033
1034   public static boolean isSpecialName(@NotNull String name) {
1035     return name.length() > 4 && name.startsWith("__") && name.endsWith("__");
1036   }
1037
1038   /**
1039    * Constructs new lookup element for completion of keyword argument with equals sign appended.
1040    *
1041    * @param name    name of the parameter
1042    * @param project project instance to check code style settings and surround equals sign with spaces if necessary
1043    * @return lookup element
1044    */
1045   @NotNull
1046   public static LookupElement createNamedParameterLookup(@NotNull String name, @Nullable Project project) {
1047     final String suffix;
1048     if (CodeStyleSettingsManager.getSettings(project).getCustomSettings(PyCodeStyleSettings.class).SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT) {
1049       suffix = " = ";
1050     }
1051     else {
1052       suffix = "=";
1053     }
1054     LookupElementBuilder lookupElementBuilder = LookupElementBuilder.create(name + suffix).withIcon(PlatformIcons.PARAMETER_ICON);
1055     lookupElementBuilder = lookupElementBuilder.withInsertHandler(OverwriteEqualsInsertHandler.INSTANCE);
1056     return PrioritizedLookupElement.withGrouping(lookupElementBuilder, 1);
1057   }
1058
1059   /**
1060    * Peels argument expression of parentheses and of keyword argument wrapper
1061    *
1062    * @param expr an item of getArguments() array
1063    * @return expression actually passed as argument
1064    */
1065   @Nullable
1066   public static PyExpression peelArgument(PyExpression expr) {
1067     while (expr instanceof PyParenthesizedExpression) expr = ((PyParenthesizedExpression)expr).getContainedExpression();
1068     if (expr instanceof PyKeywordArgument) expr = ((PyKeywordArgument)expr).getValueExpression();
1069     return expr;
1070   }
1071
1072   public static String getFirstParameterName(PyFunction container) {
1073     String selfName = PyNames.CANONICAL_SELF;
1074     if (container != null) {
1075       final PyParameter[] params = container.getParameterList().getParameters();
1076       if (params.length > 0) {
1077         final PyNamedParameter named = params[0].getAsNamed();
1078         if (named != null) {
1079           selfName = named.getName();
1080         }
1081       }
1082     }
1083     return selfName;
1084   }
1085
1086   /**
1087    * @return Source roots <strong>and</strong> content roots for element's project
1088    */
1089   @NotNull
1090   public static Collection<VirtualFile> getSourceRoots(@NotNull PsiElement foothold) {
1091     final Module module = ModuleUtilCore.findModuleForPsiElement(foothold);
1092     if (module != null) {
1093       return getSourceRoots(module);
1094     }
1095     return Collections.emptyList();
1096   }
1097
1098   /**
1099    * @return Source roots <strong>and</strong> content roots for module
1100    */
1101   @NotNull
1102   public static Collection<VirtualFile> getSourceRoots(@NotNull Module module) {
1103     final Set<VirtualFile> result = new LinkedHashSet<VirtualFile>();
1104     final ModuleRootManager manager = ModuleRootManager.getInstance(module);
1105     Collections.addAll(result, manager.getSourceRoots());
1106     Collections.addAll(result, manager.getContentRoots());
1107     return result;
1108   }
1109
1110   @Nullable
1111   public static VirtualFile findInRoots(Module module, String path) {
1112     if (module != null) {
1113       for (VirtualFile root : getSourceRoots(module)) {
1114         VirtualFile file = root.findFileByRelativePath(path);
1115         if (file != null) {
1116           return file;
1117         }
1118       }
1119     }
1120     return null;
1121   }
1122
1123   @Nullable
1124   public static List<String> getStringListFromTargetExpression(PyTargetExpression attr) {
1125     return strListValue(attr.findAssignedValue());
1126   }
1127
1128   @Nullable
1129   public static List<String> strListValue(PyExpression value) {
1130     while (value instanceof PyParenthesizedExpression) {
1131       value = ((PyParenthesizedExpression)value).getContainedExpression();
1132     }
1133     if (value instanceof PySequenceExpression) {
1134       final PyExpression[] elements = ((PySequenceExpression)value).getElements();
1135       List<String> result = new ArrayList<String>(elements.length);
1136       for (PyExpression element : elements) {
1137         if (!(element instanceof PyStringLiteralExpression)) {
1138           return null;
1139         }
1140         result.add(((PyStringLiteralExpression)element).getStringValue());
1141       }
1142       return result;
1143     }
1144     return null;
1145   }
1146
1147   @NotNull
1148   public static Map<String, PyExpression> dictValue(@NotNull PyDictLiteralExpression dict) {
1149     Map<String, PyExpression> result = Maps.newLinkedHashMap();
1150     for (PyKeyValueExpression keyValue : dict.getElements()) {
1151       PyExpression key = keyValue.getKey();
1152       PyExpression value = keyValue.getValue();
1153       if (key instanceof PyStringLiteralExpression) {
1154         result.put(((PyStringLiteralExpression)key).getStringValue(), value);
1155       }
1156     }
1157     return result;
1158   }
1159
1160   /**
1161    * @param what     thing to search for
1162    * @param variants things to search among
1163    * @return true iff what.equals() one of the variants.
1164    */
1165   public static <T> boolean among(@NotNull T what, T... variants) {
1166     for (T s : variants) {
1167       if (what.equals(s)) return true;
1168     }
1169     return false;
1170   }
1171
1172   @Nullable
1173   public static String getKeywordArgumentString(PyCallExpression expr, String keyword) {
1174     return PyPsiUtils.strValue(expr.getKeywordArgument(keyword));
1175   }
1176
1177   public static boolean isExceptionClass(PyClass pyClass) {
1178     if (isBaseException(pyClass.getQualifiedName())) {
1179       return true;
1180     }
1181     for (PyClassLikeType type : pyClass.getAncestorTypes(TypeEvalContext.codeInsightFallback(pyClass.getProject()))) {
1182       if (type != null && isBaseException(type.getClassQName())) {
1183         return true;
1184       }
1185     }
1186     return false;
1187   }
1188
1189   private static boolean isBaseException(String name) {
1190     return name != null && (name.contains("BaseException") || name.startsWith("exceptions."));
1191   }
1192
1193   public static class MethodFlags {
1194
1195     private final boolean myIsStaticMethod;
1196     private final boolean myIsMetaclassMethod;
1197     private final boolean myIsSpecialMetaclassMethod;
1198     private final boolean myIsClassMethod;
1199
1200     /**
1201      * @return true iff the method belongs to a metaclass (an ancestor of 'type').
1202      */
1203     public boolean isMetaclassMethod() {
1204       return myIsMetaclassMethod;
1205     }
1206
1207     /**
1208      * @return iff isMetaclassMethod and the method is either __init__ or __call__.
1209      */
1210     public boolean isSpecialMetaclassMethod() {
1211       return myIsSpecialMetaclassMethod;
1212     }
1213
1214     public boolean isStaticMethod() {
1215       return myIsStaticMethod;
1216     }
1217
1218     public boolean isClassMethod() {
1219       return myIsClassMethod;
1220     }
1221
1222     private MethodFlags(boolean isClassMethod, boolean isStaticMethod, boolean isMetaclassMethod, boolean isSpecialMetaclassMethod) {
1223       myIsClassMethod = isClassMethod;
1224       myIsStaticMethod = isStaticMethod;
1225       myIsMetaclassMethod = isMetaclassMethod;
1226       myIsSpecialMetaclassMethod = isSpecialMetaclassMethod;
1227     }
1228
1229     /**
1230      * @param node a function
1231      * @return a new flags object, or null if the function is not a method
1232      */
1233     @Nullable
1234     public static MethodFlags of(@NotNull PyFunction node) {
1235       PyClass cls = node.getContainingClass();
1236       if (cls != null) {
1237         PyFunction.Modifier modifier = node.getModifier();
1238         boolean isMetaclassMethod = false;
1239         PyClass type_cls = PyBuiltinCache.getInstance(node).getClass("type");
1240         for (PyClass ancestor_cls : cls.getAncestorClasses(null)) {
1241           if (ancestor_cls == type_cls) {
1242             isMetaclassMethod = true;
1243             break;
1244           }
1245         }
1246         final String method_name = node.getName();
1247         boolean isSpecialMetaclassMethod = isMetaclassMethod && method_name != null && among(method_name, PyNames.INIT, "__call__");
1248         return new MethodFlags(modifier == CLASSMETHOD, modifier == STATICMETHOD, isMetaclassMethod, isSpecialMetaclassMethod);
1249       }
1250       return null;
1251     }
1252
1253     //TODO: Doc
1254     public boolean isInstanceMethod() {
1255       return !(myIsClassMethod || myIsStaticMethod);
1256     }
1257   }
1258
1259   public static boolean isSuperCall(@NotNull PyCallExpression node) {
1260     PyClass klass = PsiTreeUtil.getParentOfType(node, PyClass.class);
1261     if (klass == null) return false;
1262     PyExpression callee = node.getCallee();
1263     if (callee == null) return false;
1264     String name = callee.getName();
1265     if (PyNames.SUPER.equals(name)) {
1266       PsiReference reference = callee.getReference();
1267       if (reference == null) return false;
1268       PsiElement resolved = reference.resolve();
1269       PyBuiltinCache cache = PyBuiltinCache.getInstance(node);
1270       if (resolved != null && cache.isBuiltin(resolved)) {
1271         PyExpression[] args = node.getArguments();
1272         if (args.length > 0) {
1273           String firstArg = args[0].getText();
1274           if (firstArg.equals(klass.getName()) || firstArg.equals(PyNames.CANONICAL_SELF + "." + PyNames.__CLASS__)) {
1275             return true;
1276           }
1277           for (PyClass s : klass.getAncestorClasses(null)) {
1278             if (firstArg.equals(s.getName())) {
1279               return true;
1280             }
1281           }
1282         }
1283         else {
1284           return true;
1285         }
1286       }
1287     }
1288     return false;
1289   }
1290
1291   @NotNull
1292   public static PyFile getOrCreateFile(String path, Project project) {
1293     final VirtualFile vfile = LocalFileSystem.getInstance().findFileByIoFile(new File(path));
1294     final PsiFile psi;
1295     if (vfile == null) {
1296       final File file = new File(path);
1297       try {
1298         final VirtualFile baseDir = project.getBaseDir();
1299         final FileTemplateManager fileTemplateManager = FileTemplateManager.getInstance(project);
1300         final FileTemplate template = fileTemplateManager.getInternalTemplate("Python Script");
1301         final Properties properties = fileTemplateManager.getDefaultProperties();
1302         properties.setProperty("NAME", FileUtil.getNameWithoutExtension(file.getName()));
1303         final String content = (template != null) ? template.getText(properties) : null;
1304         psi = PyExtractSuperclassHelper.placeFile(project,
1305                                                   StringUtil.notNullize(
1306                                                     file.getParent(),
1307                                                     baseDir != null ? baseDir
1308                                                       .getPath() : "."
1309                                                   ),
1310                                                   file.getName(),
1311                                                   content
1312         );
1313       }
1314       catch (IOException e) {
1315         throw new IncorrectOperationException(String.format("Cannot create file '%s'", path));
1316       }
1317     }
1318     else {
1319       psi = PsiManager.getInstance(project).findFile(vfile);
1320     }
1321     if (!(psi instanceof PyFile)) {
1322       throw new IncorrectOperationException(PyBundle.message(
1323         "refactoring.move.module.members.error.cannot.place.elements.into.nonpython.file"));
1324     }
1325     return (PyFile)psi;
1326   }
1327
1328   /**
1329    * counts elements in iterable
1330    *
1331    * @param expression to count containing elements (iterable)
1332    * @return element count
1333    */
1334   public static int getElementsCount(PyExpression expression, TypeEvalContext evalContext) {
1335     int valuesLength = -1;
1336     PyType type = evalContext.getType(expression);
1337     if (type instanceof PyTupleType) {
1338       valuesLength = ((PyTupleType)type).getElementCount();
1339     }
1340     else if (type instanceof PyNamedTupleType) {
1341       valuesLength = ((PyNamedTupleType)type).getElementCount();
1342     }
1343     else if (expression instanceof PySequenceExpression) {
1344       valuesLength = ((PySequenceExpression)expression).getElements().length;
1345     }
1346     else if (expression instanceof PyStringLiteralExpression) {
1347       valuesLength = ((PyStringLiteralExpression)expression).getStringValue().length();
1348     }
1349     else if (expression instanceof PyNumericLiteralExpression) {
1350       valuesLength = 1;
1351     }
1352     else if (expression instanceof PyCallExpression) {
1353       PyCallExpression call = (PyCallExpression)expression;
1354       if (call.isCalleeText("dict")) {
1355         valuesLength = call.getArguments().length;
1356       }
1357       else if (call.isCalleeText("tuple")) {
1358         PyExpression[] arguments = call.getArguments();
1359         if (arguments.length > 0 && arguments[0] instanceof PySequenceExpression) {
1360           valuesLength = ((PySequenceExpression)arguments[0]).getElements().length;
1361         }
1362       }
1363     }
1364     return valuesLength;
1365   }
1366
1367   @Nullable
1368   public static PsiElement findPrevAtOffset(PsiFile psiFile, int caretOffset, Class... toSkip) {
1369     PsiElement element;
1370     if (caretOffset < 0) {
1371       return null;
1372     }
1373     int lineStartOffset = 0;
1374     final Document document = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile);
1375     if (document != null) {
1376       int lineNumber = document.getLineNumber(caretOffset);
1377       lineStartOffset = document.getLineStartOffset(lineNumber);
1378     }
1379     do {
1380       caretOffset--;
1381       element = psiFile.findElementAt(caretOffset);
1382     }
1383     while (caretOffset >= lineStartOffset && instanceOf(element, toSkip));
1384     return instanceOf(element, toSkip) ? null : element;
1385   }
1386
1387   @Nullable
1388   public static PsiElement findNonWhitespaceAtOffset(PsiFile psiFile, int caretOffset) {
1389     PsiElement element = findNextAtOffset(psiFile, caretOffset, PsiWhiteSpace.class);
1390     if (element == null) {
1391       element = findPrevAtOffset(psiFile, caretOffset - 1, PsiWhiteSpace.class);
1392     }
1393     return element;
1394   }
1395
1396   @Nullable
1397   public static PsiElement findElementAtOffset(PsiFile psiFile, int caretOffset) {
1398     PsiElement element = findPrevAtOffset(psiFile, caretOffset);
1399     if (element == null) {
1400       element = findNextAtOffset(psiFile, caretOffset);
1401     }
1402     return element;
1403   }
1404
1405   @Nullable
1406   public static PsiElement findNextAtOffset(@NotNull final PsiFile psiFile, int caretOffset, Class... toSkip) {
1407     PsiElement element = psiFile.findElementAt(caretOffset);
1408     if (element == null) {
1409       return null;
1410     }
1411
1412     final Document document = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile);
1413     int lineEndOffset = 0;
1414     if (document != null) {
1415       int lineNumber = document.getLineNumber(caretOffset);
1416       lineEndOffset = document.getLineEndOffset(lineNumber);
1417     }
1418     while (caretOffset < lineEndOffset && instanceOf(element, toSkip)) {
1419       caretOffset++;
1420       element = psiFile.findElementAt(caretOffset);
1421     }
1422     return instanceOf(element, toSkip) ? null : element;
1423   }
1424
1425   /**
1426    * Adds element to statement list to the correct place according to its dependencies.
1427    *
1428    * @param element       to insert
1429    * @param statementList where element should be inserted
1430    * @return inserted element
1431    */
1432   public static <T extends PyElement> T addElementToStatementList(@NotNull final T element,
1433                                                                   @NotNull final PyStatementList statementList) {
1434     PsiElement before = null;
1435     PsiElement after = null;
1436     for (final PyStatement statement : statementList.getStatements()) {
1437       if (PyDependenciesComparator.depends(element, statement)) {
1438         after = statement;
1439       }
1440       else if (PyDependenciesComparator.depends(statement, element)) {
1441         before = statement;
1442       }
1443     }
1444     final PsiElement result;
1445     if (after != null) {
1446
1447       result = statementList.addAfter(element, after);
1448     }
1449     else if (before != null) {
1450       result = statementList.addBefore(element, before);
1451     }
1452     else {
1453       result = addElementToStatementList(element, statementList, true);
1454     }
1455     @SuppressWarnings("unchecked") // Inserted element can't have different type
1456     final T resultCasted = (T)result;
1457     return resultCasted;
1458   }
1459
1460
1461   /**
1462    * Inserts specified element into the statement list either at the beginning or at its end. If new element is going to be
1463    * inserted at the beginning, any preceding docstrings and/or calls to super methods will be skipped.
1464    * Moreover if statement list previously didn't contain any statements, explicit new line and indentation will be inserted in
1465    * front of it.
1466    *
1467    * @param element        element to insert
1468    * @param statementList  statement list
1469    * @param toTheBeginning whether to insert element at the beginning or at the end of the statement list
1470    * @return actually inserted element as for {@link PsiElement#add(PsiElement)}
1471    */
1472   @NotNull
1473   public static PsiElement addElementToStatementList(@NotNull PsiElement element,
1474                                                      @NotNull PyStatementList statementList,
1475                                                      boolean toTheBeginning) {
1476     final boolean statementListWasEmpty = statementList.getStatements().length == 0;
1477     final PsiElement firstChild = statementList.getFirstChild();
1478     if (firstChild == statementList.getLastChild() && firstChild instanceof PyPassStatement) {
1479       element = firstChild.replace(element);
1480     }
1481     else {
1482       final PyStatement[] statements = statementList.getStatements();
1483       if (toTheBeginning && statements.length > 0) {
1484         final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(statementList, PyDocStringOwner.class);
1485         PyStatement anchor = statements[0];
1486         if (docStringOwner != null && anchor instanceof PyExpressionStatement &&
1487             ((PyExpressionStatement)anchor).getExpression() == docStringOwner.getDocStringExpression()) {
1488           final PyStatement next = PsiTreeUtil.getNextSiblingOfType(anchor, PyStatement.class);
1489           if (next == null) {
1490             return statementList.addAfter(element, anchor);
1491           }
1492           anchor = next;
1493         }
1494         while (anchor instanceof PyExpressionStatement) {
1495           final PyExpression expression = ((PyExpressionStatement)anchor).getExpression();
1496           if (expression instanceof PyCallExpression) {
1497             final PyExpression callee = ((PyCallExpression)expression).getCallee();
1498             if ((isSuperCall((PyCallExpression)expression) || (callee != null && PyNames.INIT.equals(callee.getName())))) {
1499               final PyStatement next = PsiTreeUtil.getNextSiblingOfType(anchor, PyStatement.class);
1500               if (next == null) {
1501                 return statementList.addAfter(element, anchor);
1502               }
1503               anchor = next;
1504               continue;
1505             }
1506           }
1507           break;
1508         }
1509         element = statementList.addBefore(element, anchor);
1510       }
1511       else {
1512         element = statementList.add(element);
1513       }
1514     }
1515     if (statementListWasEmpty) {
1516       final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(statementList.getProject());
1517       final Document document = documentManager.getDocument(statementList.getContainingFile());
1518       if (document != null) {
1519         documentManager.doPostponedOperationsAndUnblockDocument(document);
1520         document.insertString(statementList.getTextOffset(), "\n" + PyIndentUtil.getElementIndent(statementList));
1521         documentManager.commitDocument(document);
1522       }
1523     }
1524     return element;
1525   }
1526
1527   @NotNull
1528   public static List<List<PyParameter>> getOverloadedParametersSet(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
1529     final List<List<PyParameter>> parametersSet = getOverloadedParametersSet(context.getType(callable), context);
1530     return parametersSet != null ? parametersSet : Collections.singletonList(Arrays.asList(callable.getParameterList().getParameters()));
1531   }
1532
1533   @Nullable
1534   private static List<PyParameter> getParametersOfCallableType(@NotNull PyCallableType type, @NotNull TypeEvalContext context) {
1535     final List<PyCallableParameter> callableTypeParameters = type.getParameters(context);
1536     if (callableTypeParameters != null) {
1537       boolean allParametersDefined = true;
1538       final List<PyParameter> parameters = new ArrayList<PyParameter>();
1539       for (PyCallableParameter callableParameter : callableTypeParameters) {
1540         final PyParameter parameter = callableParameter.getParameter();
1541         if (parameter == null) {
1542           allParametersDefined = false;
1543           break;
1544         }
1545         parameters.add(parameter);
1546       }
1547       if (allParametersDefined) {
1548         return parameters;
1549       }
1550     }
1551     return null;
1552   }
1553
1554   @Nullable
1555   private static List<List<PyParameter>> getOverloadedParametersSet(@Nullable PyType type, @NotNull TypeEvalContext context) {
1556     if (type instanceof PyUnionType) {
1557       type = ((PyUnionType)type).excludeNull(context);
1558     }
1559
1560     if (type instanceof PyCallableType) {
1561       final List<PyParameter> results = getParametersOfCallableType((PyCallableType)type, context);
1562       if (results != null) {
1563         return Collections.singletonList(results);
1564       }
1565     }
1566     else if (type instanceof PyUnionType) {
1567       final List<List<PyParameter>> results = new ArrayList<List<PyParameter>>();
1568       final Collection<PyType> members = ((PyUnionType)type).getMembers();
1569       for (PyType member : members) {
1570         if (member instanceof PyCallableType) {
1571           final List<PyParameter> parameters = getParametersOfCallableType((PyCallableType)member, context);
1572           if (parameters != null) {
1573             results.add(parameters);
1574           }
1575         }
1576       }
1577       if (!results.isEmpty()) {
1578         return results;
1579       }
1580     }
1581
1582     return null;
1583   }
1584
1585   @NotNull
1586   public static List<PyParameter> getParameters(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
1587     final List<List<PyParameter>> parametersSet = getOverloadedParametersSet(callable, context);
1588     assert !parametersSet.isEmpty();
1589     return parametersSet.get(0);
1590   }
1591
1592   public static boolean isSignatureCompatibleTo(@NotNull PyCallable callable, @NotNull PyCallable otherCallable,
1593                                                 @NotNull TypeEvalContext context) {
1594     final List<PyParameter> parameters = getParameters(callable, context);
1595     final List<PyParameter> otherParameters = getParameters(otherCallable, context);
1596     final int optionalCount = optionalParametersCount(parameters);
1597     final int otherOptionalCount = optionalParametersCount(otherParameters);
1598     final int requiredCount = requiredParametersCount(callable, parameters);
1599     final int otherRequiredCount = requiredParametersCount(otherCallable, otherParameters);
1600     if (hasPositionalContainer(otherParameters) || hasKeywordContainer(otherParameters)) {
1601       if (otherParameters.size() == specialParametersCount(otherCallable, otherParameters)) {
1602         return true;
1603       }
1604     }
1605     if (hasPositionalContainer(parameters) || hasKeywordContainer(parameters)) {
1606       return requiredCount <= otherRequiredCount;
1607     }
1608     return requiredCount <= otherRequiredCount && parameters.size() >= otherParameters.size() && optionalCount >= otherOptionalCount;
1609   }
1610
1611   private static int optionalParametersCount(@NotNull List<PyParameter> parameters) {
1612     int n = 0;
1613     for (PyParameter parameter : parameters) {
1614       if (parameter.hasDefaultValue()) {
1615         n++;
1616       }
1617     }
1618     return n;
1619   }
1620
1621   private static int requiredParametersCount(@NotNull PyCallable callable, @NotNull List<PyParameter> parameters) {
1622     return parameters.size() - optionalParametersCount(parameters) - specialParametersCount(callable, parameters);
1623   }
1624
1625   private static int specialParametersCount(@NotNull PyCallable callable, @NotNull List<PyParameter> parameters) {
1626     int n = 0;
1627     if (hasPositionalContainer(parameters)) {
1628       n++;
1629     }
1630     if (hasKeywordContainer(parameters)) {
1631       n++;
1632     }
1633     if (callable.asMethod() != null) {
1634       n++;
1635     }
1636     else {
1637       if (parameters.size() > 0) {
1638         final PyParameter first = parameters.get(0);
1639         if (PyNames.CANONICAL_SELF.equals(first.getName())) {
1640           n++;
1641         }
1642       }
1643     }
1644     return n;
1645   }
1646
1647   private static boolean hasPositionalContainer(@NotNull List<PyParameter> parameters) {
1648     for (PyParameter parameter : parameters) {
1649       if (parameter instanceof PyNamedParameter && ((PyNamedParameter)parameter).isPositionalContainer()) {
1650         return true;
1651       }
1652     }
1653     return false;
1654   }
1655
1656   private static boolean hasKeywordContainer(@NotNull List<PyParameter> parameters) {
1657     for (PyParameter parameter : parameters) {
1658       if (parameter instanceof PyNamedParameter && ((PyNamedParameter)parameter).isKeywordContainer()) {
1659         return true;
1660       }
1661     }
1662     return false;
1663   }
1664
1665   public static boolean isInit(@NotNull final PyFunction function) {
1666     return PyNames.INIT.equals(function.getName());
1667   }
1668
1669   /**
1670    * Filters out {@link PyMemberInfo}
1671    * that should not be displayed in this refactoring (like object)
1672    *
1673    * @param pyMemberInfos collection to sort
1674    * @return sorted collection
1675    */
1676   @NotNull
1677   public static Collection<PyMemberInfo<PyElement>> filterOutObject(@NotNull final Collection<PyMemberInfo<PyElement>> pyMemberInfos) {
1678     return Collections2.filter(pyMemberInfos, new ObjectPredicate(false));
1679   }
1680
1681   public static boolean isStarImportableFrom(@NotNull String name, @NotNull PyFile file) {
1682     final List<String> dunderAll = file.getDunderAll();
1683     return dunderAll != null ? dunderAll.contains(name) : !name.startsWith("_");
1684   }
1685
1686   /**
1687    * Filters only pyclass object (new class)
1688    */
1689   public static class ObjectPredicate extends NotNullPredicate<PyMemberInfo<PyElement>> {
1690     private final boolean myAllowObjects;
1691
1692     /**
1693      * @param allowObjects allows only objects if true. Allows all but objects otherwise.
1694      */
1695     public ObjectPredicate(final boolean allowObjects) {
1696       myAllowObjects = allowObjects;
1697     }
1698
1699     @Override
1700     public boolean applyNotNull(@NotNull final PyMemberInfo<PyElement> input) {
1701       return myAllowObjects == isObject(input);
1702     }
1703
1704     private static boolean isObject(@NotNull final PyMemberInfo<PyElement> classMemberInfo) {
1705       final PyElement element = classMemberInfo.getMember();
1706       return (element instanceof PyClass) && PyNames.OBJECT.equals(element.getName());
1707     }
1708   }
1709
1710   /**
1711    * Sometimes you do not know real FQN of some class, but you know class name and its package.
1712    * I.e. <code>django.apps.conf.AppConfig</code> is not documented, but you know
1713    * <code>AppConfig</code> and <code>django</code> package.
1714    *
1715    * @param symbol element to check (class or function)
1716    * @param expectedPackage package like "django"
1717    * @param expectedName expected name (i.e. AppConfig)
1718    * @return true if element in package
1719    */
1720   public static boolean isSymbolInPackage(@NotNull final PyQualifiedNameOwner symbol,
1721                                           @NotNull final String expectedPackage,
1722                                           @NotNull final String expectedName) {
1723     final String qualifiedNameString = symbol.getQualifiedName();
1724     if (qualifiedNameString == null) {
1725       return false;
1726     }
1727     final QualifiedName qualifiedName = QualifiedName.fromDottedString(qualifiedNameString);
1728     final String aPackage = qualifiedName.getFirstComponent();
1729     if (!(expectedPackage.equals(aPackage))) {
1730       return false;
1731     }
1732     final String symboldName = qualifiedName.getLastComponent();
1733     return expectedName.equals(symboldName);
1734   }
1735
1736   /**
1737    * Checks that given class is the root of class hierarchy, i.e. it's either {@code object} or
1738    * special {@link PyNames#FAKE_OLD_BASE} class for old-style classes.
1739    *
1740    * @param cls    Python class to check
1741    * @see PyBuiltinCache
1742    * @see PyNames#FAKE_OLD_BASE
1743    */
1744   public static boolean isObjectClass(@NotNull PyClass cls) {
1745     final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(cls);
1746     return cls == builtinCache.getClass(PyNames.OBJECT) || cls == builtinCache.getClass(PyNames.FAKE_OLD_BASE);
1747   }
1748
1749   /**
1750    * Checks that given type is the root of type hierarchy, i.e. it's type of either {@code object} or special
1751    * {@link PyNames#FAKE_OLD_BASE} class for old-style classes.
1752    *
1753    * @param type   Python class to check
1754    * @param anchor arbitrary PSI element to find appropriate SDK
1755    * @see PyBuiltinCache
1756    * @see PyNames#FAKE_OLD_BASE
1757    */
1758   public static boolean isObjectType(@NotNull PyType type, @NotNull PsiElement anchor) {
1759     final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(anchor);
1760     return type == builtinCache.getObjectType() || type == builtinCache.getOldstyleClassobjType();
1761   }
1762
1763   public static boolean isInScratchFile(@NotNull PsiElement element) {
1764     PsiFile file = element.getContainingFile();
1765     return file != null && ScratchRootType.getInstance().isScratchFile(file.getVirtualFile());
1766   }
1767
1768   /**
1769    * This helper class allows to collect various information about AST nodes composing {@link PyStringLiteralExpression}.
1770    */
1771   public static final class StringNodeInfo {
1772     private final ASTNode myNode;
1773     private final String myPrefix;
1774     private final String myQuote;
1775     private final TextRange myContentRange;
1776
1777     public StringNodeInfo(@NotNull ASTNode node) {
1778       if (!PyTokenTypes.STRING_NODES.contains(node.getElementType())) {
1779         throw new IllegalArgumentException("Node must be valid Python string literal token, but " + node.getElementType() + " was given");
1780       }
1781       myNode = node;
1782       final String nodeText = node.getText();
1783       final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(nodeText);
1784       myPrefix = nodeText.substring(0, prefixLength);
1785       myContentRange = PyStringLiteralExpressionImpl.getNodeTextRange(nodeText);
1786       myQuote = nodeText.substring(prefixLength, myContentRange.getStartOffset());
1787     }
1788
1789     public StringNodeInfo(@NotNull PsiElement element) {
1790       this(element.getNode());
1791     }
1792
1793     @NotNull
1794     public ASTNode getNode() {
1795       return myNode;
1796     }
1797
1798     /**
1799      * @return string prefix, e.g. "UR", "b" etc.
1800      */
1801     @NotNull
1802     public String getPrefix() {
1803       return myPrefix;
1804     }
1805
1806     /**
1807      * @return content of the string node between quotes
1808      */
1809     @NotNull
1810     public String getContent() {
1811       return myContentRange.substring(myNode.getText());
1812     }
1813
1814     /**
1815      * @return <em>relative</em> range of the content (excluding prefix and quotes)
1816      * @see #getAbsoluteContentRange()
1817      */
1818     @NotNull
1819     public TextRange getContentRange() {
1820       return myContentRange;
1821     }
1822
1823     /**
1824      * @return <em>absolute</em> content range that accounts offset of the {@link #getNode() node} in the document
1825      */
1826     @NotNull
1827     public TextRange getAbsoluteContentRange() {
1828       return getContentRange().shiftRight(myNode.getStartOffset());
1829     }
1830
1831     /**
1832      * @return the first character of {@link #getQuote()}
1833      */
1834     public char getSingleQuote() {
1835       return myQuote.charAt(0);
1836     }
1837
1838     @NotNull
1839     public String getQuote() {
1840       return myQuote;
1841     }
1842
1843     public boolean isTripleQuoted() {
1844       return myQuote.length() == 3;
1845     }
1846
1847     /**
1848      * @return true if string literal ends with starting quote
1849      */
1850     public boolean isTerminated() {
1851       final String text = myNode.getText();
1852       return text.length() - myPrefix.length() >= myQuote.length() * 2 && text.endsWith(myQuote);
1853     }
1854
1855     /**
1856      * @return true if given string node contains "u" or "U" prefix
1857      */
1858     public boolean isUnicode() {
1859       return StringUtil.containsIgnoreCase(myPrefix, "u");
1860     }
1861
1862     /**
1863      * @return true if given string node contains "r" or "R" prefix
1864      */
1865     public boolean isRaw() {
1866       return StringUtil.containsIgnoreCase(myPrefix, "r");
1867     }
1868
1869     /**
1870      * @return true if given string node contains "b" or "B" prefix
1871      */
1872     public boolean isBytes() {
1873       return StringUtil.containsIgnoreCase(myPrefix, "b");
1874     }
1875
1876     /**
1877      * @return true if other string node has the same decorations, i.e. quotes and prefix
1878      */
1879     @Override
1880     public boolean equals(Object o) {
1881       if (this == o) return true;
1882       if (o == null || getClass() != o.getClass()) return false;
1883
1884       StringNodeInfo info = (StringNodeInfo)o;
1885
1886       return getQuote().equals(info.getQuote()) &&
1887              isRaw() == info.isRaw() &&
1888              isUnicode() == info.isUnicode() &&
1889              isBytes() == info.isBytes();
1890     }
1891   }
1892 }