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    
017    package org.jetbrains.jet.lang.psi;
018    
019    import com.google.common.base.Function;
020    import com.google.common.base.Predicate;
021    import com.google.common.collect.Lists;
022    import com.intellij.lang.ASTNode;
023    import com.intellij.openapi.util.Condition;
024    import com.intellij.psi.*;
025    import com.intellij.psi.impl.CheckUtil;
026    import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
027    import com.intellij.psi.tree.IElementType;
028    import com.intellij.psi.util.PsiTreeUtil;
029    import com.intellij.util.codeInsight.CommentUtilCore;
030    import com.intellij.util.containers.ContainerUtil;
031    import org.jetbrains.annotations.NotNull;
032    import org.jetbrains.annotations.Nullable;
033    import org.jetbrains.jet.JetNodeTypes;
034    import org.jetbrains.jet.kdoc.psi.api.KDocElement;
035    import org.jetbrains.jet.lang.parsing.JetExpressionParsing;
036    import org.jetbrains.jet.lang.resolve.ImportPath;
037    import org.jetbrains.jet.lang.resolve.name.FqName;
038    import org.jetbrains.jet.lang.resolve.name.Name;
039    import org.jetbrains.jet.lang.types.expressions.OperatorConventions;
040    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
041    import org.jetbrains.jet.lexer.JetToken;
042    import org.jetbrains.jet.lexer.JetTokens;
043    
044    import java.util.Collection;
045    import java.util.HashSet;
046    import java.util.List;
047    import java.util.Set;
048    
049    public class JetPsiUtil {
050    
051        public static final Name NO_NAME_PROVIDED = Name.special("<no name provided>");
052    
053        private JetPsiUtil() {
054        }
055    
056        public interface JetExpressionWrapper {
057            JetExpression getBaseExpression();
058        }
059    
060        public static <D> void visitChildren(@NotNull JetElement element, @NotNull JetTreeVisitor<D> visitor, D data) {
061            PsiElement child = element.getFirstChild();
062            while (child != null) {
063                if (child instanceof JetElement) {
064                    ((JetElement) child).accept(visitor, data);
065                }
066                child = child.getNextSibling();
067            }
068        }
069    
070        @NotNull
071        public static JetExpression safeDeparenthesize(@NotNull JetExpression expression, boolean deparenthesizeBinaryExpressionWithTypeRHS) {
072            JetExpression deparenthesized = deparenthesize(expression, deparenthesizeBinaryExpressionWithTypeRHS);
073            return deparenthesized != null ? deparenthesized : expression;
074        }
075    
076        @Nullable
077        public static JetExpression deparenthesize(@Nullable JetExpression expression) {
078            return deparenthesize(expression, true);
079        }
080    
081        @Nullable
082        public static JetExpression deparenthesize(
083                @Nullable JetExpression expression,
084                boolean deparenthesizeBinaryExpressionWithTypeRHS
085        ) {
086            return deparenthesizeWithResolutionStrategy(expression, deparenthesizeBinaryExpressionWithTypeRHS, null);
087        }
088    
089        @Nullable
090        @Deprecated //Use JetPsiUtil.deparenthesize() or ExpressionTypingServices.deparenthesize()
091        public static JetExpression deparenthesizeWithResolutionStrategy(
092                @Nullable JetExpression expression,
093                boolean deparenthesizeBinaryExpressionWithTypeRHS,
094                @Nullable Function<JetTypeReference, Void> typeResolutionStrategy
095        ) {
096            if (deparenthesizeBinaryExpressionWithTypeRHS && expression instanceof JetBinaryExpressionWithTypeRHS) {
097                JetBinaryExpressionWithTypeRHS binaryExpression = (JetBinaryExpressionWithTypeRHS) expression;
098                JetSimpleNameExpression operationSign = binaryExpression.getOperationReference();
099                if (JetTokens.COLON.equals(operationSign.getReferencedNameElementType())) {
100                    expression = binaryExpression.getLeft();
101                    JetTypeReference typeReference = binaryExpression.getRight();
102                    if (typeResolutionStrategy != null && typeReference != null) {
103                        typeResolutionStrategy.apply(typeReference);
104                    }
105                }
106            }
107            else if (expression instanceof JetPrefixExpression) {
108                JetExpression baseExpression = getBaseExpressionIfLabeledExpression((JetPrefixExpression) expression);
109                if (baseExpression != null) {
110                    expression = baseExpression;
111                }
112            }
113            else if (expression instanceof JetExpressionWrapper) {
114                expression = ((JetExpressionWrapper) expression).getBaseExpression();
115            }
116            if (expression instanceof JetParenthesizedExpression) {
117                JetExpression innerExpression = ((JetParenthesizedExpression) expression).getExpression();
118                return innerExpression != null ? deparenthesizeWithResolutionStrategy(
119                        innerExpression, deparenthesizeBinaryExpressionWithTypeRHS, typeResolutionStrategy) : null;
120            }
121            return expression;
122        }
123    
124        @Nullable
125        public static JetExpression getBaseExpressionIfLabeledExpression(@NotNull JetPrefixExpression expression) {
126            if (JetTokens.LABELS.contains(expression.getOperationReference().getReferencedNameElementType())) {
127                return expression.getBaseExpression();
128            }
129            return null;
130        }
131    
132        @NotNull
133        public static Name safeName(@Nullable String name) {
134            return name == null ? NO_NAME_PROVIDED : Name.identifier(name);
135        }
136    
137        @NotNull
138        public static Set<JetElement> findRootExpressions(@NotNull Collection<JetElement> unreachableElements) {
139            Set<JetElement> rootElements = new HashSet<JetElement>();
140            final Set<JetElement> shadowedElements = new HashSet<JetElement>();
141            JetVisitorVoid shadowAllChildren = new JetVisitorVoid() {
142                @Override
143                public void visitJetElement(JetElement element) {
144                    if (shadowedElements.add(element)) {
145                        element.acceptChildren(this);
146                    }
147                }
148            };
149    
150            for (JetElement element : unreachableElements) {
151                if (shadowedElements.contains(element)) continue;
152                element.acceptChildren(shadowAllChildren);
153    
154                rootElements.removeAll(shadowedElements);
155                rootElements.add(element);
156            }
157            return rootElements;
158        }
159    
160        @NotNull
161        public static String unquoteIdentifier(@NotNull String quoted) {
162            if (quoted.indexOf('`') < 0) {
163                return quoted;
164            }
165    
166            if (quoted.startsWith("`") && quoted.endsWith("`") && quoted.length() >= 2) {
167                return quoted.substring(1, quoted.length() - 1);
168            }
169            else {
170                return quoted;
171            }
172        }
173    
174        @NotNull
175        public static String unquoteIdentifierOrFieldReference(@NotNull String quoted) {
176            if (quoted.indexOf('`') < 0) {
177                return quoted;
178            }
179    
180            if (quoted.startsWith("$")) {
181                return "$" + unquoteIdentifier(quoted.substring(1));
182            }
183            else {
184                return unquoteIdentifier(quoted);
185            }
186        }
187    
188        @NotNull
189        public static FqName getFQName(@NotNull JetFile file) {
190            JetNamespaceHeader header = file.getNamespaceHeader();
191            return header != null ? header.getFqName() : FqName.ROOT;
192        }
193    
194        @Nullable
195        public static FqName getFQName(@NotNull JetNamedDeclaration namedDeclaration) {
196            if (namedDeclaration instanceof JetObjectDeclarationName) {
197                JetNamedDeclaration objectDeclaration = PsiTreeUtil.getParentOfType(namedDeclaration, JetObjectDeclaration.class);
198                if (objectDeclaration == null) {
199                    objectDeclaration = PsiTreeUtil.getParentOfType(namedDeclaration, JetEnumEntry.class);
200                }
201    
202                if (objectDeclaration == null) {
203                    return null;
204                }
205    
206                return getFQName(objectDeclaration);
207            }
208    
209            Name name = namedDeclaration.getNameAsName();
210            if (name == null) {
211                return null;
212            }
213    
214            PsiElement parent = namedDeclaration.getParent();
215            if (parent instanceof JetClassBody) {
216                // One nesting to JetClassBody doesn't affect to qualified name
217                parent = parent.getParent();
218            }
219    
220            FqName firstPart = null;
221            if (parent instanceof JetFile) {
222                firstPart = getFQName((JetFile) parent);
223            }
224            else if (parent instanceof JetNamedFunction || parent instanceof JetClass) {
225                firstPart = getFQName((JetNamedDeclaration) parent);
226            }
227            else if (namedDeclaration instanceof JetParameter) {
228                JetClass constructorClass = getClassIfParameterIsProperty((JetParameter) namedDeclaration);
229                if (constructorClass != null) {
230                    firstPart = getFQName(constructorClass);
231                }
232            }
233            else if (parent instanceof JetObjectDeclaration) {
234                if (parent.getParent() instanceof JetClassObject) {
235                    JetClassOrObject classOrObject = PsiTreeUtil.getParentOfType(parent, JetClassOrObject.class);
236                    if (classOrObject != null) {
237                        firstPart = getFQName(classOrObject);
238                    }
239                }
240                else {
241                    firstPart = getFQName((JetNamedDeclaration) parent);
242                }
243            }
244    
245            if (firstPart == null) {
246                return null;
247            }
248    
249            return firstPart.child(name);
250        }
251    
252        /** @return <code>null</code> iff the tye has syntactic errors */
253        @Nullable
254        public static FqName toQualifiedName(@NotNull JetUserType userType) {
255            List<String> reversedNames = Lists.newArrayList();
256    
257            JetUserType current = userType;
258            while (current != null) {
259                String name = current.getReferencedName();
260                if (name == null) return null;
261    
262                reversedNames.add(name);
263                current = current.getQualifier();
264            }
265    
266            return FqName.fromSegments(ContainerUtil.reverse(reversedNames));
267        }
268    
269        @Nullable
270        public static Name getShortName(@NotNull JetAnnotationEntry annotation) {
271            JetTypeReference typeReference = annotation.getTypeReference();
272            assert typeReference != null : "Annotation entry hasn't typeReference " + annotation.getText();
273            JetTypeElement typeElement = typeReference.getTypeElement();
274            if (typeElement instanceof JetUserType) {
275                JetUserType userType = (JetUserType) typeElement;
276                String shortName = userType.getReferencedName();
277                if (shortName != null) {
278                    return Name.identifier(shortName);
279                }
280            }
281            return null;
282        }
283    
284        public static boolean isDeprecated(@NotNull JetModifierListOwner owner) {
285            JetModifierList modifierList = owner.getModifierList();
286            if (modifierList != null) {
287                List<JetAnnotationEntry> annotationEntries = modifierList.getAnnotationEntries();
288                for (JetAnnotationEntry annotation : annotationEntries) {
289                    Name shortName = getShortName(annotation);
290                    if (KotlinBuiltIns.getInstance().getDeprecatedAnnotation().getName().equals(shortName)) {
291                        return true;
292                    }
293                }
294            }
295            return false;
296        }
297    
298        @Nullable
299        @IfNotParsed
300        public static ImportPath getImportPath(@NotNull JetImportDirective importDirective) {
301            if (PsiTreeUtil.hasErrorElements(importDirective)) {
302                return null;
303            }
304    
305            FqName importFqn = getFQName(importDirective.getImportedReference());
306            if (importFqn == null) {
307                return null;
308            }
309    
310            Name alias = null;
311            String aliasName = importDirective.getAliasName();
312            if (aliasName != null) {
313                alias = Name.identifier(aliasName);
314            }
315    
316            return new ImportPath(importFqn, importDirective.isAllUnder(), alias);
317        }
318    
319        @Nullable
320        public static <T extends PsiElement> T getDirectParentOfTypeForBlock(@NotNull JetBlockExpression block, @NotNull Class<T> aClass) {
321            T parent = PsiTreeUtil.getParentOfType(block, aClass);
322            if (parent instanceof JetIfExpression) {
323                JetIfExpression ifExpression = (JetIfExpression) parent;
324                if (ifExpression.getElse() == block || ifExpression.getThen() == block) {
325                    return parent;
326                }
327            }
328            if (parent instanceof JetWhenExpression) {
329                JetWhenExpression whenExpression = (JetWhenExpression) parent;
330                for (JetWhenEntry whenEntry : whenExpression.getEntries()) {
331                    if (whenEntry.getExpression() == block) {
332                        return parent;
333                    }
334                }
335            }
336            if (parent instanceof JetFunctionLiteral) {
337                JetFunctionLiteral functionLiteral = (JetFunctionLiteral) parent;
338                if (functionLiteral.getBodyExpression() == block) {
339                    return parent;
340                }
341            }
342            if (parent instanceof JetTryExpression) {
343                JetTryExpression tryExpression = (JetTryExpression) parent;
344                if (tryExpression.getTryBlock() == block) {
345                    return parent;
346                }
347                for (JetCatchClause clause : tryExpression.getCatchClauses()) {
348                    if (clause.getCatchBody() == block) {
349                        return parent;
350                    }
351                }
352            }
353            return null;
354        }
355    
356        public static boolean isImplicitlyUsed(@NotNull JetElement element) {
357            PsiElement parent = element.getParent();
358            if (!(parent instanceof JetBlockExpression)) return true;
359            JetBlockExpression block = (JetBlockExpression) parent;
360            List<JetElement> statements = block.getStatements();
361            if (statements.get(statements.size() - 1) == element) {
362                JetExpression expression = getDirectParentOfTypeForBlock(block, JetIfExpression.class);
363                if (expression == null) {
364                    expression = getDirectParentOfTypeForBlock(block, JetWhenExpression.class);
365                }
366                if (expression == null) {
367                    expression = getDirectParentOfTypeForBlock(block, JetFunctionLiteral.class);
368                }
369                if (expression == null) {
370                    expression = getDirectParentOfTypeForBlock(block, JetTryExpression.class);
371                }
372                if (expression != null) {
373                    return isImplicitlyUsed(expression);
374                }
375            }
376            return false;
377        }
378    
379        public static void deleteClass(@NotNull JetClassOrObject clazz) {
380            CheckUtil.checkWritable(clazz);
381            JetFile file = (JetFile) clazz.getContainingFile();
382            List<JetDeclaration> declarations = file.getDeclarations();
383            if (declarations.size() == 1) {
384                file.delete();
385            }
386            else {
387                PsiElement parent = clazz.getParent();
388                CodeEditUtil.removeChild(parent.getNode(), clazz.getNode());
389            }
390        }
391    
392        @Nullable
393        public static Name getAliasName(@NotNull JetImportDirective importDirective) {
394            String aliasName = importDirective.getAliasName();
395            JetExpression importedReference = importDirective.getImportedReference();
396            if (importedReference == null) {
397                return null;
398            }
399            JetSimpleNameExpression referenceExpression = getLastReference(importedReference);
400            if (aliasName == null) {
401                aliasName = referenceExpression != null ? referenceExpression.getReferencedName() : null;
402            }
403    
404            return aliasName != null && !aliasName.isEmpty() ? Name.identifier(aliasName) : null;
405        }
406    
407        @Nullable
408        public static JetSimpleNameExpression getLastReference(@NotNull JetExpression importedReference) {
409            if (importedReference instanceof JetDotQualifiedExpression) {
410                JetExpression selectorExpression = ((JetDotQualifiedExpression) importedReference).getSelectorExpression();
411                return (selectorExpression instanceof JetSimpleNameExpression) ? (JetSimpleNameExpression) selectorExpression : null;
412            }
413            if (importedReference instanceof JetSimpleNameExpression) {
414                return (JetSimpleNameExpression) importedReference;
415            }
416            return null;
417        }
418    
419        public static boolean isSelectorInQualified(@NotNull JetSimpleNameExpression nameExpression) {
420            PsiElement nameExpressionParent = nameExpression.getParent();
421    
422            if (nameExpressionParent instanceof JetUserType) {
423                assert ((JetUserType) nameExpressionParent).getReferenceExpression() == nameExpression;
424                return ((JetUserType) nameExpressionParent).getQualifier() != null;
425            }
426    
427            JetExpression selector = nameExpression;
428            if (nameExpressionParent instanceof JetCallExpression && ((JetCallExpression) nameExpressionParent).getCalleeExpression() == nameExpression) {
429                selector = (JetCallExpression) nameExpressionParent;
430            }
431    
432            PsiElement selectorParent = selector.getParent();
433            return selectorParent instanceof JetQualifiedExpression && (((JetQualifiedExpression) selectorParent).getSelectorExpression() == selector);
434        }
435    
436        public static boolean isVoidType(@Nullable JetTypeReference typeReference) {
437            if (typeReference == null) {
438                return false;
439            }
440    
441            return KotlinBuiltIns.getInstance().getUnit().getName().asString().equals(typeReference.getText());
442        }
443    
444        public static boolean isSafeCall(@NotNull Call call) {
445            ASTNode callOperationNode = call.getCallOperationNode();
446            return callOperationNode != null && callOperationNode.getElementType() == JetTokens.SAFE_ACCESS;
447        }
448    
449        public static boolean isFunctionLiteralWithoutDeclaredParameterTypes(@Nullable JetExpression expression) {
450            if (!(expression instanceof JetFunctionLiteralExpression)) return false;
451            JetFunctionLiteralExpression functionLiteral = (JetFunctionLiteralExpression) expression;
452            for (JetParameter parameter : functionLiteral.getValueParameters()) {
453                if (parameter.getTypeReference() != null) {
454                    return false;
455                }
456            }
457            return true;
458        }
459    
460        public static boolean isScriptDeclaration(@NotNull JetDeclaration namedDeclaration) {
461            return getScript(namedDeclaration) != null;
462        }
463    
464        @Nullable
465        public static JetScript getScript(@NotNull JetDeclaration namedDeclaration) {
466            PsiElement parent = namedDeclaration.getParent();
467            if (parent != null && parent.getParent() instanceof JetScript) {
468                return (JetScript) parent.getParent();
469            }
470            else {
471                return null;
472            }
473        }
474    
475        public static boolean isVariableNotParameterDeclaration(@NotNull JetDeclaration declaration) {
476            if (!(declaration instanceof JetVariableDeclaration)) return false;
477            if (declaration instanceof JetProperty) return true;
478            assert declaration instanceof JetMultiDeclarationEntry;
479            JetMultiDeclarationEntry multiDeclarationEntry = (JetMultiDeclarationEntry) declaration;
480            return !(multiDeclarationEntry.getParent().getParent() instanceof JetForExpression);
481        }
482    
483        @Nullable
484        public static Name getConventionName(@NotNull JetSimpleNameExpression simpleNameExpression) {
485            if (simpleNameExpression.getIdentifier() != null) {
486                return simpleNameExpression.getReferencedNameAsName();
487            }
488    
489            PsiElement firstChild = simpleNameExpression.getFirstChild();
490            if (firstChild != null) {
491                IElementType elementType = firstChild.getNode().getElementType();
492                if (elementType instanceof JetToken) {
493                    JetToken jetToken = (JetToken) elementType;
494                    return OperatorConventions.getNameForOperationSymbol(jetToken);
495                }
496            }
497    
498            return null;
499        }
500    
501        @Nullable
502        public static PsiElement getTopmostParentOfTypes(@Nullable PsiElement element, @NotNull Class<? extends PsiElement>... parentTypes) {
503            if (element == null) {
504                return null;
505            }
506    
507            PsiElement result = null;
508            PsiElement parent = element.getParent();
509            while (parent != null) {
510                if (PsiTreeUtil.instanceOf(parent, parentTypes)) {
511                    result = parent;
512                }
513    
514                parent = parent.getParent();
515            }
516    
517            return result;
518        }
519    
520        public static boolean isNullConstant(@NotNull JetExpression expression) {
521            JetExpression deparenthesized = deparenthesize(expression);
522            return deparenthesized instanceof JetConstantExpression && deparenthesized.getNode().getElementType() == JetNodeTypes.NULL;
523        }
524    
525        public static boolean isAbstract(@NotNull JetDeclarationWithBody declaration) {
526            return declaration.getBodyExpression() == null;
527        }
528    
529        public static boolean isBackingFieldReference(@NotNull JetSimpleNameExpression expression) {
530            return expression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER;
531        }
532    
533        public static boolean isBackingFieldReference(@Nullable JetElement element) {
534            return element instanceof JetSimpleNameExpression && isBackingFieldReference((JetSimpleNameExpression)element);
535        }
536    
537        @Nullable
538        private static FqName getFQName(@Nullable JetExpression expression) {
539            if (expression == null) {
540                return null;
541            }
542    
543            if (expression instanceof JetDotQualifiedExpression) {
544                JetDotQualifiedExpression dotQualifiedExpression = (JetDotQualifiedExpression) expression;
545                FqName parentFqn = getFQName(dotQualifiedExpression.getReceiverExpression());
546                Name child = getName(dotQualifiedExpression.getSelectorExpression());
547    
548                return parentFqn != null && child != null ? parentFqn.child(child) : null;
549            }
550            else if (expression instanceof JetSimpleNameExpression) {
551                JetSimpleNameExpression simpleNameExpression = (JetSimpleNameExpression) expression;
552                return FqName.topLevel(simpleNameExpression.getReferencedNameAsName());
553            }
554            else {
555                throw new IllegalArgumentException("Can't construct fqn for: " + expression.getClass().toString());
556            }
557        }
558    
559        @Nullable
560        private static Name getName(@Nullable JetExpression expression) {
561            if (expression == null) {
562                return null;
563            }
564    
565            if (expression instanceof JetSimpleNameExpression) {
566                return ((JetSimpleNameExpression) expression).getReferencedNameAsName();
567            }
568            else {
569                throw new IllegalArgumentException("Can't construct name for: " + expression.getClass().toString());
570            }
571        }
572    
573        @Nullable
574        public static JetElement getLastStatementInABlock(@Nullable JetBlockExpression blockExpression) {
575            if (blockExpression == null) return null;
576            List<JetElement> statements = blockExpression.getStatements();
577            return statements.isEmpty() ? null : statements.get(statements.size() - 1);
578        }
579    
580        public static boolean isLocalClass(@NotNull JetClassOrObject classOrObject) {
581            return getOutermostClassOrObject(classOrObject) == null;
582        }
583    
584        public static boolean isTrait(@NotNull JetClassOrObject classOrObject) {
585            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isTrait();
586        }
587    
588        @Nullable
589        public static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
590            JetClassOrObject current = classOrObject;
591            while (true) {
592                PsiElement parent = current.getParent();
593                assert classOrObject.getParent() != null : "Class with no parent: " + classOrObject.getText();
594    
595                if (parent instanceof PsiFile) {
596                    return current;
597                }
598                if (parent instanceof JetClassObject) {
599                    // current class IS the class object declaration
600                    parent = parent.getParent();
601                    assert parent instanceof JetClassBody : "Parent of class object is not a class body: " + parent;
602                }
603                if (!(parent instanceof JetClassBody)) {
604                    // It is a local class, no legitimate outer
605                    return null;
606                }
607    
608                current = (JetClassOrObject) parent.getParent();
609            }
610        }
611    
612        @Nullable
613        public static JetClass getClassIfParameterIsProperty(@NotNull JetParameter jetParameter) {
614            if (jetParameter.getValOrVarNode() != null) {
615                PsiElement parent = jetParameter.getParent();
616                if (parent instanceof JetParameterList && parent.getParent() instanceof JetClass) {
617                    return (JetClass) parent.getParent();
618                }
619            }
620    
621            return null;
622        }
623    
624        @Nullable
625        private static IElementType getOperation(@NotNull JetExpression expression) {
626            if (expression instanceof JetQualifiedExpression) {
627                return ((JetQualifiedExpression) expression).getOperationSign();
628            }
629            else if (expression instanceof JetOperationExpression) {
630                return ((JetOperationExpression) expression).getOperationReference().getReferencedNameElementType();
631            }
632            return null;
633        }
634    
635        private static int getPriority(@NotNull JetExpression expression) {
636            int maxPriority = JetExpressionParsing.Precedence.values().length + 1;
637    
638            // same as postfix operations
639            if (expression instanceof JetPostfixExpression ||
640                expression instanceof JetQualifiedExpression ||
641                expression instanceof JetCallExpression ||
642                expression instanceof JetArrayAccessExpression) {
643                return maxPriority - 1;
644            }
645    
646            if (expression instanceof JetPrefixExpression) return maxPriority - 2;
647    
648            if (expression instanceof JetDeclaration || expression instanceof JetStatementExpression || expression instanceof JetIfExpression) {
649                return 0;
650            }
651    
652            IElementType operation = getOperation(expression);
653            for (JetExpressionParsing.Precedence precedence : JetExpressionParsing.Precedence.values()) {
654                if (precedence != JetExpressionParsing.Precedence.PREFIX && precedence != JetExpressionParsing.Precedence.POSTFIX &&
655                    precedence.getOperations().contains(operation)) {
656                    return maxPriority - precedence.ordinal() - 1;
657                }
658            }
659    
660            return maxPriority;
661        }
662    
663        public static boolean areParenthesesUseless(@NotNull JetParenthesizedExpression expression) {
664            JetExpression innerExpression = expression.getExpression();
665            if (innerExpression == null) return true;
666    
667            PsiElement parent = expression.getParent();
668            if (!(parent instanceof JetExpression)) return true;
669    
670            return !areParenthesesNecessary(innerExpression, expression, (JetExpression) parent);
671        }
672    
673        public static boolean areParenthesesNecessary(@NotNull JetExpression innerExpression, @NotNull JetExpression currentInner, @NotNull JetExpression parentExpression) {
674            if (parentExpression instanceof JetParenthesizedExpression || innerExpression instanceof JetParenthesizedExpression) {
675                return false;
676            }
677    
678            if (parentExpression instanceof JetWhenExpression || innerExpression instanceof JetWhenExpression) {
679                return false;
680            }
681    
682            if (innerExpression instanceof JetIfExpression) {
683                PsiElement current = parentExpression;
684    
685                while (!(current instanceof JetBlockExpression || current instanceof JetDeclaration || current instanceof JetStatementExpression)) {
686                    if (current.getTextRange().getEndOffset() != currentInner.getTextRange().getEndOffset()) {
687                        return current.getText().charAt(current.getTextLength() - 1) != ')'; // if current expression is "guarded" by parenthesis, no extra parenthesis is necessary
688                    }
689    
690                    current = current.getParent();
691                }
692            }
693    
694            IElementType innerOperation = getOperation(innerExpression);
695            IElementType parentOperation = getOperation(parentExpression);
696    
697            // 'return (@label{...})' case
698            if (parentExpression instanceof JetReturnExpression && innerOperation == JetTokens.LABEL_IDENTIFIER) {
699                return true;
700            }
701    
702            // '(x: Int) < y' case
703            if (innerExpression instanceof JetBinaryExpressionWithTypeRHS && parentOperation == JetTokens.LT) {
704                return true;
705            }
706    
707            int innerPriority = getPriority(innerExpression);
708            int parentPriority = getPriority(parentExpression);
709    
710            if (innerPriority == parentPriority) {
711                if (parentExpression instanceof JetBinaryExpression) {
712                    if (innerOperation == JetTokens.ANDAND || innerOperation == JetTokens.OROR) {
713                        return false;
714                    }
715                    return ((JetBinaryExpression) parentExpression).getRight() == currentInner;
716                }
717    
718                //'-(-x)' case
719                if (parentExpression instanceof JetPrefixExpression && innerExpression instanceof JetPrefixExpression) {
720                    return innerOperation == parentOperation && (innerOperation == JetTokens.PLUS || innerOperation == JetTokens.MINUS);
721                }
722                return false;
723            }
724    
725            return innerPriority < parentPriority;
726        }
727    
728        public static boolean isAssignment(@NotNull PsiElement element) {
729            return element instanceof JetBinaryExpression &&
730                   JetTokens.ALL_ASSIGNMENTS.contains(((JetBinaryExpression) element).getOperationToken());
731        }
732    
733        public static boolean isOrdinaryAssignment(@NotNull PsiElement element) {
734            return element instanceof JetBinaryExpression &&
735                   ((JetBinaryExpression) element).getOperationToken().equals(JetTokens.EQ);
736        }
737    
738        @Nullable
739        public static JetElement getOutermostLastBlockElement(@Nullable JetElement element, @NotNull Predicate<JetElement> checkElement) {
740            if (element == null) return null;
741    
742            if (!(element instanceof JetBlockExpression)) return checkElement.apply(element) ? element : null;
743    
744            JetBlockExpression block = (JetBlockExpression)element;
745            int n = block.getStatements().size();
746    
747            if (n == 0) return null;
748    
749            JetElement lastElement = block.getStatements().get(n - 1);
750            return checkElement.apply(lastElement) ? lastElement : null;
751        }
752    
753        public static boolean checkVariableDeclarationInBlock(@NotNull JetBlockExpression block, @NotNull String varName) {
754            for (JetElement element : block.getStatements()) {
755                if (element instanceof JetVariableDeclaration) {
756                    if (((JetVariableDeclaration) element).getNameAsSafeName().asString().equals(varName)) {
757                        return true;
758                    }
759                }
760            }
761    
762            return false;
763        }
764    
765        public static boolean checkWhenExpressionHasSingleElse(@NotNull JetWhenExpression whenExpression) {
766            int elseCount = 0;
767            for (JetWhenEntry entry : whenExpression.getEntries()) {
768                if (entry.isElse()) {
769                    elseCount++;
770                }
771            }
772            return (elseCount == 1);
773        }
774    
775        @Nullable
776        public static PsiElement skipTrailingWhitespacesAndComments(@Nullable PsiElement element)  {
777            return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class, PsiComment.class);
778        }
779    
780        public static final Predicate<JetElement> ANY_JET_ELEMENT = new Predicate<JetElement>() {
781            @Override
782            public boolean apply(@Nullable JetElement input) {
783                return true;
784            }
785        };
786    
787        @NotNull
788        public static String getText(@Nullable PsiElement element) {
789            return element != null ? element.getText() : "";
790        }
791    
792        @Nullable
793        public static String getNullableText(@Nullable PsiElement element) {
794            return element != null ? element.getText() : null;
795        }
796    
797        /**
798         * CommentUtilCore.isComment fails if element <strong>inside</strong> comment.
799         *
800         * Also, we can not add KDocTokens to COMMENTS TokenSet, because it is used in JetParserDefinition.getCommentTokens(),
801         * and therefor all COMMENTS tokens will be ignored by PsiBuilder.
802         *
803         * @param element
804         * @return
805         */
806        public static boolean isInComment(PsiElement element) {
807            return CommentUtilCore.isComment(element) || element instanceof KDocElement;
808        }
809    
810        @Nullable
811        public static PsiElement getOutermostParent(@NotNull PsiElement element, @NotNull PsiElement upperBound, boolean strict) {
812            PsiElement parent = strict ? element.getParent() : element;
813            while (parent != null && parent.getParent() != upperBound) {
814                parent = parent.getParent();
815            }
816    
817            return parent;
818        }
819    
820        public static <T extends PsiElement> T getLastChildByType(@NotNull PsiElement root, @NotNull Class<? extends T>... elementTypes) {
821            PsiElement[] children = root.getChildren();
822    
823            for (int i = children.length - 1; i >= 0; i--) {
824                if (PsiTreeUtil.instanceOf(children[i], elementTypes)) {
825                    //noinspection unchecked
826                    return (T) children[i];
827                }
828            }
829    
830            return null;
831        }
832    
833        @Nullable
834        public static JetElement getOutermostDescendantElement(
835                @Nullable PsiElement root,
836                boolean first,
837                final @NotNull Predicate<JetElement> predicate
838        ) {
839            if (!(root instanceof JetElement)) return null;
840    
841            final List<JetElement> results = Lists.newArrayList();
842    
843            ((JetElement) root).accept(
844                    new JetVisitorVoid() {
845                        @Override
846                        public void visitJetElement(JetElement element) {
847                            if (predicate.apply(element)) {
848                                //noinspection unchecked
849                                results.add(element);
850                            }
851                            else {
852                                element.acceptChildren(this);
853                            }
854                        }
855                    }
856            );
857    
858            if (results.isEmpty()) return null;
859    
860            return first ? results.get(0) : results.get(results.size() - 1);
861        }
862    
863        @Nullable
864        public static PsiElement findChildByType(@NotNull PsiElement element, @NotNull IElementType type) {
865            ASTNode node = element.getNode().findChildByType(type);
866            return node == null ? null : node.getPsi();
867        }
868    
869        @Nullable
870        public static JetExpression getCalleeExpressionIfAny(@NotNull JetExpression expression) {
871            if (expression instanceof JetCallElement) {
872                JetCallElement callExpression = (JetCallElement) expression;
873                return callExpression.getCalleeExpression();
874            }
875            if (expression instanceof JetQualifiedExpression) {
876                JetExpression selectorExpression = ((JetQualifiedExpression) expression).getSelectorExpression();
877                if (selectorExpression != null) {
878                    return getCalleeExpressionIfAny(selectorExpression);
879                }
880            }
881            if (expression instanceof JetUnaryExpression) {
882                return ((JetUnaryExpression) expression).getOperationReference();
883            }
884            if (expression instanceof JetBinaryExpression) {
885                return ((JetBinaryExpression) expression).getOperationReference();
886            }
887            return null;
888        }
889    
890        @Nullable
891        public static PsiElement skipSiblingsBackwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
892            if (element == null) return null;
893            for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) {
894                if (elementsToSkip.apply(e)) continue;
895                return e;
896            }
897            return null;
898        }
899    
900        @Nullable
901        public static PsiElement skipSiblingsForwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
902            if (element == null) return null;
903            for (PsiElement e = element.getNextSibling(); e != null; e = e.getNextSibling()) {
904                if (elementsToSkip.apply(e)) continue;
905                return e;
906            }
907            return null;
908        }
909    
910        public static NavigatablePsiElement getPackageReference(@NotNull JetFile file, int partIndex) {
911            JetNamespaceHeader header = file.getNamespaceHeader();
912            if (header == null) {
913                throw new IllegalArgumentException("Should be called only for files with namespace: " + file);
914            }
915    
916            List<JetSimpleNameExpression> names = header.getParentNamespaceNames();
917            if (!(0 <= partIndex && partIndex < names.size() + 1)) {
918                throw new IndexOutOfBoundsException(String.format("%s index for file with header %s is out of range", partIndex, header.getText()));
919            }
920    
921            return (names.size() > partIndex) ? names.get(partIndex) : header.getLastPartExpression();
922        }
923    
924        // Delete given element and all the elements separating it from the neighboring elements of the same class
925        public static void deleteElementWithDelimiters(@NotNull PsiElement element) {
926            PsiElement paramBefore = PsiTreeUtil.getPrevSiblingOfType(element, element.getClass());
927    
928            PsiElement from;
929            PsiElement to;
930            if (paramBefore != null) {
931                from = paramBefore.getNextSibling();
932                to = element;
933            }
934            else {
935                PsiElement paramAfter = PsiTreeUtil.getNextSiblingOfType(element, element.getClass());
936    
937                from = element;
938                to = paramAfter != null ? paramAfter.getPrevSibling() : element;
939            }
940    
941            PsiElement parent = element.getParent();
942    
943            parent.deleteChildRange(from, to);
944        }
945    
946        // Delete element if it doesn't contain children of a given type
947        public static <T extends PsiElement> void deleteChildlessElement(PsiElement element, Class<T> childClass) {
948            if (PsiTreeUtil.getChildrenOfType(element, childClass) == null) {
949                element.delete();
950            }
951        }
952    
953        public static PsiElement ascendIfPropertyAccessor(PsiElement element) {
954            if (element instanceof JetPropertyAccessor) {
955                return element.getParent();
956            }
957            return element;
958        }
959    
960        @NotNull
961        public static String getElementTextWithContext(@NotNull JetElement element) {
962            if (element instanceof JetFile) {
963                return element.getContainingFile().getText();
964            }
965    
966            // Find parent for element among file children
967            PsiElement inFileParent = PsiTreeUtil.findFirstParent(element, new Condition<PsiElement>() {
968                @Override
969                public boolean value(PsiElement parentCandidate) {
970                    return parentCandidate != null && parentCandidate.getParent() instanceof JetFile;
971                }
972            });
973    
974            assert inFileParent != null : "For non-file element we should always be able to find parent in file children";
975    
976            int startContextOffset = inFileParent.getTextRange().getStartOffset();
977            int elementContextOffset = element.getTextRange().getStartOffset();
978    
979            int inFileParentOffset = elementContextOffset - startContextOffset;
980    
981            return new StringBuilder(inFileParent.getText()).insert(inFileParentOffset, "<caret>").toString();
982        }
983    
984        @Nullable
985        public static JetModifierList replaceModifierList(@NotNull JetModifierListOwner owner, @Nullable JetModifierList modifierList) {
986            JetModifierList oldModifierList = owner.getModifierList();
987            if (modifierList == null) {
988                if (oldModifierList != null) oldModifierList.delete();
989                return null;
990            }
991            else {
992                if (oldModifierList == null) {
993                    PsiElement firstChild = owner.getFirstChild();
994                    return (JetModifierList) owner.addBefore(modifierList, firstChild);
995                }
996                else {
997                    return (JetModifierList) oldModifierList.replace(modifierList);
998                }
999            }
1000        }
1001    
1002        @Nullable
1003        public static String getPackageName(@NotNull JetElement element) {
1004            JetFile file = (JetFile) element.getContainingFile();
1005            JetNamespaceHeader header = PsiTreeUtil.findChildOfType(file, JetNamespaceHeader.class);
1006    
1007            return header != null ? header.getQualifiedName() : null;
1008        }
1009    
1010        @Nullable
1011        public static JetElement getEnclosingBlockForLocalDeclaration(@Nullable JetNamedDeclaration declaration) {
1012            if (declaration instanceof JetTypeParameter || declaration instanceof JetParameter) {
1013                declaration = PsiTreeUtil.getParentOfType(declaration, JetNamedDeclaration.class);
1014            }
1015    
1016            //noinspection unchecked
1017            JetElement container =
1018                    PsiTreeUtil.getParentOfType(declaration, JetBlockExpression.class, JetClassInitializer.class);
1019            if (container == null) return null;
1020    
1021            return (container instanceof JetClassInitializer) ? ((JetClassInitializer) container).getBody() : container;
1022        }
1023    
1024        public static boolean isLocal(@NotNull JetNamedDeclaration declaration) {
1025            return getEnclosingBlockForLocalDeclaration(declaration) != null;
1026        }
1027    }