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