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