IDEA-79486 Java formatter: Correct processing of anonymous classes as method call arguments

This commit is contained in:
Denis.Zhdanov
2012-01-11 15:34:41 +04:00
parent 9ff64eb2db
commit 5e39306022
6 changed files with 135 additions and 194 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.psi.formatter.java.JavaFormatterUtil.isFirstAmongOthersAnonymousClassMethodCallArguments;
import static com.intellij.psi.formatter.java.JavaFormatterUtil.hasAnonymousClassesArguments;
import static java.util.Arrays.asList;
public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlock, ReservedWrapsProvider {
@@ -130,8 +130,8 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
public static Block createJavaBlock(final ASTNode child,
final CommonCodeStyleSettings settings,
final Indent indent,
Wrap wrap,
@Nullable final Indent indent,
@Nullable Wrap wrap,
Alignment alignment) {
return createJavaBlock(child, settings, indent, wrap, AlignmentStrategy.wrap(alignment));
}
@@ -139,7 +139,7 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
public static Block createJavaBlock(final ASTNode child,
final CommonCodeStyleSettings settings,
final Indent indent,
Wrap wrap,
@Nullable Wrap wrap,
@NotNull AlignmentStrategy alignmentStrategy) {
return createJavaBlock(child, settings, indent, wrap, alignmentStrategy, -1);
}
@@ -388,8 +388,9 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
}
}
protected Alignment chooseAlignment(Alignment alignment, Alignment alignment2, ASTNode child) {
if (preferesSlaveAlignment(child)) {
@Nullable
protected Alignment chooseAlignment(@Nullable Alignment alignment, @Nullable Alignment alignment2, ASTNode child) {
if (preferSlaveAlignment(child)) {
return alignment2;
}
else {
@@ -397,7 +398,7 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
}
}
private boolean preferesSlaveAlignment(final ASTNode child) {
private boolean preferSlaveAlignment(final ASTNode child) {
final IElementType nodeType = myNode.getElementType();
if (nodeType == JavaElementType.CONDITIONAL_EXPRESSION) {
@@ -433,7 +434,7 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
protected ASTNode processChild(final List<Block> result,
ASTNode child,
AlignmentStrategy alignmentStrategy,
final Wrap defaultWrap,
@Nullable final Wrap defaultWrap,
final Indent childIndent) {
return processChild(result, child, alignmentStrategy, defaultWrap, childIndent, -1);
}
@@ -724,7 +725,7 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
// Just create a no-aligned sub-block if we don't need to bother with it's alignment (either due to end-user
// setup or sub-block state).
if (chainedCallsAlignment == null || subNodes.isEmpty()) {
subBlocks.add(createSynthBlock(subNodes, wrap, alignmentToUseForSubBlock));
subBlocks.add(createSyntheticBlock(subNodes, wrap, alignmentToUseForSubBlock));
continue;
}
@@ -742,21 +743,21 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
if (callPointDefined && currentSubBlockIsMethodCall) {
alignmentToUseForSubBlock = chainedCallsAlignment;
}
subBlocks.add(createSynthBlock(subNodes, wrap, alignmentToUseForSubBlock));
subBlocks.add(createSyntheticBlock(subNodes, wrap, alignmentToUseForSubBlock));
}
return new SyntheticCodeBlock(subBlocks, alignment, mySettings,
Indent.getContinuationWithoutFirstIndent(myIndentSettings.USE_RELATIVE_INDENTS), blockWrap);
}
private Block createSynthBlock(final ArrayList<ASTNode> subNodes, final Wrap wrap, final Alignment alignment) {
private Block createSyntheticBlock(final ArrayList<ASTNode> subNodes, final Wrap wrap, @Nullable final Alignment alignment) {
final ArrayList<Block> subBlocks = new ArrayList<Block>();
final ASTNode firstNode = subNodes.get(0);
if (firstNode.getElementType() == JavaTokenType.DOT) {
subBlocks.add(createJavaBlock(firstNode, getSettings(), Indent.getNoneIndent(), null, AlignmentStrategy.getNullStrategy()));
subNodes.remove(0);
if (!subNodes.isEmpty()) {
subBlocks.add(createSynthBlock(subNodes, wrap, null));
subBlocks.add(createSyntheticBlock(subNodes, wrap, null));
}
return new SyntheticCodeBlock(subBlocks, alignment, mySettings,
Indent.getContinuationIndent(myIndentSettings.USE_RELATIVE_INDENTS), wrap);
@@ -837,7 +838,7 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
}
if (nodeType == JavaElementType.CLASS) {
// There is a possible case that current document state is invalid from language syntax point of view, e.g. the user starts
// typing field definition and re-formatting is triggered by 'auto insert javadocs' processing. Example:
// typing field definition and re-formatting is triggered by 'auto insert javadoc' processing. Example:
// class Test {
// @NotNull Object
// }
@@ -945,12 +946,6 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
}
return null;
}
else if (nodeType == JavaElementType.ANONYMOUS_CLASS && role == ChildRole.RBRACE
&& isFirstAmongOthersAnonymousClassMethodCallArguments(myNode))
{
return myAlignment;
}
else {
return defaultAlignment;
@@ -1004,11 +999,14 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
}
*/
private static Alignment createAlignment(final boolean alignOption, final Alignment defaultAlignment) {
@Nullable
private static Alignment createAlignment(final boolean alignOption, @Nullable final Alignment defaultAlignment) {
return alignOption ? createAlignmentOrDefault(null, defaultAlignment) : defaultAlignment;
}
private static Alignment createAlignment(Alignment base, final boolean alignOption, final Alignment defaultAlignment) {
@Nullable
private static Alignment createAlignment(Alignment base, final boolean alignOption, @Nullable final Alignment defaultAlignment) {
return alignOption ? createAlignmentOrDefault(base, defaultAlignment) : defaultAlignment;
}
@@ -1068,6 +1066,14 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
|| methodCandidate.getElementType() == JavaElementType.METHOD_CALL_EXPRESSION);
}
Alignment bracketAlignment = methodParametersBlock && mySettings.ALIGN_MULTILINE_METHOD_BRACKETS ? Alignment.createAlignment() : null;
AlignmentStrategy anonymousClassStrategy = doAlign ? alignmentStrategy
: AlignmentStrategy.wrap(Alignment.createAlignment(),
false,
JavaTokenType.NEW_KEYWORD,
JavaElementType.NEW_EXPRESSION,
JavaTokenType.RBRACE);
setChildIndent(internalIndent);
setChildAlignment(alignmentStrategy.getAlignment(null));
boolean isAfterIncomplete = false;
@@ -1088,8 +1094,10 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
}
else {
final IElementType elementType = child.getElementType();
Indent indentToUse = shouldEnforceIndentToChildren(child) ? internalIndentEnforcedToChildren : internalIndent;
processChild(result, child, alignmentStrategy.getAlignment(elementType), wrappingStrategy.getWrap(elementType), indentToUse);
final boolean enforceIndent = shouldEnforceIndentToChildren(child);
Indent indentToUse = enforceIndent ? internalIndentEnforcedToChildren : internalIndent;
AlignmentStrategy alignmentStrategyToUse = canUseAnonymousClassAlignment(child) ? anonymousClassStrategy : alignmentStrategy;
processChild(result, child, alignmentStrategyToUse.getAlignment(elementType), wrappingStrategy.getWrap(elementType), indentToUse);
if (to == null) {//process only one statement
return child;
}
@@ -1103,26 +1111,48 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
return prev;
}
private boolean shouldEnforceIndentToChildren(@NotNull ASTNode node) {
// Don't enforce indent if given node is the last argument, i.e. prefer the code below
// void test() {
// foo("test", new Runnable() {
// public void run() {
// }
// });
// }
// to this one:
// void test() {
// foo("test", new Runnable() {
// public void run() {
// }
// });
// }
ASTNode rBrace = myNode.getLastChildNode();
if (node == FormatterUtil.getPreviousNonWhitespaceSibling(rBrace)) {
private static boolean canUseAnonymousClassAlignment(@NotNull ASTNode child) {
// The general idea is to handle situations like below:
// test(new Runnable() {
// public void run() {
// }
// }, new Runnable() {
// public void run() {
// }
// }
// );
// I.e. we want to align subsequent anonymous class argument to the previous one if it's not preceded by another argument
// at the same line, e.g.:
// test("this is a long argument", new Runnable() {
// public void run() {
// }
// }, new Runnable() {
// public void run() {
// }
// }
// );
if (!isAnonymousClass(child)) {
return false;
}
for (ASTNode node = child.getTreePrev(); node != null; node = node.getTreePrev()) {
if (node.getElementType() == TokenType.WHITE_SPACE) {
if (StringUtil.countNewLines(node.getChars()) > 0) {
return false;
}
}
else if (node.getElementType() == JavaTokenType.LPARENTH) {
// First method call argument.
return true;
}
else if (node.getElementType() != JavaTokenType.COMMA && !isAnonymousClass(node)) {
return false;
}
}
return true;
}
private boolean shouldEnforceIndentToChildren(@NotNull ASTNode node) {
// Filter only anonymous class instances as method call arguments
if (myNode.getElementType() != JavaElementType.EXPRESSION_LIST) {
return false;
@@ -1131,7 +1161,7 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
if (parent == null || parent.getElementType() != JavaElementType.METHOD_CALL_EXPRESSION) {
return false;
}
if (!isAnonymousClass(node)) {
if (!isAnonymousClass(node) || !hasAnonymousClassesArguments((PsiExpressionList)myNode.getPsi(), 2)) {
return false;
}
@@ -1140,24 +1170,8 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
if (prev == null || (StringUtil.containsLineBreak(prev.getChars()) && prev.getElementType() != TokenType.WHITE_SPACE)) {
return false;
}
final PsiElement psi = myNode.getPsi();
if (!(psi instanceof PsiExpressionList)) {
return false;
}
PsiExpressionList expressionList = (PsiExpressionList)psi;
for (PsiExpression expression : expressionList.getExpressions()) {
final ASTNode argumentNode = expression.getNode();
if (argumentNode == node) {
continue;
}
if (isAnonymousClass(argumentNode)) {
return true;
}
}
return false;
return true;
}
private static boolean isAnonymousClass(@Nullable ASTNode node) {
@@ -1194,7 +1208,8 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
myChildIndent = internalIndent;
}
private static Alignment createAlignmentOrDefault(Alignment base, final Alignment defaultAlignment) {
@Nullable
private static Alignment createAlignmentOrDefault(@Nullable Alignment base, @Nullable final Alignment defaultAlignment) {
if (defaultAlignment == null) {
return base == null ? Alignment.createAlignment() : Alignment.createChildAlignment(base);
}
@@ -1385,7 +1400,7 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
@Nullable
protected ASTNode composeCodeBlock(final ArrayList<Block> result, ASTNode child, final Indent indent, final int childrenIndent,
final Wrap childWrap) {
@Nullable final Wrap childWrap) {
final ArrayList<Block> localResult = new ArrayList<Block>();
processChild(localResult, child, AlignmentStrategy.getNullStrategy(), null, Indent.getNoneIndent());
child = child.getTreeNext();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package com.intellij.psi.formatter.java;
import com.intellij.formatting.*;
import com.intellij.formatting.alignment.AlignmentStrategy;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.TokenType;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
@@ -128,7 +129,7 @@ public class CodeBlockBlock extends AbstractJavaBlock {
child = composeCodeBlock(result, child, indent, myChildrenIndent, childWrap);
}
else {
child = processChild(result, child, childAlignment, childWrap, indent);
child = processChild(result, child, chooseAlignment(child, childAlignment), childWrap, indent);
}
}
if (child != null) {
@@ -137,6 +138,26 @@ public class CodeBlockBlock extends AbstractJavaBlock {
}
}
@Nullable
private Alignment chooseAlignment(@NotNull ASTNode child, @Nullable Alignment defaultAlignment) {
if (defaultAlignment != null) {
return defaultAlignment;
}
// Take special care about anonymous classes.
if (child.getElementType() != JavaTokenType.RBRACE) {
return defaultAlignment;
}
final ASTNode parent = child.getTreeParent();
if (parent == null || parent.getElementType() != JavaElementType.ANONYMOUS_CLASS) {
return defaultAlignment;
}
final ASTNode whiteSpaceCandidate = parent.getTreePrev();
if (whiteSpaceCandidate == null || whiteSpaceCandidate.getElementType() != TokenType.WHITE_SPACE) {
return defaultAlignment;
}
return StringUtil.countNewLines(whiteSpaceCandidate.getChars()) > 0 ? myAlignment : defaultAlignment;
}
@Nullable
private ASTNode processCaseAndStatementAfter(final ArrayList<Block> result,
ASTNode child,
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,9 +19,7 @@ import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiExpressionList;
import com.intellij.psi.PsiPolyadicExpression;
import com.intellij.psi.formatter.FormatterUtil;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.JavaJspElementType;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -47,10 +45,10 @@ public class JavaFormatterUtil {
}
/**
* Allows to answer if given node wraps assignement operation.
* Allows to answer if given node wraps assignment operation.
*
* @param node node to check
* @return <code>true</code> if given node wraps assignement operation; <code>false</code> otherwise
* @return <code>true</code> if given node wraps assignment operation; <code>false</code> otherwise
*/
public static boolean isAssignment(ASTNode node) {
return ASSIGNMENT_ELEMENT_TYPES.contains(node.getElementType());
@@ -77,116 +75,6 @@ public class JavaFormatterUtil {
return expression1.getOperationTokenType() == expression2.getOperationTokenType();
}
public static boolean isFirstMethodCallArgument(@NotNull ASTNode node) {
ASTNode firstArgCandidate = node;
ASTNode expressionList = node.getTreeParent();
if (expressionList == null) {
return false;
}
if (expressionList.getElementType() != JavaElementType.EXPRESSION_LIST
&& expressionList.getElementType() == JavaElementType.NEW_EXPRESSION)
{
firstArgCandidate = expressionList;
expressionList = expressionList.getTreeParent();
}
if (expressionList == null || expressionList.getElementType() != JavaElementType.EXPRESSION_LIST) {
return false;
}
ASTNode methodCallExpression = expressionList.getTreeParent();
if (methodCallExpression == null || methodCallExpression.getElementType() != JavaElementType.METHOD_CALL_EXPRESSION) {
return false;
}
ASTNode lbrace = expressionList.getFirstChildNode();
ASTNode firstArg = lbrace.getTreeNext();
if (firstArg != null && JavaJspElementType.WHITE_SPACE_BIT_SET.contains(firstArg.getElementType())) {
firstArg = firstArg.getTreeNext();
}
return firstArg == firstArgCandidate;
}
/**
* Allows to check if given node references anonymous class instance used as a method call argument. The most important thing
* is that that method call expression should have other anonymous classes as well.
* <p/>
* <b>Examples</b>
* <pre>
* test(new Runnable() { &lt;-- true is returned for this node
* public void run() {
* }
* },
* new Runnable() { &lt;-- false is returned for this node
* public void run() {
* }
* }
* );
*
* test(1234, "text", new Runnable() { &lt;-- true is returned for this node because there are no other anonymous
* public void run() { class objects at method call expression before it
* }
* },
* new Runnable() { &lt;-- false is returned for this node
* public void run() {
* }
* }
* );
*
* test(1234, "text", new Runnable() { &lt;-- false is returned for this node because there are no other anonymous
* public void run() { class objects at method call expression after it
* }
* });
* </pre>
*
* @param node node to process
* @return
*/
public static boolean isFirstAmongOthersAnonymousClassMethodCallArguments(@NotNull ASTNode node) {
ASTNode expressionList = node.getTreeParent();
ASTNode firstAnonymousClassCandidate = node;
if (expressionList == null) {
return false;
}
if (expressionList.getElementType() != JavaElementType.EXPRESSION_LIST
&& expressionList.getElementType() == JavaElementType.NEW_EXPRESSION)
{
firstAnonymousClassCandidate = expressionList;
expressionList = expressionList.getTreeParent();
}
if (expressionList == null || expressionList.getElementType() != JavaElementType.EXPRESSION_LIST) {
return false;
}
ASTNode methodCallExpression = expressionList.getTreeParent();
if (methodCallExpression == null || methodCallExpression.getElementType() != JavaElementType.METHOD_CALL_EXPRESSION) {
return false;
}
ASTNode lbrace = expressionList.getFirstChildNode();
boolean firstAnonymousClass = false;
for (ASTNode arg = lbrace.getTreeNext(); arg != null; arg = FormatterUtil.getNextNonWhitespaceSibling(arg)) {
if (!isAnonymousClass(arg)) {
continue;
}
if (firstAnonymousClass) {
// Other anonymous class is found at the method call expression after the target one.
return true;
}
else if (arg != firstAnonymousClassCandidate) {
return false;
}
else {
firstAnonymousClass = true;
}
}
return false;
}
/**
* Allows to check if given expression list has given number of anonymous classes.
*
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.formatter.FormatterUtil;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.StdTokenSets;
@@ -75,10 +74,11 @@ public class SimpleJavaBlock extends AbstractJavaBlock {
if (astNode != child && child != null) {
offset = child.getTextRange().getStartOffset();
}
if (indent != null && !(myNode.getPsi() instanceof PsiFile) && child != null && child.getElementType() != ElementType.MODIFIER_LIST) {
if (indent != null
&& !(myNode.getPsi() instanceof PsiFile) && child != null && child.getElementType() != JavaElementType.MODIFIER_LIST)
{
indent = Indent.getContinuationIndent(myIndentSettings.USE_RELATIVE_INDENTS);
}
//indent = FormatterEx.getInstance().getContinuationIndent();
}
if (child != null) {
offset += child.getTextLength();
@@ -101,7 +101,7 @@ public class SimpleJavaBlock extends AbstractJavaBlock {
@Override
@NotNull
public ChildAttributes getChildAttributes(final int newChildIndex) {
if (myNode.getElementType() == ElementType.CONDITIONAL_EXPRESSION && mySettings.ALIGN_MULTILINE_TERNARY_OPERATION) {
if (myNode.getElementType() == JavaElementType.CONDITIONAL_EXPRESSION && mySettings.ALIGN_MULTILINE_TERNARY_OPERATION) {
final Alignment usedAlignment = getUsedAlignment(newChildIndex);
if (usedAlignment != null) {
return new ChildAttributes(null, usedAlignment);
@@ -129,8 +129,5 @@ public class SimpleJavaBlock extends AbstractJavaBlock {
public void setStartOffset(final int startOffset) {
myStartOffset = startOffset;
//if (startOffset != -1 && startOffset != myNode.getTextRange().getStartOffset()) {
// assert false;
//}
}
}