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