001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.jet.lang.psi;
018
019import com.google.common.base.Function;
020import com.google.common.base.Predicate;
021import com.google.common.collect.Lists;
022import com.intellij.lang.ASTNode;
023import com.intellij.psi.PsiComment;
024import com.intellij.psi.PsiElement;
025import com.intellij.psi.PsiFile;
026import com.intellij.psi.PsiWhiteSpace;
027import com.intellij.psi.impl.CheckUtil;
028import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
029import com.intellij.psi.tree.IElementType;
030import com.intellij.psi.util.PsiTreeUtil;
031import com.intellij.util.codeInsight.CommentUtilCore;
032import com.intellij.util.containers.ContainerUtil;
033import org.jetbrains.annotations.NotNull;
034import org.jetbrains.annotations.Nullable;
035import org.jetbrains.jet.JetNodeTypes;
036import org.jetbrains.jet.kdoc.psi.api.KDocElement;
037import org.jetbrains.jet.lang.parsing.JetExpressionParsing;
038import org.jetbrains.jet.lang.resolve.ImportPath;
039import org.jetbrains.jet.lang.resolve.name.FqName;
040import org.jetbrains.jet.lang.resolve.name.Name;
041import org.jetbrains.jet.lang.types.expressions.OperatorConventions;
042import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
043import org.jetbrains.jet.lexer.JetToken;
044import org.jetbrains.jet.lexer.JetTokens;
045
046import java.util.Collection;
047import java.util.HashSet;
048import java.util.List;
049import java.util.Set;
050
051public class JetPsiUtil {
052
053    public static final Name NO_NAME_PROVIDED = Name.special("<no name provided>");
054    public static final Name ROOT_NAMESPACE_NAME = Name.special("<root namespace>");
055
056    private JetPsiUtil() {
057    }
058
059    public static <D> void visitChildren(@NotNull JetElement element, @NotNull JetTreeVisitor<D> visitor, D data) {
060        PsiElement child = element.getFirstChild();
061        while (child != null) {
062            if (child instanceof JetElement) {
063                ((JetElement) child).accept(visitor, data);
064            }
065            child = child.getNextSibling();
066        }
067    }
068
069    @Nullable
070    public static JetExpression deparenthesizeWithNoTypeResolution(@NotNull JetExpression expression) {
071        return deparenthesizeWithResolutionStrategy(expression, null);
072    }
073
074    @Nullable
075    @Deprecated //Use JetPsiUtil.deparenthesizeWithNoTypeResolution() or ExpressionTypingServices.deparenthesize()
076    public static JetExpression deparenthesizeWithResolutionStrategy(
077            @NotNull JetExpression expression,
078            @Nullable Function<JetTypeReference, Void> typeResolutionStrategy
079    ) {
080        if (expression instanceof JetBinaryExpressionWithTypeRHS) {
081            JetBinaryExpressionWithTypeRHS binaryExpression = (JetBinaryExpressionWithTypeRHS) expression;
082            JetSimpleNameExpression operationSign = binaryExpression.getOperationReference();
083            if (JetTokens.COLON.equals(operationSign.getReferencedNameElementType())) {
084                expression = binaryExpression.getLeft();
085                JetTypeReference typeReference = binaryExpression.getRight();
086                if (typeResolutionStrategy != null && typeReference != null) {
087                    typeResolutionStrategy.apply(typeReference);
088                }
089            }
090        }
091        else if (expression instanceof JetPrefixExpression) {
092            if (JetTokens.LABELS.contains(((JetPrefixExpression) expression).getOperationReference().getReferencedNameElementType())) {
093                JetExpression baseExpression = ((JetPrefixExpression) expression).getBaseExpression();
094                if (baseExpression != null) {
095                    expression = baseExpression;
096                }
097            }
098        }
099        if (expression instanceof JetParenthesizedExpression) {
100            JetExpression innerExpression = ((JetParenthesizedExpression) expression).getExpression();
101            return innerExpression != null ? deparenthesizeWithResolutionStrategy(innerExpression, typeResolutionStrategy) : null;
102        }
103        return expression;
104    }
105
106    @NotNull
107    public static Name safeName(@Nullable String name) {
108        return name == null ? NO_NAME_PROVIDED : Name.identifier(name);
109    }
110
111    @NotNull
112    public static Set<JetElement> findRootExpressions(@NotNull Collection<JetElement> unreachableElements) {
113        Set<JetElement> rootElements = new HashSet<JetElement>();
114        final Set<JetElement> shadowedElements = new HashSet<JetElement>();
115        JetVisitorVoid shadowAllChildren = new JetVisitorVoid() {
116            @Override
117            public void visitJetElement(JetElement element) {
118                if (shadowedElements.add(element)) {
119                    element.acceptChildren(this);
120                }
121            }
122        };
123
124        for (JetElement element : unreachableElements) {
125            if (shadowedElements.contains(element)) continue;
126            element.acceptChildren(shadowAllChildren);
127
128            rootElements.removeAll(shadowedElements);
129            rootElements.add(element);
130        }
131        return rootElements;
132    }
133
134    @NotNull
135    public static String unquoteIdentifier(@NotNull String quoted) {
136        if (quoted.indexOf('`') < 0) {
137            return quoted;
138        }
139
140        if (quoted.startsWith("`") && quoted.endsWith("`") && quoted.length() >= 2) {
141            return quoted.substring(1, quoted.length() - 1);
142        }
143        else {
144            return quoted;
145        }
146    }
147
148    @NotNull
149    public static String unquoteIdentifierOrFieldReference(@NotNull String quoted) {
150        if (quoted.indexOf('`') < 0) {
151            return quoted;
152        }
153
154        if (quoted.startsWith("$")) {
155            return "$" + unquoteIdentifier(quoted.substring(1));
156        }
157        else {
158            return unquoteIdentifier(quoted);
159        }
160    }
161
162    @NotNull
163    public static FqName getFQName(@NotNull JetFile file) {
164        JetNamespaceHeader header = file.getNamespaceHeader();
165        return header != null ? header.getFqName() : FqName.ROOT;
166    }
167
168    @Nullable
169    public static FqName getFQName(@NotNull JetNamedDeclaration namedDeclaration) {
170        if (namedDeclaration instanceof JetObjectDeclarationName) {
171            JetNamedDeclaration objectDeclaration = PsiTreeUtil.getParentOfType(namedDeclaration, JetObjectDeclaration.class);
172            if (objectDeclaration == null) {
173                objectDeclaration = PsiTreeUtil.getParentOfType(namedDeclaration, JetEnumEntry.class);
174            }
175
176            if (objectDeclaration == null) {
177                return null;
178            }
179
180            return getFQName(objectDeclaration);
181        }
182
183        Name name = namedDeclaration.getNameAsName();
184        if (name == null) {
185            return null;
186        }
187
188        PsiElement parent = namedDeclaration.getParent();
189        if (parent instanceof JetClassBody) {
190            // One nesting to JetClassBody doesn't affect to qualified name
191            parent = parent.getParent();
192        }
193
194        FqName firstPart = null;
195        if (parent instanceof JetFile) {
196            firstPart = getFQName((JetFile) parent);
197        }
198        else if (parent instanceof JetNamedFunction || parent instanceof JetClass) {
199            firstPart = getFQName((JetNamedDeclaration) parent);
200        }
201        else if (namedDeclaration instanceof JetParameter) {
202            JetClass constructorClass = getClassIfParameterIsProperty((JetParameter) namedDeclaration);
203            if (constructorClass != null) {
204                firstPart = getFQName(constructorClass);
205            }
206        }
207        else if (parent instanceof JetObjectDeclaration) {
208            if (parent.getParent() instanceof JetClassObject) {
209                JetClassOrObject classOrObject = PsiTreeUtil.getParentOfType(parent, JetClassOrObject.class);
210                if (classOrObject != null) {
211                    firstPart = getFQName(classOrObject);
212                }
213            }
214            else {
215                firstPart = getFQName((JetNamedDeclaration) parent);
216            }
217        }
218
219        if (firstPart == null) {
220            return null;
221        }
222
223        return firstPart.child(name);
224    }
225
226    /** @return <code>null</code> iff the tye has syntactic errors */
227    @Nullable
228    public static FqName toQualifiedName(@NotNull JetUserType userType) {
229        List<String> reversedNames = Lists.newArrayList();
230
231        JetUserType current = userType;
232        while (current != null) {
233            String name = current.getReferencedName();
234            if (name == null) return null;
235
236            reversedNames.add(name);
237            current = current.getQualifier();
238        }
239
240        return FqName.fromSegments(ContainerUtil.reverse(reversedNames));
241    }
242
243    @Nullable
244    public static Name getShortName(@NotNull JetAnnotationEntry annotation) {
245        JetTypeReference typeReference = annotation.getTypeReference();
246        assert typeReference != null : "Annotation entry hasn't typeReference " + annotation.getText();
247        JetTypeElement typeElement = typeReference.getTypeElement();
248        if (typeElement instanceof JetUserType) {
249            JetUserType userType = (JetUserType) typeElement;
250            String shortName = userType.getReferencedName();
251            if (shortName != null) {
252                return Name.identifier(shortName);
253            }
254        }
255        return null;
256    }
257
258    public static boolean isDeprecated(@NotNull JetModifierListOwner owner) {
259        JetModifierList modifierList = owner.getModifierList();
260        if (modifierList != null) {
261            List<JetAnnotationEntry> annotationEntries = modifierList.getAnnotationEntries();
262            for (JetAnnotationEntry annotation : annotationEntries) {
263                Name shortName = getShortName(annotation);
264                if (KotlinBuiltIns.getInstance().getDeprecatedAnnotation().getName().equals(shortName)) {
265                    return true;
266                }
267            }
268        }
269        return false;
270    }
271
272    @Nullable
273    @IfNotParsed
274    public static ImportPath getImportPath(@NotNull JetImportDirective importDirective) {
275        if (PsiTreeUtil.hasErrorElements(importDirective)) {
276            return null;
277        }
278
279        FqName importFqn = getFQName(importDirective.getImportedReference());
280        if (importFqn == null) {
281            return null;
282        }
283
284        Name alias = null;
285        String aliasName = importDirective.getAliasName();
286        if (aliasName != null) {
287            alias = Name.identifier(aliasName);
288        }
289
290        return new ImportPath(importFqn, importDirective.isAllUnder(), alias);
291    }
292
293    @Nullable
294    public static <T extends PsiElement> T getDirectParentOfTypeForBlock(@NotNull JetBlockExpression block, @NotNull Class<T> aClass) {
295        T parent = PsiTreeUtil.getParentOfType(block, aClass);
296        if (parent instanceof JetIfExpression) {
297            JetIfExpression ifExpression = (JetIfExpression) parent;
298            if (ifExpression.getElse() == block || ifExpression.getThen() == block) {
299                return parent;
300            }
301        }
302        if (parent instanceof JetWhenExpression) {
303            JetWhenExpression whenExpression = (JetWhenExpression) parent;
304            for (JetWhenEntry whenEntry : whenExpression.getEntries()) {
305                if (whenEntry.getExpression() == block) {
306                    return parent;
307                }
308            }
309        }
310        if (parent instanceof JetFunctionLiteral) {
311            JetFunctionLiteral functionLiteral = (JetFunctionLiteral) parent;
312            if (functionLiteral.getBodyExpression() == block) {
313                return parent;
314            }
315        }
316        if (parent instanceof JetTryExpression) {
317            JetTryExpression tryExpression = (JetTryExpression) parent;
318            if (tryExpression.getTryBlock() == block) {
319                return parent;
320            }
321            for (JetCatchClause clause : tryExpression.getCatchClauses()) {
322                if (clause.getCatchBody() == block) {
323                    return parent;
324                }
325            }
326        }
327        return null;
328    }
329
330    public static boolean isImplicitlyUsed(@NotNull JetElement element) {
331        PsiElement parent = element.getParent();
332        if (!(parent instanceof JetBlockExpression)) return true;
333        JetBlockExpression block = (JetBlockExpression) parent;
334        List<JetElement> statements = block.getStatements();
335        if (statements.get(statements.size() - 1) == element) {
336            JetExpression expression = getDirectParentOfTypeForBlock(block, JetIfExpression.class);
337            if (expression == null) {
338                expression = getDirectParentOfTypeForBlock(block, JetWhenExpression.class);
339            }
340            if (expression == null) {
341                expression = getDirectParentOfTypeForBlock(block, JetFunctionLiteral.class);
342            }
343            if (expression == null) {
344                expression = getDirectParentOfTypeForBlock(block, JetTryExpression.class);
345            }
346            if (expression != null) {
347                return isImplicitlyUsed(expression);
348            }
349        }
350        return false;
351    }
352
353    public static void deleteClass(@NotNull JetClassOrObject clazz) {
354        CheckUtil.checkWritable(clazz);
355        JetFile file = (JetFile) clazz.getContainingFile();
356        List<JetDeclaration> declarations = file.getDeclarations();
357        if (declarations.size() == 1) {
358            file.delete();
359        }
360        else {
361            PsiElement parent = clazz.getParent();
362            CodeEditUtil.removeChild(parent.getNode(), clazz.getNode());
363        }
364    }
365
366    @Nullable
367    public static Name getAliasName(@NotNull JetImportDirective importDirective) {
368        String aliasName = importDirective.getAliasName();
369        JetExpression importedReference = importDirective.getImportedReference();
370        if (importedReference == null) {
371            return null;
372        }
373        JetSimpleNameExpression referenceExpression = getLastReference(importedReference);
374        if (aliasName == null) {
375            aliasName = referenceExpression != null ? referenceExpression.getReferencedName() : null;
376        }
377
378        return aliasName != null && !aliasName.isEmpty() ? Name.identifier(aliasName) : null;
379    }
380
381    @Nullable
382    public static JetSimpleNameExpression getLastReference(@NotNull JetExpression importedReference) {
383        if (importedReference instanceof JetDotQualifiedExpression) {
384            JetExpression selectorExpression = ((JetDotQualifiedExpression) importedReference).getSelectorExpression();
385            return (selectorExpression instanceof JetSimpleNameExpression) ? (JetSimpleNameExpression) selectorExpression : null;
386        }
387        if (importedReference instanceof JetSimpleNameExpression) {
388            return (JetSimpleNameExpression) importedReference;
389        }
390        return null;
391    }
392
393    public static boolean isFirstPartInQualified(@NotNull JetSimpleNameExpression nameExpression) {
394        @SuppressWarnings("unchecked") JetUserType userType = PsiTreeUtil.getParentOfType(nameExpression, JetUserType.class, true,
395                                                                                          JetDeclaration.class);
396        if (userType != null) {
397            return PsiTreeUtil.isAncestor(userType.getFirstChild(), nameExpression, false);
398        }
399
400        @SuppressWarnings("unchecked") JetQualifiedExpression qualifiedExpression = PsiTreeUtil.getParentOfType(nameExpression, JetQualifiedExpression.class, true, JetDeclaration.class);
401        if (qualifiedExpression != null) {
402            return PsiTreeUtil.isAncestor(qualifiedExpression.getFirstChild(), nameExpression, false);
403        }
404
405        return true;
406    }
407
408    public static boolean isVoidType(@Nullable JetTypeReference typeReference) {
409        if (typeReference == null) {
410            return false;
411        }
412
413        return KotlinBuiltIns.getInstance().getUnit().getName().asString().equals(typeReference.getText());
414    }
415
416    public static boolean isSafeCall(@NotNull Call call) {
417        ASTNode callOperationNode = call.getCallOperationNode();
418        return callOperationNode != null && callOperationNode.getElementType() == JetTokens.SAFE_ACCESS;
419    }
420
421    public static boolean isFunctionLiteralWithoutDeclaredParameterTypes(@Nullable JetExpression expression) {
422        if (!(expression instanceof JetFunctionLiteralExpression)) return false;
423        JetFunctionLiteralExpression functionLiteral = (JetFunctionLiteralExpression) expression;
424        for (JetParameter parameter : functionLiteral.getValueParameters()) {
425            if (parameter.getTypeReference() != null) {
426                return false;
427            }
428        }
429        return true;
430    }
431
432    public static boolean isScriptDeclaration(@NotNull JetDeclaration namedDeclaration) {
433        return getScript(namedDeclaration) != null;
434    }
435
436    @Nullable
437    public static JetScript getScript(@NotNull JetDeclaration namedDeclaration) {
438        PsiElement parent = namedDeclaration.getParent();
439        if (parent != null && parent.getParent() instanceof JetScript) {
440            return (JetScript) parent.getParent();
441        }
442        else {
443            return null;
444        }
445    }
446
447    public static boolean isVariableNotParameterDeclaration(@NotNull JetDeclaration declaration) {
448        if (!(declaration instanceof JetVariableDeclaration)) return false;
449        if (declaration instanceof JetProperty) return true;
450        assert declaration instanceof JetMultiDeclarationEntry;
451        JetMultiDeclarationEntry multiDeclarationEntry = (JetMultiDeclarationEntry) declaration;
452        return !(multiDeclarationEntry.getParent().getParent() instanceof JetForExpression);
453    }
454
455    @Nullable
456    public static Name getConventionName(@NotNull JetSimpleNameExpression simpleNameExpression) {
457        if (simpleNameExpression.getIdentifier() != null) {
458            return simpleNameExpression.getReferencedNameAsName();
459        }
460
461        PsiElement firstChild = simpleNameExpression.getFirstChild();
462        if (firstChild != null) {
463            IElementType elementType = firstChild.getNode().getElementType();
464            if (elementType instanceof JetToken) {
465                JetToken jetToken = (JetToken) elementType;
466                return OperatorConventions.getNameForOperationSymbol(jetToken);
467            }
468        }
469
470        return null;
471    }
472
473    @Nullable
474    public static PsiElement getTopmostParentOfTypes(@Nullable PsiElement element, @NotNull Class<? extends PsiElement>... parentTypes) {
475        if (element == null) {
476            return null;
477        }
478
479        PsiElement result = null;
480        PsiElement parent = element.getParent();
481        while (parent != null) {
482            if (PsiTreeUtil.instanceOf(parent, parentTypes)) {
483                result = parent;
484            }
485
486            parent = parent.getParent();
487        }
488
489        return result;
490    }
491
492    public static boolean isNullConstant(@NotNull JetExpression expression) {
493        JetExpression deparenthesized = deparenthesizeWithNoTypeResolution(expression);
494        return deparenthesized instanceof JetConstantExpression && deparenthesized.getNode().getElementType() == JetNodeTypes.NULL;
495    }
496
497    public static boolean isAbstract(@NotNull JetDeclarationWithBody declaration) {
498        return declaration.getBodyExpression() == null;
499    }
500
501    public static boolean isBackingFieldReference(@NotNull JetSimpleNameExpression expression) {
502        return expression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER;
503    }
504
505    public static boolean isBackingFieldReference(@Nullable JetElement element) {
506        return element instanceof JetSimpleNameExpression && isBackingFieldReference((JetSimpleNameExpression)element);
507    }
508
509    @Nullable
510    private static FqName getFQName(@Nullable JetExpression expression) {
511        if (expression == null) {
512            return null;
513        }
514
515        if (expression instanceof JetDotQualifiedExpression) {
516            JetDotQualifiedExpression dotQualifiedExpression = (JetDotQualifiedExpression) expression;
517            FqName parentFqn = getFQName(dotQualifiedExpression.getReceiverExpression());
518            Name child = getName(dotQualifiedExpression.getSelectorExpression());
519
520            return parentFqn != null && child != null ? parentFqn.child(child) : null;
521        }
522        else if (expression instanceof JetSimpleNameExpression) {
523            JetSimpleNameExpression simpleNameExpression = (JetSimpleNameExpression) expression;
524            return FqName.topLevel(simpleNameExpression.getReferencedNameAsName());
525        }
526        else {
527            throw new IllegalArgumentException("Can't construct fqn for: " + expression.getClass().toString());
528        }
529    }
530
531    @Nullable
532    private static Name getName(@Nullable JetExpression expression) {
533        if (expression == null) {
534            return null;
535        }
536
537        if (expression instanceof JetSimpleNameExpression) {
538            return ((JetSimpleNameExpression) expression).getReferencedNameAsName();
539        }
540        else {
541            throw new IllegalArgumentException("Can't construct name for: " + expression.getClass().toString());
542        }
543    }
544
545    @Nullable
546    public static JetElement getLastStatementInABlock(@Nullable JetBlockExpression blockExpression) {
547        if (blockExpression == null) return null;
548        List<JetElement> statements = blockExpression.getStatements();
549        return statements.isEmpty() ? null : statements.get(statements.size() - 1);
550    }
551
552    public static boolean isLocalClass(@NotNull JetClassOrObject classOrObject) {
553        return getOutermostClassOrObject(classOrObject) == null;
554    }
555
556    public static boolean isTrait(@NotNull JetClassOrObject classOrObject) {
557        return classOrObject instanceof JetClass && ((JetClass) classOrObject).isTrait();
558    }
559
560    @Nullable
561    public static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
562        JetClassOrObject current = classOrObject;
563        while (true) {
564            PsiElement parent = current.getParent();
565            assert classOrObject.getParent() != null : "Class with no parent: " + classOrObject.getText();
566
567            if (parent instanceof PsiFile) {
568                return current;
569            }
570            if (parent instanceof JetClassObject) {
571                // current class IS the class object declaration
572                parent = parent.getParent();
573                assert parent instanceof JetClassBody : "Parent of class object is not a class body: " + parent;
574            }
575            if (!(parent instanceof JetClassBody)) {
576                // It is a local class, no legitimate outer
577                return null;
578            }
579
580            current = (JetClassOrObject) parent.getParent();
581        }
582    }
583
584    @Nullable
585    public static JetClass getClassIfParameterIsProperty(@NotNull JetParameter jetParameter) {
586        if (jetParameter.getValOrVarNode() != null) {
587            PsiElement parent = jetParameter.getParent();
588            if (parent instanceof JetParameterList && parent.getParent() instanceof JetClass) {
589                return (JetClass) parent.getParent();
590            }
591        }
592
593        return null;
594    }
595
596    @Nullable
597    private static IElementType getOperation(@NotNull JetExpression expression) {
598        if (expression instanceof JetQualifiedExpression) {
599            return ((JetQualifiedExpression) expression).getOperationSign();
600        }
601        else if (expression instanceof JetOperationExpression) {
602            return ((JetOperationExpression) expression).getOperationReference().getReferencedNameElementType();
603        }
604        return null;
605    }
606
607    private static int getPrecedenceOfOperation(@NotNull JetExpression expression, @NotNull IElementType operation) {
608        if (expression instanceof JetPostfixExpression) return 0;
609        if (expression instanceof JetQualifiedExpression) return 0;
610        if (expression instanceof JetPrefixExpression) return 1;
611
612        for (JetExpressionParsing.Precedence precedence : JetExpressionParsing.Precedence.values()) {
613            if (precedence != JetExpressionParsing.Precedence.PREFIX && precedence != JetExpressionParsing.Precedence.POSTFIX &&
614                precedence.getOperations().contains(operation)) {
615                return precedence.ordinal();
616            }
617        }
618        throw new IllegalStateException("Unknown operation");
619    }
620
621    public static boolean areParenthesesUseless(@NotNull JetParenthesizedExpression expression) {
622        JetExpression innerExpression = expression.getExpression();
623        JetExpression parentExpression = PsiTreeUtil.getParentOfType(expression, JetExpression.class, true);
624        if (innerExpression == null || parentExpression == null) return true;
625
626        IElementType innerOperation = getOperation(innerExpression);
627        IElementType parentOperation = getOperation(parentExpression);
628
629        // 'return (@label{...})' case
630        if (parentExpression instanceof JetReturnExpression && innerOperation == JetTokens.LABEL_IDENTIFIER) {
631            return false;
632        }
633
634        // '(x: Int) < y' case
635        if (innerExpression instanceof JetBinaryExpressionWithTypeRHS && parentOperation == JetTokens.LT) {
636            return false;
637        }
638
639        // associative operations
640        if (innerOperation == parentOperation && (innerOperation == JetTokens.OROR || innerOperation == JetTokens.ANDAND)) {
641            return true;
642        }
643
644        if (innerOperation == null) return true;
645        if (parentExpression instanceof JetArrayAccessExpression) {
646            return ((JetArrayAccessExpression) parentExpression).getArrayExpression() != expression;
647        }
648        if (parentOperation == null) return true;
649
650        int innerPrecedence = getPrecedenceOfOperation(innerExpression, innerOperation);
651        int parentPrecedence = getPrecedenceOfOperation(parentExpression, parentOperation);
652        return innerPrecedence < parentPrecedence;
653    }
654
655    public static boolean isAssignment(@NotNull PsiElement element) {
656        return element instanceof JetBinaryExpression &&
657               JetTokens.ALL_ASSIGNMENTS.contains(((JetBinaryExpression) element).getOperationToken());
658    }
659
660    public static boolean isOrdinaryAssignment(@NotNull PsiElement element) {
661        return element instanceof JetBinaryExpression &&
662               ((JetBinaryExpression) element).getOperationToken().equals(JetTokens.EQ);
663    }
664
665    @Nullable
666    public static JetElement getOutermostLastBlockElement(@Nullable JetElement element, @NotNull Predicate<JetElement> checkElement) {
667        if (element == null) return null;
668
669        if (!(element instanceof JetBlockExpression)) return checkElement.apply(element) ? element : null;
670
671        JetBlockExpression block = (JetBlockExpression)element;
672        int n = block.getStatements().size();
673
674        if (n == 0) return null;
675
676        JetElement lastElement = block.getStatements().get(n - 1);
677        return checkElement.apply(lastElement) ? lastElement : null;
678    }
679
680    @Nullable
681    public static PsiElement getParentByTypeAndPredicate(
682            @Nullable PsiElement element, @NotNull Class<? extends PsiElement> aClass, @NotNull Predicate<PsiElement> predicate, boolean strict) {
683        if (element == null) return null;
684        if (strict) {
685            element = element.getParent();
686        }
687
688        while (element != null) {
689            //noinspection unchecked
690            if (aClass.isInstance(element) && predicate.apply(element)) {
691                //noinspection unchecked
692                return element;
693            }
694            if (element instanceof PsiFile) return null;
695            element = element.getParent();
696        }
697
698        return null;
699    }
700
701    public static boolean checkVariableDeclarationInBlock(@NotNull JetBlockExpression block, @NotNull String varName) {
702        for (JetElement element : block.getStatements()) {
703            if (element instanceof JetVariableDeclaration) {
704                if (((JetVariableDeclaration) element).getNameAsSafeName().asString().equals(varName)) {
705                    return true;
706                }
707            }
708        }
709
710        return false;
711    }
712
713    public static boolean checkWhenExpressionHasSingleElse(@NotNull JetWhenExpression whenExpression) {
714        int elseCount = 0;
715        for (JetWhenEntry entry : whenExpression.getEntries()) {
716            if (entry.isElse()) {
717                elseCount++;
718            }
719        }
720        return (elseCount == 1);
721    }
722
723    @Nullable
724    public static PsiElement skipTrailingWhitespacesAndComments(@Nullable PsiElement element)  {
725        return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class, PsiComment.class);
726    }
727
728    public static final Predicate<JetElement> ANY_JET_ELEMENT = new Predicate<JetElement>() {
729        @Override
730        public boolean apply(@Nullable JetElement input) {
731            return true;
732        }
733    };
734
735    @NotNull
736    public static String getText(@Nullable PsiElement element) {
737        return element != null ? element.getText() : "";
738    }
739
740    @Nullable
741    public static String getNullableText(@Nullable PsiElement element) {
742        return element != null ? element.getText() : null;
743    }
744
745    /**
746     * CommentUtilCore.isComment fails if element <strong>inside</strong> comment.
747     *
748     * Also, we can not add KDocTokens to COMMENTS TokenSet, because it is used in JetParserDefinition.getCommentTokens(),
749     * and therefor all COMMENTS tokens will be ignored by PsiBuilder.
750     *
751     * @param element
752     * @return
753     */
754    public static boolean isInComment(PsiElement element) {
755        return CommentUtilCore.isComment(element) || element instanceof KDocElement;
756    }
757
758    @Nullable
759    public static PsiElement getOutermostParent(@NotNull PsiElement element, @NotNull PsiElement upperBound, boolean strict) {
760        PsiElement parent = strict ? element.getParent() : element;
761        while (parent != null && parent.getParent() != upperBound) {
762            parent = parent.getParent();
763        }
764
765        return parent;
766    }
767
768    public static <T extends PsiElement> T getLastChildByType(@NotNull PsiElement root, @NotNull Class<? extends T>... elementTypes) {
769        PsiElement[] children = root.getChildren();
770
771        for (int i = children.length - 1; i >= 0; i--) {
772            if (PsiTreeUtil.instanceOf(children[i], elementTypes)) {
773                //noinspection unchecked
774                return (T) children[i];
775            }
776        }
777
778        return null;
779    }
780
781    @Nullable
782    public static <T extends JetElement> T getOutermostJetElement(
783            @Nullable PsiElement root,
784            boolean first,
785            @NotNull final Class<? extends T>... elementTypes
786    ) {
787        if (!(root instanceof JetElement)) return null;
788
789        final List<T> results = Lists.newArrayList();
790
791        ((JetElement) root).accept(
792                new JetVisitorVoid() {
793                    @Override
794                    public void visitJetElement(JetElement element) {
795                        if (PsiTreeUtil.instanceOf(element, elementTypes)) {
796                            //noinspection unchecked
797                            results.add((T) element);
798                        }
799                        else {
800                            element.acceptChildren(this);
801                        }
802                    }
803                }
804        );
805
806        if (results.isEmpty()) return null;
807
808        return first ? results.get(0) : results.get(results.size() - 1);
809    }
810
811    @Nullable
812    public static PsiElement findChildByType(@NotNull PsiElement element, @NotNull IElementType type) {
813        ASTNode node = element.getNode().findChildByType(type);
814        return node == null ? null : node.getPsi();
815    }
816
817    @Nullable
818    public static JetExpression getCalleeExpressionIfAny(@NotNull JetExpression expression) {
819        if (expression instanceof JetCallElement) {
820            JetCallElement callExpression = (JetCallElement) expression;
821            return callExpression.getCalleeExpression();
822        }
823        if (expression instanceof JetQualifiedExpression) {
824            JetExpression selectorExpression = ((JetQualifiedExpression) expression).getSelectorExpression();
825            if (selectorExpression != null) {
826                return getCalleeExpressionIfAny(selectorExpression);
827            }
828        }
829        if (expression instanceof JetUnaryExpression) {
830            return ((JetUnaryExpression) expression).getOperationReference();
831        }
832        if (expression instanceof JetBinaryExpression) {
833            return ((JetBinaryExpression) expression).getOperationReference();
834        }
835        return null;
836    }
837
838    @Nullable
839    public static PsiElement skipSiblingsBackwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
840        if (element == null) return null;
841        for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) {
842            if (elementsToSkip.apply(e)) continue;
843            return e;
844        }
845        return null;
846    }
847
848    @Nullable
849    public static PsiElement skipSiblingsForwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
850        if (element == null) return null;
851        for (PsiElement e = element.getNextSibling(); e != null; e = e.getNextSibling()) {
852            if (elementsToSkip.apply(e)) continue;
853            return e;
854        }
855        return null;
856    }
857}