fold as closures anonymous classes with a single implemented method, as long they...
[idea/community.git] / java / java-psi-impl / src / com / intellij / codeInsight / folding / impl / JavaFoldingBuilderBase.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.codeInsight.folding.impl;
17
18 import com.intellij.codeInsight.daemon.impl.CollectHighlightsUtil;
19 import com.intellij.codeInsight.folding.JavaCodeFoldingSettings;
20 import com.intellij.lang.ASTNode;
21 import com.intellij.lang.folding.CustomFoldingBuilder;
22 import com.intellij.lang.folding.FoldingDescriptor;
23 import com.intellij.lang.folding.NamedFoldingDescriptor;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.Document;
26 import com.intellij.openapi.editor.FoldingGroup;
27 import com.intellij.openapi.progress.ProgressIndicatorProvider;
28 import com.intellij.openapi.progress.ProgressManager;
29 import com.intellij.openapi.project.DumbAware;
30 import com.intellij.openapi.project.DumbService;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.util.TextRange;
33 import com.intellij.openapi.util.UnfairTextRange;
34 import com.intellij.openapi.util.text.StringUtil;
35 import com.intellij.psi.*;
36 import com.intellij.psi.impl.source.PsiClassReferenceType;
37 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
38 import com.intellij.psi.impl.source.tree.JavaDocElementType;
39 import com.intellij.psi.impl.source.tree.JavaElementType;
40 import com.intellij.psi.javadoc.PsiDocComment;
41 import com.intellij.psi.tree.IElementType;
42 import com.intellij.psi.util.PropertyUtil;
43 import com.intellij.psi.util.PsiTreeUtil;
44 import com.intellij.psi.util.PsiUtil;
45 import com.intellij.psi.util.PsiUtilCore;
46 import com.intellij.util.text.CharArrayUtil;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import java.util.Arrays;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Set;
54
55 public abstract class JavaFoldingBuilderBase extends CustomFoldingBuilder implements DumbAware {
56   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.folding.impl.JavaFoldingBuilder");
57   private static final String SMILEY = "<~>";
58
59   private static String getPlaceholderText(@NotNull PsiElement element) {
60     if (element instanceof PsiImportList) {
61       return "...";
62     }
63     if (element instanceof PsiMethod || element instanceof PsiClassInitializer || element instanceof PsiClass) {
64       return "{...}";
65     }
66     if (element instanceof PsiDocComment) {
67       return "/**...*/";
68     }
69     if (element instanceof PsiFile) {
70       return "/.../";
71     }
72     if (element instanceof PsiAnnotation) {
73       return "@{...}";
74     }
75     if (element instanceof PsiReferenceParameterList) {
76       return SMILEY;
77     }
78     if (element instanceof PsiComment) {
79       return "//...";
80     }
81     return "...";
82   }
83
84   private static boolean areOnAdjacentLines(@NotNull PsiElement e1, @NotNull PsiElement e2, @NotNull Document document) {
85     return document.getLineNumber(e1.getTextRange().getEndOffset()) + 1 == document.getLineNumber(e2.getTextRange().getStartOffset());
86   }
87
88   private static boolean isSimplePropertyAccessor(@NotNull PsiMethod method) {
89     if (DumbService.isDumb(method.getProject())) return false;
90
91     PsiCodeBlock body = method.getBody();
92     if (body == null || body.getLBrace() == null || body.getRBrace() == null) return false;
93     PsiStatement[] statements = body.getStatements();
94     if (statements.length == 0) return false;
95
96     PsiStatement statement = statements[0];
97     if (PropertyUtil.isSimplePropertyGetter(method)) {
98       if (statement instanceof PsiReturnStatement) {
99         return ((PsiReturnStatement)statement).getReturnValue() instanceof PsiReferenceExpression;
100       }
101       return false;
102     }
103
104     // builder-style setter?
105     if (statements.length > 1 && !(statements[1] instanceof PsiReturnStatement)) return false;
106     
107     // any setter? 
108     if (statement instanceof PsiExpressionStatement) {
109       PsiExpression expr = ((PsiExpressionStatement)statement).getExpression();
110       if (expr instanceof PsiAssignmentExpression) {
111         PsiExpression lhs = ((PsiAssignmentExpression)expr).getLExpression();
112         PsiExpression rhs = ((PsiAssignmentExpression)expr).getRExpression();
113         return lhs instanceof PsiReferenceExpression && 
114                rhs instanceof PsiReferenceExpression && 
115                !((PsiReferenceExpression)rhs).isQualified() && 
116                PropertyUtil.isSimplePropertySetter(method); // last check because it can perform long return type resolve
117       }
118     }
119     return false;
120   }
121
122   @Nullable
123   private static TextRange getRangeToFold(@NotNull PsiElement element) {
124     if (element instanceof SyntheticElement) {
125       return null;
126     }
127     if (element instanceof PsiMethod) {
128       PsiCodeBlock body = ((PsiMethod)element).getBody();
129       if (body == null) return null;
130       return body.getTextRange();
131     }
132     if (element instanceof PsiClassInitializer) {
133       return ((PsiClassInitializer)element).getBody().getTextRange();
134     }
135     if (element instanceof PsiClass) {
136       PsiClass aClass = (PsiClass)element;
137       PsiElement lBrace = aClass.getLBrace();
138       if (lBrace == null) return null;
139       PsiElement rBrace = aClass.getRBrace();
140       if (rBrace == null) return null;
141       return new TextRange(lBrace.getTextOffset(), rBrace.getTextOffset() + 1);
142     }
143     if (element instanceof PsiJavaFile) {
144       return getFileHeader((PsiJavaFile)element);
145     }
146     if (element instanceof PsiImportList) {
147       PsiImportList list = (PsiImportList)element;
148       PsiImportStatementBase[] statements = list.getAllImportStatements();
149       if (statements.length == 0) return null;
150       final PsiElement importKeyword = statements[0].getFirstChild();
151       if (importKeyword == null) return null;
152       int startOffset = importKeyword.getTextRange().getEndOffset() + 1;
153       int endOffset = statements[statements.length - 1].getTextRange().getEndOffset();
154       if (!hasErrorElementsNearby(element.getContainingFile(), startOffset, endOffset)) {
155         return new TextRange(startOffset, endOffset);
156       }
157     }
158     if (element instanceof PsiDocComment) {
159       return element.getTextRange();
160     }
161     if (element instanceof PsiAnnotation) {
162       int startOffset = element.getTextRange().getStartOffset();
163       PsiElement last = element;
164       while (element instanceof PsiAnnotation) {
165         last = element;
166         element = PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class, PsiComment.class);
167       }
168
169       return new TextRange(startOffset, last.getTextRange().getEndOffset());
170     }
171     return null;
172   }
173
174   public static boolean hasErrorElementsNearby(@NotNull PsiFile file, int startOffset, int endOffset) {
175     endOffset = CharArrayUtil.shiftForward(file.getViewProvider().getContents(), endOffset, " \t\n");
176     for (PsiElement element : CollectHighlightsUtil.getElementsInRange(file, startOffset, endOffset)) {
177       if (element instanceof PsiErrorElement) {
178         return true;
179       }
180     }
181     return false;
182   }
183
184   @Nullable
185   private static TextRange getFileHeader(@NotNull PsiJavaFile file) {
186     PsiElement first = file.getFirstChild();
187     if (first instanceof PsiWhiteSpace) first = first.getNextSibling();
188     PsiElement element = first;
189     while (element instanceof PsiComment) {
190       element = element.getNextSibling();
191       if (element instanceof PsiWhiteSpace) {
192         element = element.getNextSibling();
193       }
194       else {
195         break;
196       }
197     }
198     if (element == null) return null;
199     PsiElement prevSibling = element.getPrevSibling();
200     if (prevSibling instanceof PsiWhiteSpace) element = prevSibling;
201     if (element.equals(first)) return null;
202     return new UnfairTextRange(first.getTextOffset(), element.getTextOffset());
203   }
204
205   private static void addAnnotationsToFold(@Nullable PsiModifierList modifierList,
206                                            @NotNull List<FoldingDescriptor> foldElements,
207                                            @NotNull Document document) {
208     if (modifierList == null) return;
209     PsiElement[] children = modifierList.getChildren();
210     for (int i = 0; i < children.length; i++) {
211       PsiElement child = children[i];
212       if (child instanceof PsiAnnotation) {
213         addToFold(foldElements, child, document, false);
214         int j;
215         for (j = i + 1; j < children.length; j++) {
216           PsiElement nextChild = children[j];
217           if (nextChild instanceof PsiModifier) break;
218         }
219
220         //noinspection AssignmentToForLoopParameter
221         i = j;
222       }
223     }
224   }
225
226   /**
227    * We want to allow to fold subsequent single line comments like
228    * <pre>
229    *     // this is comment line 1
230    *     // this is comment line 2
231    * </pre>
232    *
233    * @param comment             comment to check
234    * @param processedComments   set that contains already processed elements. It is necessary because we process all elements of
235    *                            the PSI tree, hence, this method may be called for both comments from the example above. However,
236    *                            we want to create fold region during the first comment processing, put second comment to it and
237    *                            skip processing when current method is called for the second element
238    * @param foldElements        fold descriptors holder to store newly created descriptor (if any)
239    */
240   private static void addCommentFolds(@NotNull PsiComment comment,
241                                       @NotNull Set<PsiElement> processedComments,
242                                       @NotNull List<FoldingDescriptor> foldElements) {
243     if (processedComments.contains(comment) || comment.getTokenType() != JavaTokenType.END_OF_LINE_COMMENT) {
244       return;
245     }
246
247     PsiElement end = null;
248     boolean containsCustomRegionMarker = isCustomRegionElement(comment);
249     for (PsiElement current = comment.getNextSibling(); current != null; current = current.getNextSibling()) {
250       ASTNode node = current.getNode();
251       if (node == null) {
252         break;
253       }
254       IElementType elementType = node.getElementType();
255       if (elementType == JavaTokenType.END_OF_LINE_COMMENT) {
256         end = current;
257         // We don't want to process, say, the second comment in case of three subsequent comments when it's being examined
258         // during all elements traversal. I.e. we expect to start from the first comment and grab as many subsequent
259         // comments as possible during the single iteration.
260         processedComments.add(current);
261         containsCustomRegionMarker |= isCustomRegionElement(current);
262         continue;
263       }
264       if (elementType == TokenType.WHITE_SPACE) {
265         continue;
266       }
267       break;
268     }
269
270     if (end != null && !containsCustomRegionMarker) {
271       foldElements.add(
272         new FoldingDescriptor(comment, new TextRange(comment.getTextRange().getStartOffset(), end.getTextRange().getEndOffset()))
273       );
274     }
275   }
276
277   private static void addMethodGenericParametersFolding(@NotNull PsiMethodCallExpression expression,
278                                                         @NotNull List<FoldingDescriptor> foldElements,
279                                                         @NotNull Document document,
280                                                         boolean quick) {
281     final PsiReferenceExpression methodExpression = expression.getMethodExpression();
282     final PsiReferenceParameterList list = methodExpression.getParameterList();
283     if (list == null || list.getTextLength() <= 5) {
284       return;
285     }
286
287     PsiMethodCallExpression element = expression;
288     while (true) {
289       if (!quick && !resolvesCorrectly(element.getMethodExpression())) return;
290       final PsiElement parent = element.getParent();
291       if (!(parent instanceof PsiExpressionList) || !(parent.getParent() instanceof PsiMethodCallExpression)) break;
292       element = (PsiMethodCallExpression)parent.getParent();
293     }
294
295     addTypeParametersFolding(foldElements, document, list, 3, quick);
296   }
297
298   private static boolean resolvesCorrectly(@NotNull PsiReferenceExpression expression) {
299     for (final JavaResolveResult result : expression.multiResolve(true)) {
300   if (!result.isValidResult()) {
301     return false;
302   }
303 }
304     return true;
305   }
306
307   private static void addGenericParametersFolding(@NotNull PsiNewExpression expression,
308                                                   @NotNull List<FoldingDescriptor> foldElements,
309                                                   @NotNull Document document,
310                                                   boolean quick) {
311     final PsiElement parent = expression.getParent();
312     if (!(parent instanceof PsiVariable)) {
313       return;
314     }
315
316     final PsiType declType = ((PsiVariable)parent).getType();
317     if (!(declType instanceof PsiClassReferenceType)) {
318       return;
319     }
320
321     final PsiType[] parameters = ((PsiClassType)declType).getParameters();
322     if (parameters.length == 0) {
323       return;
324     }
325
326    PsiJavaCodeReferenceElement classReference = expression.getClassReference();
327     if (classReference == null) {
328       final PsiAnonymousClass anonymousClass = expression.getAnonymousClass();
329       if (anonymousClass != null) {
330         classReference = anonymousClass.getBaseClassReference();
331
332         if (quick || ClosureFolding.seemsLikeLambda(anonymousClass.getSuperClass(), anonymousClass)) {
333           return;
334         }
335       }
336     }
337
338     if (classReference != null) {
339       final PsiReferenceParameterList list = classReference.getParameterList();
340       if (list != null) {
341         if (quick) {
342           final PsiJavaCodeReferenceElement declReference = ((PsiClassReferenceType)declType).getReference();
343           final PsiReferenceParameterList declList = declReference.getParameterList();
344           if (declList == null || !list.getText().equals(declList.getText())) {
345             return;
346           }
347         } else {
348           if (!Arrays.equals(list.getTypeArguments(), parameters)) {
349             return;
350           }
351         }
352
353         addTypeParametersFolding(foldElements, document, list, 5, quick);
354       }
355     }
356   }
357
358   private static void addTypeParametersFolding(@NotNull List<FoldingDescriptor> foldElements,
359                                                @NotNull Document document,
360                                                @NotNull PsiReferenceParameterList list,
361                                                int ifLongerThan,
362                                                boolean quick) {
363     if (!quick) {
364       for (final PsiType type : list.getTypeArguments()) {
365         if (!type.isValid()) {
366           return;
367         }
368         if (type instanceof PsiClassType || type instanceof PsiArrayType) {
369           if (PsiUtil.resolveClassInType(type) == null) {
370             return;
371           }
372         }
373       }
374     }
375
376     final String text = list.getText();
377     if (text.startsWith("<") && text.endsWith(">") && text.length() > ifLongerThan) {
378       final TextRange range = list.getTextRange();
379       addFoldRegion(foldElements, list, document, true, range);
380     }
381   }
382
383   protected abstract boolean shouldShowExplicitLambdaType(@NotNull PsiAnonymousClass anonymousClass, @NotNull PsiNewExpression expression);
384
385   private static boolean addToFold(@NotNull List<FoldingDescriptor> list,
386                                    @NotNull PsiElement elementToFold,
387                                    @NotNull Document document,
388                                    boolean allowOneLiners) {
389     PsiUtilCore.ensureValid(elementToFold);
390     TextRange range = getRangeToFold(elementToFold);
391     return range != null && addFoldRegion(list, elementToFold, document, allowOneLiners, range);
392   }
393
394   private static boolean addFoldRegion(@NotNull List<FoldingDescriptor> list,
395                                        @NotNull PsiElement elementToFold,
396                                        @NotNull Document document,
397                                        boolean allowOneLiners,
398                                        @NotNull TextRange range) {
399     final TextRange fileRange = elementToFold.getContainingFile().getTextRange();
400     if (range.equals(fileRange)) return false;
401
402     LOG.assertTrue(range.getStartOffset() >= 0 && range.getEndOffset() <= fileRange.getEndOffset());
403     // PSI element text ranges may be invalid because of reparse exception (see, for example, IDEA-10617)
404     if (range.getStartOffset() < 0 || range.getEndOffset() > fileRange.getEndOffset()) {
405       return false;
406     }
407     if (!allowOneLiners) {
408       int startLine = document.getLineNumber(range.getStartOffset());
409       int endLine = document.getLineNumber(range.getEndOffset() - 1);
410       if (startLine < endLine && range.getLength() > 1) {
411         list.add(new FoldingDescriptor(elementToFold, range));
412         return true;
413       }
414       return false;
415     }
416     else {
417       if (range.getLength() > getPlaceholderText(elementToFold).length()) {
418         list.add(new FoldingDescriptor(elementToFold, range));
419         return true;
420       }
421       return false;
422     }
423   }
424
425   @Override
426   protected void buildLanguageFoldRegions(@NotNull List<FoldingDescriptor> descriptors,
427                                           @NotNull PsiElement root,
428                                           @NotNull Document document,
429                                           boolean quick) {
430     if (!(root instanceof PsiJavaFile)) {
431       return;
432     }
433     PsiJavaFile file = (PsiJavaFile) root;
434
435     PsiImportList importList = file.getImportList();
436     if (importList != null) {
437       PsiImportStatementBase[] statements = importList.getAllImportStatements();
438       if (statements.length > 1) {
439         final TextRange rangeToFold = getRangeToFold(importList);
440         if (rangeToFold != null && rangeToFold.getLength() > 1) {
441           FoldingDescriptor descriptor = new FoldingDescriptor(importList, rangeToFold);
442           // imports are often added/removed automatically, so we enable autoupdate of folded region for foldings even if it's collapsed
443           descriptor.setCanBeRemovedWhenCollapsed(true);
444           descriptors.add(descriptor);
445         }
446       }
447     }
448
449     PsiClass[] classes = file.getClasses();
450     for (PsiClass aClass : classes) {
451       ProgressManager.checkCanceled();
452       ProgressIndicatorProvider.checkCanceled();
453       addElementsToFold(descriptors, aClass, document, true, quick);
454     }
455
456     TextRange range = getFileHeader(file);
457     if (range != null && range.getLength() > 1 && document.getLineNumber(range.getEndOffset()) > document.getLineNumber(range.getStartOffset())) {
458       PsiElement anchorElementToUse = file;
459       PsiElement candidate = file.getFirstChild();
460
461       // We experienced the following problem situation:
462       //     1. There is a collapsed class-level javadoc;
463       //     2. User starts typing at class definition line (e.g. we had definition like 'public class Test' and user starts
464       //        typing 'abstract' between 'public' and 'class');
465       //     3. Collapsed class-level javadoc automatically expanded. That happened because PSI structure became invalid (because
466       //        class definition line at start looks like 'public class Test');
467       // So, our point is to preserve fold descriptor referencing javadoc PSI element.
468       if (candidate != null && candidate.getTextRange().equals(range)) {
469         ASTNode node = candidate.getNode();
470         if (node != null && node.getElementType() == JavaDocElementType.DOC_COMMENT) {
471           anchorElementToUse = candidate;
472         }
473       }
474       descriptors.add(new FoldingDescriptor(anchorElementToUse, range));
475     }
476   }
477
478   private void addElementsToFold(@NotNull List<FoldingDescriptor> list,
479                                  @NotNull PsiClass aClass,
480                                  @NotNull Document document,
481                                  boolean foldJavaDocs,
482                                  boolean quick) {
483     if (!(aClass.getParent() instanceof PsiJavaFile) || ((PsiJavaFile)aClass.getParent()).getClasses().length > 1) {
484       addToFold(list, aClass, document, true);
485     }
486
487     PsiDocComment docComment;
488     if (foldJavaDocs) {
489       docComment = aClass.getDocComment();
490       if (docComment != null) {
491         addToFold(list, docComment, document, true);
492       }
493     }
494     addAnnotationsToFold(aClass.getModifierList(), list, document);
495
496     PsiElement[] children = aClass.getChildren();
497     Set<PsiElement> processedComments = new HashSet<PsiElement>();
498     for (PsiElement child : children) {
499       ProgressIndicatorProvider.checkCanceled();
500
501       if (child instanceof PsiMethod) {
502         PsiMethod method = (PsiMethod)child;
503         boolean oneLiner = addOneLineMethodFolding(list, method);
504         if (!oneLiner) {
505           addToFold(list, method, document, true);
506         }
507         addAnnotationsToFold(method.getModifierList(), list, document);
508
509         if (foldJavaDocs) {
510           docComment = method.getDocComment();
511           if (docComment != null) {
512             addToFold(list, docComment, document, true);
513           }
514         }
515
516         PsiCodeBlock body = method.getBody();
517         if (body != null && !oneLiner) {
518           addCodeBlockFolds(body, list, processedComments, document, quick);
519         }
520       }
521       else if (child instanceof PsiField) {
522         PsiField field = (PsiField)child;
523         if (foldJavaDocs) {
524           docComment = field.getDocComment();
525           if (docComment != null) {
526             addToFold(list, docComment, document, true);
527           }
528         }
529         addAnnotationsToFold(field.getModifierList(), list, document);
530         PsiExpression initializer = field.getInitializer();
531         if (initializer != null) {
532           addCodeBlockFolds(initializer, list, processedComments, document, quick);
533         } else if (field instanceof PsiEnumConstant) {
534           addCodeBlockFolds(field, list, processedComments, document, quick);
535         }
536       }
537       else if (child instanceof PsiClassInitializer) {
538         PsiClassInitializer initializer = (PsiClassInitializer)child;
539         addToFold(list, initializer, document, true);
540         addCodeBlockFolds(initializer, list, processedComments, document, quick);
541       }
542       else if (child instanceof PsiClass) {
543         addElementsToFold(list, (PsiClass)child, document, true, quick);
544       }
545       else if (child instanceof PsiComment) {
546         addCommentFolds((PsiComment)child, processedComments, list);
547       }
548     }
549   }
550
551   private boolean addOneLineMethodFolding(@NotNull List<FoldingDescriptor> descriptorList, @NotNull PsiMethod method) {
552     if (!JavaCodeFoldingSettings.getInstance().isCollapseOneLineMethods()) {
553       return false;
554     }
555
556     Document document = method.getContainingFile().getViewProvider().getDocument();
557     PsiCodeBlock body = method.getBody();
558     PsiIdentifier nameIdentifier = method.getNameIdentifier();
559     if (body == null || document == null || nameIdentifier == null) {
560       return false;
561     }
562     if (document.getLineNumber(nameIdentifier.getTextRange().getStartOffset()) !=
563         document.getLineNumber(method.getParameterList().getTextRange().getEndOffset())) {
564       return false;
565     }
566
567     PsiJavaToken lBrace = body.getLBrace();
568     PsiJavaToken rBrace = body.getRBrace();
569     PsiStatement[] statements = body.getStatements();
570     if (lBrace == null || rBrace == null || statements.length != 1) {
571       return false;
572     }
573
574     PsiStatement statement = statements[0];
575     if (statement.textContains('\n')) {
576       return false;
577     }
578
579     if (!areOnAdjacentLines(lBrace, statement, document) || !areOnAdjacentLines(statement, rBrace, document)) {
580       //the user might intend to type at an empty line
581       return false;
582     }
583
584     int leftStart = method.getParameterList().getTextRange().getEndOffset();
585     int bodyStart = body.getTextRange().getStartOffset();
586     if (bodyStart > leftStart && !StringUtil.isEmptyOrSpaces(document.getCharsSequence().subSequence(leftStart + 1, bodyStart))) {
587       return false;
588     }
589     
590     int leftEnd = statement.getTextRange().getStartOffset();
591     int rightStart = statement.getTextRange().getEndOffset();
592     int rightEnd = body.getTextRange().getEndOffset();
593     if (leftEnd <= leftStart + 1 || rightEnd <= rightStart + 1) {
594       return false;
595     }
596
597     String leftText = " { ";
598     String rightText = " }";
599     if (!fitsRightMargin(method, document, leftStart, rightEnd, rightStart - leftEnd + leftText.length() + rightText.length())) {
600       return false;
601     }
602
603     FoldingGroup group = FoldingGroup.newGroup("one-liner");
604     descriptorList.add(new NamedFoldingDescriptor(lBrace, leftStart, leftEnd, group, leftText));
605     descriptorList.add(new NamedFoldingDescriptor(rBrace, rightStart, rightEnd, group, rightText));
606     return true;
607   }
608   
609   @Override
610   protected String getLanguagePlaceholderText(@NotNull ASTNode node, @NotNull TextRange range) {
611     return getPlaceholderText(SourceTreeToPsiMap.treeElementToPsi(node));
612   }
613
614   @Override
615   protected boolean isRegionCollapsedByDefault(@NotNull ASTNode node) {
616     final PsiElement element = SourceTreeToPsiMap.treeElementToPsi(node);
617     JavaCodeFoldingSettings settings = JavaCodeFoldingSettings.getInstance();
618     if (element instanceof PsiNewExpression || element instanceof PsiJavaToken && 
619                                                element.getParent() instanceof PsiAnonymousClass) {
620       return settings.isCollapseLambdas();
621     }
622     if (element instanceof PsiJavaToken &&
623         element.getParent() instanceof PsiCodeBlock &&
624         element.getParent().getParent() instanceof PsiMethod) {
625       return settings.isCollapseOneLineMethods();
626     }
627     if (element instanceof PsiReferenceParameterList) {
628       return settings.isCollapseConstructorGenericParameters();
629     }
630
631     if (element instanceof PsiImportList) {
632       return settings.isCollapseImports();
633     }
634     else if (element instanceof PsiMethod || element instanceof PsiClassInitializer || element instanceof PsiCodeBlock) {
635       if (element instanceof PsiMethod) {
636
637         if (!settings.isCollapseAccessors() && !settings.isCollapseMethods()) {
638           return false;
639         }
640
641         if (isSimplePropertyAccessor((PsiMethod)element)) {
642           return settings.isCollapseAccessors();
643         }
644       }
645       return settings.isCollapseMethods();
646     }
647     else if (element instanceof PsiAnonymousClass) {
648       return settings.isCollapseAnonymousClasses();
649     }
650     else if (element instanceof PsiClass) {
651       return !(element.getParent() instanceof PsiFile) && settings.isCollapseInnerClasses();
652     }
653     else if (element instanceof PsiDocComment) {
654       PsiElement parent = element.getParent();
655       if (parent instanceof PsiJavaFile) {
656         if (((PsiJavaFile)parent).getName().equals(PsiPackage.PACKAGE_INFO_FILE)) {
657           return false;
658         }
659         PsiElement firstChild = parent.getFirstChild();
660         if (firstChild instanceof PsiWhiteSpace) {
661           firstChild = firstChild.getNextSibling();
662         }
663         if (element.equals(firstChild)) {
664           return settings.isCollapseFileHeader();
665         }
666       }
667       return settings.isCollapseJavadocs();
668     }
669     else if (element instanceof PsiJavaFile) {
670       return settings.isCollapseFileHeader();
671     }
672     else if (element instanceof PsiAnnotation) {
673       return settings.isCollapseAnnotations();
674     }
675     else if (element instanceof PsiComment) {
676       return settings.isCollapseEndOfLineComments();
677     }
678     else {
679       LOG.error("Unknown element:" + element);
680       return false;
681     }
682   }
683
684   private void addCodeBlockFolds(@NotNull PsiElement scope,
685                                  @NotNull final List<FoldingDescriptor> foldElements,
686                                  @NotNull final Set<PsiElement> processedComments,
687                                  @NotNull final Document document,
688                                  final boolean quick) {
689     final boolean dumb = DumbService.isDumb(scope.getProject());
690     scope.accept(new JavaRecursiveElementWalkingVisitor() {
691       @Override
692       public void visitClass(PsiClass aClass) {
693         if (dumb || !addClosureFolding(aClass, document, foldElements, processedComments, quick)) {
694           addToFold(foldElements, aClass, document, true);
695           addElementsToFold(foldElements, aClass, document, false, quick);
696         }
697       }
698
699       @Override
700       public void visitMethodCallExpression(PsiMethodCallExpression expression) {
701         if (!dumb) {
702           addMethodGenericParametersFolding(expression, foldElements, document, quick);
703         }
704
705         super.visitMethodCallExpression(expression);
706       }
707
708       @Override
709       public void visitNewExpression(PsiNewExpression expression) {
710         if (!dumb) {
711           addGenericParametersFolding(expression, foldElements, document, quick);
712         }
713
714         super.visitNewExpression(expression);
715       }
716
717       @Override
718       public void visitComment(PsiComment comment) {
719         addCommentFolds(comment, processedComments, foldElements);
720         super.visitComment(comment);
721       }
722     });
723   }
724
725   private boolean addClosureFolding(@NotNull PsiClass aClass,
726                                     @NotNull Document document,
727                                     @NotNull List<FoldingDescriptor> foldElements,
728                                     @NotNull Set<PsiElement> processedComments,
729                                     boolean quick) {
730     if (!JavaCodeFoldingSettings.getInstance().isCollapseLambdas()) {
731       return false;
732     }
733
734     if (aClass instanceof PsiAnonymousClass) {
735       final PsiAnonymousClass anonymousClass = (PsiAnonymousClass)aClass;
736       ClosureFolding closureFolding = ClosureFolding.prepare(anonymousClass, quick, this);
737       List<NamedFoldingDescriptor> descriptors = closureFolding == null ? null : closureFolding.process(document);
738       if (descriptors != null) {
739         foldElements.addAll(descriptors);
740         addCodeBlockFolds(closureFolding.methodBody, foldElements, processedComments, document, quick);
741         return true;
742       }
743     }
744     return false;
745   }
746
747   @NotNull
748   protected String rightArrow() {
749     return "->";
750   }
751
752   boolean fitsRightMargin(@NotNull PsiElement element, @NotNull Document document, int foldingStart, int foldingEnd, int collapsedLength) {
753     final int beforeLength = foldingStart - document.getLineStartOffset(document.getLineNumber(foldingStart));
754     final int afterLength = document.getLineEndOffset(document.getLineNumber(foldingEnd)) - foldingEnd;
755     return isBelowRightMargin(element.getProject(), beforeLength + collapsedLength + afterLength);
756   }
757
758   protected abstract boolean isBelowRightMargin(@NotNull Project project, final int lineLength);
759
760   @Override
761   protected boolean isCustomFoldingCandidate(@NotNull ASTNode node) {
762     return node.getElementType() == JavaTokenType.END_OF_LINE_COMMENT;
763   }
764
765   @Override
766   protected boolean isCustomFoldingRoot(@NotNull ASTNode node) {
767     IElementType nodeType = node.getElementType();
768     if (nodeType == JavaElementType.CLASS) {
769       ASTNode parent = node.getTreeParent();
770       return parent == null || parent.getElementType() != JavaElementType.CLASS;
771     }
772     return nodeType == JavaElementType.CODE_BLOCK;
773   }
774 }