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