2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.codeInsight.folding.impl;
18 import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtilBase;
19 import com.intellij.codeInsight.generation.OverrideImplementExploreUtil;
20 import com.intellij.lang.folding.NamedFoldingDescriptor;
21 import com.intellij.openapi.editor.Document;
22 import com.intellij.openapi.editor.FoldingGroup;
23 import com.intellij.openapi.project.IndexNotReadyException;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.psi.*;
26 import com.intellij.psi.util.PsiUtil;
27 import com.intellij.util.Function;
28 import com.intellij.util.ObjectUtils;
29 import com.intellij.util.text.CharArrayUtil;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
33 import java.util.ArrayList;
34 import java.util.List;
39 class ClosureFolding {
40 @NotNull private final PsiAnonymousClass myAnonymousClass;
41 @NotNull private final PsiNewExpression myNewExpression;
42 @Nullable private final PsiClass myBaseClass;
43 @NotNull private final JavaFoldingBuilderBase myBuilder;
44 @NotNull private final PsiMethod myMethod;
45 @NotNull final PsiCodeBlock methodBody;
46 private final boolean myQuick;
48 private ClosureFolding(@NotNull PsiAnonymousClass anonymousClass,
49 @NotNull PsiNewExpression newExpression,
51 @Nullable PsiClass baseClass,
52 @NotNull JavaFoldingBuilderBase builder,
53 @NotNull PsiMethod method,
54 @NotNull PsiCodeBlock methodBody) {
55 myAnonymousClass = anonymousClass;
56 myNewExpression = newExpression;
58 myBaseClass = baseClass;
61 this.methodBody = methodBody;
65 List<NamedFoldingDescriptor> process(Document document) {
66 PsiJavaToken lbrace = methodBody.getLBrace();
67 PsiJavaToken rbrace = methodBody.getRBrace();
68 PsiElement classRBrace = myAnonymousClass.getRBrace();
69 if (lbrace == null || rbrace == null || classRBrace == null) return null;
71 CharSequence seq = document.getCharsSequence();
72 int rangeStart = lbrace.getTextRange().getEndOffset();
73 int rangeEnd = getContentRangeEnd(document, rbrace, classRBrace);
75 String contents = getClosureContents(rangeStart, rangeEnd, seq);
76 if (contents == null) return null;
78 String header = getFoldingHeader();
79 if (showSingleLineFolding(document, contents, header)) {
80 return createDescriptors(classRBrace, trimStartSpaces(seq, rangeStart), trimTailSpaces(seq, rangeEnd), header + " ", " }");
83 return createDescriptors(classRBrace, rangeStart, rangeEnd, header, "}");
86 private static int trimStartSpaces(CharSequence seq, int rangeStart) {
87 return CharArrayUtil.shiftForward(seq, rangeStart, " \n\t");
90 private static int trimTailSpaces(CharSequence seq, int rangeEnd) {
91 return CharArrayUtil.shiftBackward(seq, rangeEnd - 1, " \n\t") + 1;
94 private static int getContentRangeEnd(Document document, PsiJavaToken rbrace, PsiElement classRBrace) {
95 CharSequence seq = document.getCharsSequence();
96 int rangeEnd = rbrace.getTextRange().getStartOffset();
98 int methodEndLine = document.getLineNumber(rangeEnd);
99 int methodEndLineStart = document.getLineStartOffset(methodEndLine);
100 if ("}".equals(seq.subSequence(methodEndLineStart, document.getLineEndOffset(methodEndLine)).toString().trim())) {
101 int classEndStart = classRBrace.getTextRange().getStartOffset();
102 int classEndCol = classEndStart - document.getLineStartOffset(document.getLineNumber(classEndStart));
103 return classEndCol + methodEndLineStart;
108 private boolean showSingleLineFolding(Document document, String contents, String header) {
109 return contents.indexOf('\n') < 0 &&
110 myBuilder.fitsRightMargin(myAnonymousClass, document, getClosureStartOffset(), getClosureEndOffset(), header.length() + contents.length() + 5);
113 private int getClosureEndOffset() {
114 return myNewExpression.getTextRange().getEndOffset();
117 private int getClosureStartOffset() {
118 return myNewExpression.getTextRange().getStartOffset();
122 private List<NamedFoldingDescriptor> createDescriptors(PsiElement classRBrace,
127 if (rangeStart >= rangeEnd) return null;
129 FoldingGroup group = FoldingGroup.newGroup("lambda");
130 List<NamedFoldingDescriptor> foldElements = new ArrayList<NamedFoldingDescriptor>();
131 foldElements.add(new NamedFoldingDescriptor(myNewExpression, getClosureStartOffset(), rangeStart, group, header));
132 if (rangeEnd + 1 < getClosureEndOffset()) {
133 foldElements.add(new NamedFoldingDescriptor(classRBrace, rangeEnd, getClosureEndOffset(), group, footer));
139 private static String getClosureContents(int rangeStart, int rangeEnd, CharSequence seq) {
140 int firstLineStart = CharArrayUtil.shiftForward(seq, rangeStart, " \t");
141 if (firstLineStart < seq.length() - 1 && seq.charAt(firstLineStart) == '\n') firstLineStart++;
143 int lastLineEnd = CharArrayUtil.shiftBackward(seq, rangeEnd - 1, " \t");
144 if (lastLineEnd > 0 && seq.charAt(lastLineEnd) == '\n') lastLineEnd--;
145 if (lastLineEnd < firstLineStart) return null;
146 return seq.subSequence(firstLineStart, lastLineEnd).toString();
149 private String getFoldingHeader() {
150 String methodName = shouldShowMethodName() ? myMethod.getName() : "";
151 String type = myQuick ? "" : getOptionalLambdaType();
152 String params = StringUtil.join(myMethod.getParameterList().getParameters(), new Function<PsiParameter, String>() {
154 public String fun(PsiParameter psiParameter) {
155 return psiParameter.getName();
158 return type + methodName + "(" + params + ") " + myBuilder.rightArrow() + " {";
162 static ClosureFolding prepare(PsiAnonymousClass anonymousClass, boolean quick, JavaFoldingBuilderBase builder) {
163 PsiElement parent = anonymousClass.getParent();
164 if (parent instanceof PsiNewExpression && hasNoArguments((PsiNewExpression)parent)) {
165 PsiClass baseClass = quick ? null : anonymousClass.getBaseClassType().resolve();
166 if (hasOnlyOneLambdaMethod(anonymousClass, !quick) && (quick || seemsLikeLambda(baseClass, anonymousClass))) {
167 PsiMethod method = anonymousClass.getMethods()[0];
168 PsiCodeBlock body = method.getBody();
170 return new ClosureFolding(anonymousClass, (PsiNewExpression)parent, quick, baseClass, builder, method, body);
177 private static boolean hasNoArguments(PsiNewExpression expression) {
178 PsiExpressionList argumentList = expression.getArgumentList();
179 return argumentList != null && argumentList.getExpressions().length == 0;
182 private static boolean hasOnlyOneLambdaMethod(@NotNull PsiAnonymousClass anonymousClass, boolean checkResolve) {
183 PsiField[] fields = anonymousClass.getFields();
184 if (fields.length != 0) {
185 if (fields.length == 1 && HighlightUtilBase.SERIAL_VERSION_UID_FIELD_NAME.equals(fields[0].getName()) &&
186 fields[0].hasModifierProperty(PsiModifier.STATIC)) {
192 if (anonymousClass.getInitializers().length != 0 ||
193 anonymousClass.getInnerClasses().length != 0 ||
194 anonymousClass.getMethods().length != 1) {
198 PsiMethod method = anonymousClass.getMethods()[0];
199 if (method.hasModifierProperty(PsiModifier.SYNCHRONIZED)) {
204 for (PsiClassType type : method.getThrowsList().getReferencedTypes()) {
205 if (type.resolve() == null) {
214 static boolean seemsLikeLambda(@Nullable PsiClass baseClass, @NotNull PsiElement context) {
215 if (baseClass == null || !PsiUtil.hasDefaultConstructor(baseClass, true)) return false;
217 if (PsiUtil.isLanguageLevel8OrHigher(context) && LambdaUtil.isFunctionalClass(baseClass)) {
224 private String getOptionalLambdaType() {
225 if (myBuilder.shouldShowExplicitLambdaType(myAnonymousClass, myNewExpression)) {
226 String baseClassName = ObjectUtils.assertNotNull(myAnonymousClass.getBaseClassType().resolve()).getName();
227 if (baseClassName != null) {
228 return "(" + baseClassName + ") ";
234 private boolean shouldShowMethodName() {
235 if (myBaseClass == null || !myBaseClass.hasModifierProperty(PsiModifier.ABSTRACT)) return true;
237 for (PsiMethod method : myBaseClass.getMethods()) {
238 if (method.hasModifierProperty(PsiModifier.ABSTRACT)) {
244 return OverrideImplementExploreUtil.getMethodSignaturesToImplement(myBaseClass).isEmpty();
246 catch (IndexNotReadyException e) {