| /* |
| * 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. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.psi.formatter.java; |
| |
| import com.intellij.formatting.*; |
| import com.intellij.formatting.alignment.AlignmentInColumnsConfig; |
| import com.intellij.formatting.alignment.AlignmentInColumnsHelper; |
| import com.intellij.formatting.alignment.AlignmentStrategy; |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleSettings; |
| import com.intellij.psi.codeStyle.CommonCodeStyleSettings; |
| import com.intellij.psi.formatter.FormatterUtil; |
| import com.intellij.psi.formatter.common.AbstractBlock; |
| import com.intellij.psi.formatter.java.wrap.JavaWrapManager; |
| import com.intellij.psi.formatter.java.wrap.ReservedWrapsProvider; |
| import com.intellij.psi.impl.source.SourceTreeToPsiMap; |
| import com.intellij.psi.impl.source.codeStyle.ShiftIndentInsideHelper; |
| import com.intellij.psi.impl.source.tree.*; |
| import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; |
| import com.intellij.psi.impl.source.tree.java.ClassElement; |
| import com.intellij.psi.jsp.JspElementType; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.tree.TokenSet; |
| import com.intellij.util.text.CharArrayUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlock, ReservedWrapsProvider { |
| |
| /** |
| * Holds types of the elements for which <code>'align in column'</code> rule may be preserved. |
| * |
| * @see CodeStyleSettings#ALIGN_GROUP_FIELD_DECLARATIONS |
| */ |
| protected static final Set<IElementType> ALIGN_IN_COLUMNS_ELEMENT_TYPES = Collections.unmodifiableSet(new HashSet<IElementType>( |
| Arrays.asList(JavaElementType.FIELD))); |
| |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.formatter.java.AbstractJavaBlock"); |
| |
| /** |
| * Shared thread-safe config object to use during <code>'align in column'</code> processing. |
| * |
| * @see CodeStyleSettings#ALIGN_GROUP_FIELD_DECLARATIONS |
| */ |
| private static final AlignmentInColumnsConfig ALIGNMENT_IN_COLUMNS_CONFIG = new AlignmentInColumnsConfig( |
| TokenSet.create(JavaTokenType.IDENTIFIER), JavaJspElementType.WHITE_SPACE_BIT_SET, ElementType.JAVA_COMMENT_BIT_SET, |
| TokenSet.create(JavaTokenType.EQ), TokenSet.create(JavaElementType.FIELD)); |
| |
| /** |
| * Enumerates types of variable declaration sub-elements that should be aligned in columns. |
| */ |
| private static final Set<IElementType> VAR_DECLARATION_ELEMENT_TYPES_TO_ALIGN = new HashSet<IElementType>(Arrays.asList( |
| JavaElementType.MODIFIER_LIST, JavaElementType.TYPE, JavaTokenType.IDENTIFIER, JavaTokenType.EQ |
| )); |
| |
| @NotNull protected final CommonCodeStyleSettings mySettings; |
| protected final CommonCodeStyleSettings.IndentOptions myIndentSettings; |
| private final Indent myIndent; |
| protected Indent myChildIndent; |
| protected Alignment myChildAlignment; |
| protected boolean myUseChildAttributes = false; |
| @NotNull protected final AlignmentStrategy myAlignmentStrategy; |
| private boolean myIsAfterClassKeyword = false; |
| private Wrap myAnnotationWrap = null; |
| |
| protected Alignment myReservedAlignment; |
| protected Alignment myReservedAlignment2; |
| |
| private final JavaWrapManager myWrapManager; |
| private final AlignmentInColumnsHelper myAlignmentInColumnsHelper; |
| |
| protected AbstractJavaBlock(@NotNull final ASTNode node, |
| final Wrap wrap, |
| final Alignment alignment, |
| final Indent indent, |
| @NotNull final CommonCodeStyleSettings settings) { |
| this(node, wrap, indent, settings, JavaWrapManager.INSTANCE, AlignmentStrategy.wrap(alignment), AlignmentInColumnsHelper.INSTANCE); |
| } |
| |
| protected AbstractJavaBlock(@NotNull final ASTNode node, |
| final Wrap wrap, |
| @NotNull final AlignmentStrategy alignmentStrategy, |
| final Indent indent, |
| @NotNull final CommonCodeStyleSettings settings) { |
| this(node, wrap, indent, settings, JavaWrapManager.INSTANCE, alignmentStrategy, AlignmentInColumnsHelper.INSTANCE); |
| } |
| |
| protected AbstractJavaBlock(@NotNull final ASTNode node, |
| final Wrap wrap, |
| final Indent indent, |
| @NotNull final CommonCodeStyleSettings settings, |
| final JavaWrapManager wrapManager, |
| @NotNull final AlignmentStrategy alignmentStrategy, |
| AlignmentInColumnsHelper alignmentInColumnsHelper) { |
| super(node, wrap, createBlockAlignment(alignmentStrategy, node)); |
| mySettings = settings; |
| myIndentSettings = settings.getIndentOptions(); |
| myIndent = indent; |
| myWrapManager = wrapManager; |
| myAlignmentStrategy = alignmentStrategy; |
| myAlignmentInColumnsHelper = alignmentInColumnsHelper; |
| } |
| |
| @Nullable |
| private static Alignment createBlockAlignment(@NotNull AlignmentStrategy strategy, @NotNull ASTNode node) { |
| // There is a possible case that 'implements' section is incomplete (e.g. ends with comma). We may want to align lbrace |
| // to the first implemented interface reference then. |
| if (node.getElementType() == JavaElementType.IMPLEMENTS_LIST) { |
| return null; |
| } |
| return strategy.getAlignment(node.getElementType()); |
| } |
| |
| @NotNull |
| public static Block createJavaBlock(@NotNull final ASTNode child, |
| @NotNull final CommonCodeStyleSettings settings, |
| @Nullable final Indent indent, |
| @Nullable Wrap wrap, |
| Alignment alignment) { |
| return createJavaBlock(child, settings, indent, wrap, AlignmentStrategy.wrap(alignment)); |
| } |
| |
| @NotNull |
| public static Block createJavaBlock(@NotNull final ASTNode child, |
| @NotNull final CommonCodeStyleSettings settings, |
| final Indent indent, |
| @Nullable Wrap wrap, |
| @NotNull AlignmentStrategy alignmentStrategy) { |
| return createJavaBlock(child, settings, indent, wrap, alignmentStrategy, -1); |
| } |
| |
| @NotNull |
| public static Block createJavaBlock(@NotNull final ASTNode child, |
| @NotNull final CommonCodeStyleSettings settings, |
| @Nullable final Indent indent, |
| Wrap wrap, |
| @NotNull AlignmentStrategy alignmentStrategy, |
| int startOffset) { |
| Indent actualIndent = indent == null ? getDefaultSubtreeIndent(child, getJavaIndentOptions(settings)) : indent; |
| final IElementType elementType = child.getElementType(); |
| Alignment alignment = alignmentStrategy.getAlignment(elementType); |
| |
| if (child.getPsi() instanceof PsiWhiteSpace) { |
| String text = child.getText(); |
| int start = CharArrayUtil.shiftForward(text, 0, " \t\n"); |
| int end = CharArrayUtil.shiftBackward(text, text.length() - 1, " \t\n") + 1; |
| LOG.assertTrue(start < end); |
| return new PartialWhitespaceBlock(child, new TextRange(start + child.getStartOffset(), end + child.getStartOffset()), |
| wrap, alignment, actualIndent, settings); |
| } |
| if (child.getPsi() instanceof PsiClass) { |
| return new CodeBlockBlock(child, wrap, alignment, actualIndent, settings); |
| } |
| if (isBlockType(elementType)) { |
| return new BlockContainingJavaBlock(child, wrap, alignment, actualIndent, settings); |
| } |
| if (isStatement(child, child.getTreeParent())) { |
| return new CodeBlockBlock(child, wrap, alignment, actualIndent, settings); |
| } |
| if (child instanceof PsiComment && child instanceof PsiLanguageInjectionHost && InjectedLanguageUtil |
| .hasInjections((PsiLanguageInjectionHost)child)) { |
| return new CommentWithInjectionBlock(child, wrap, alignment, indent, settings); |
| } |
| if (child instanceof LeafElement) { |
| final LeafBlock block = new LeafBlock(child, wrap, alignment, actualIndent); |
| block.setStartOffset(startOffset); |
| return block; |
| } |
| else if (isLikeExtendsList(elementType)) { |
| return new ExtendsListBlock(child, wrap, alignmentStrategy, settings); |
| } |
| else if (elementType == JavaElementType.CODE_BLOCK) { |
| return new CodeBlockBlock(child, wrap, alignment, actualIndent, settings); |
| } |
| else if (elementType == JavaElementType.LABELED_STATEMENT) { |
| return new LabeledJavaBlock(child, wrap, alignment, actualIndent, settings); |
| } |
| else if (elementType == JavaDocElementType.DOC_COMMENT) { |
| return new DocCommentBlock(child, wrap, alignment, actualIndent, settings); |
| } |
| else { |
| final SimpleJavaBlock simpleJavaBlock = new SimpleJavaBlock(child, wrap, alignmentStrategy, actualIndent, settings); |
| simpleJavaBlock.setStartOffset(startOffset); |
| return simpleJavaBlock; |
| } |
| } |
| |
| @NotNull |
| public static Block createJavaBlock(@NotNull ASTNode child, @NotNull CommonCodeStyleSettings settings) { |
| return createJavaBlock(child, settings, getDefaultSubtreeIndent(child, getJavaIndentOptions(settings)), |
| null, AlignmentStrategy.getNullStrategy()); |
| } |
| |
| @NotNull |
| private static CommonCodeStyleSettings.IndentOptions getJavaIndentOptions(CommonCodeStyleSettings settings) { |
| CommonCodeStyleSettings.IndentOptions indentOptions = settings.getIndentOptions(); |
| assert indentOptions != null : "Java indent options are not initialized"; |
| return indentOptions; |
| } |
| |
| private static boolean isLikeExtendsList(final IElementType elementType) { |
| return elementType == JavaElementType.EXTENDS_LIST |
| || elementType == JavaElementType.IMPLEMENTS_LIST |
| || elementType == JavaElementType.THROWS_LIST; |
| } |
| |
| private static boolean isBlockType(final IElementType elementType) { |
| return elementType == JavaElementType.SWITCH_STATEMENT |
| || elementType == JavaElementType.FOR_STATEMENT |
| || elementType == JavaElementType.WHILE_STATEMENT |
| || elementType == JavaElementType.DO_WHILE_STATEMENT |
| || elementType == JavaElementType.TRY_STATEMENT |
| || elementType == JavaElementType.CATCH_SECTION |
| || elementType == JavaElementType.IF_STATEMENT |
| || elementType == JavaElementType.METHOD |
| || elementType == JavaElementType.ARRAY_INITIALIZER_EXPRESSION |
| || elementType == JavaElementType.ANNOTATION_ARRAY_INITIALIZER |
| || elementType == JavaElementType.CLASS_INITIALIZER |
| || elementType == JavaElementType.SYNCHRONIZED_STATEMENT |
| || elementType == JavaElementType.FOREACH_STATEMENT; |
| } |
| |
| |
| @Nullable |
| private static Indent getDefaultSubtreeIndent(@NotNull ASTNode child, @NotNull CommonCodeStyleSettings.IndentOptions indentOptions) { |
| final ASTNode parent = child.getTreeParent(); |
| final IElementType childNodeType = child.getElementType(); |
| if (childNodeType == JavaElementType.ANNOTATION) { |
| if (parent.getPsi() instanceof PsiArrayInitializerMemberValue) { |
| return Indent.getNormalIndent(); |
| } |
| return Indent.getNoneIndent(); |
| } |
| |
| final ASTNode prevElement = FormatterUtil.getPreviousNonWhitespaceSibling(child); |
| if (prevElement != null && prevElement.getElementType() == JavaElementType.MODIFIER_LIST) { |
| return Indent.getNoneIndent(); |
| } |
| |
| if (childNodeType == JavaDocElementType.DOC_TAG) return Indent.getNoneIndent(); |
| if (childNodeType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) return Indent.getSpaceIndent(1); |
| if (child.getPsi() instanceof PsiFile) return Indent.getNoneIndent(); |
| if (parent != null) { |
| final Indent defaultChildIndent = getChildIndent(parent, indentOptions); |
| if (defaultChildIndent != null) return defaultChildIndent; |
| } |
| if (child.getTreeParent() instanceof PsiLambdaExpression && child instanceof PsiCodeBlock) { |
| return Indent.getNoneIndent(); |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private static Indent getChildIndent(@NotNull ASTNode parent, @NotNull CommonCodeStyleSettings.IndentOptions indentOptions) { |
| final IElementType parentType = parent.getElementType(); |
| if (parentType == JavaElementType.MODIFIER_LIST) return Indent.getNoneIndent(); |
| if (parentType == JspElementType.JSP_CODE_BLOCK) return Indent.getNormalIndent(); |
| if (parentType == JspElementType.JSP_CLASS_LEVEL_DECLARATION_STATEMENT) return Indent.getNormalIndent(); |
| if (parentType == TokenType.DUMMY_HOLDER) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.CLASS) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.IF_STATEMENT) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.TRY_STATEMENT) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.CATCH_SECTION) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.FOR_STATEMENT) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.FOREACH_STATEMENT) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.BLOCK_STATEMENT) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.DO_WHILE_STATEMENT) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.WHILE_STATEMENT) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.SWITCH_STATEMENT) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.METHOD) return Indent.getNoneIndent(); |
| if (parentType == JavaDocElementType.DOC_COMMENT) return Indent.getNoneIndent(); |
| if (parentType == JavaDocElementType.DOC_TAG) return Indent.getNoneIndent(); |
| if (parentType == JavaDocElementType.DOC_INLINE_TAG) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.IMPORT_LIST) return Indent.getNoneIndent(); |
| if (parentType == JavaElementType.FIELD) return Indent.getContinuationWithoutFirstIndent(indentOptions.USE_RELATIVE_INDENTS); |
| if (parentType == JavaElementType.EXPRESSION_STATEMENT) return Indent.getNoneIndent(); |
| if (SourceTreeToPsiMap.treeElementToPsi(parent) instanceof PsiFile) { |
| return Indent.getNoneIndent(); |
| } |
| return null; |
| } |
| |
| protected static boolean isRBrace(@NotNull final ASTNode child) { |
| return child.getElementType() == JavaTokenType.RBRACE; |
| } |
| |
| @Nullable |
| @Override |
| public Spacing getSpacing(Block child1, @NotNull Block child2) { |
| return JavaSpacePropertyProcessor.getSpacing(getTreeNode(child2), mySettings); |
| } |
| |
| @Override |
| public ASTNode getFirstTreeNode() { |
| return myNode; |
| } |
| |
| @Override |
| public Indent getIndent() { |
| return myIndent; |
| } |
| |
| protected static boolean isStatement(final ASTNode child, @Nullable final ASTNode parentNode) { |
| if (parentNode != null) { |
| final IElementType parentType = parentNode.getElementType(); |
| if (parentType == JavaElementType.CODE_BLOCK) return false; |
| final int role = ((CompositeElement)parentNode).getChildRole(child); |
| if (parentType == JavaElementType.IF_STATEMENT) return role == ChildRole.THEN_BRANCH || role == ChildRole.ELSE_BRANCH; |
| if (parentType == JavaElementType.FOR_STATEMENT) return role == ChildRole.LOOP_BODY; |
| if (parentType == JavaElementType.WHILE_STATEMENT) return role == ChildRole.LOOP_BODY; |
| if (parentType == JavaElementType.DO_WHILE_STATEMENT) return role == ChildRole.LOOP_BODY; |
| if (parentType == JavaElementType.FOREACH_STATEMENT) return role == ChildRole.LOOP_BODY; |
| } |
| return false; |
| } |
| |
| @Nullable |
| protected Wrap createChildWrap() { |
| return myWrapManager.createChildBlockWrap(this, getSettings(), this); |
| } |
| |
| @Nullable |
| protected Alignment createChildAlignment() { |
| IElementType nodeType = myNode.getElementType(); |
| if (nodeType == JavaElementType.POLYADIC_EXPRESSION) nodeType = JavaElementType.BINARY_EXPRESSION; |
| if (nodeType == JavaElementType.ASSIGNMENT_EXPRESSION) { |
| if (myNode.getTreeParent() != null |
| && myNode.getTreeParent().getElementType() == JavaElementType.ASSIGNMENT_EXPRESSION |
| && myAlignment != null) { |
| return myAlignment; |
| } |
| return createAlignment(mySettings.ALIGN_MULTILINE_ASSIGNMENT, null); |
| } |
| if (nodeType == JavaElementType.PARENTH_EXPRESSION) { |
| return createAlignment(mySettings.ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION, null); |
| } |
| if (nodeType == JavaElementType.CONDITIONAL_EXPRESSION) { |
| return createAlignment(mySettings.ALIGN_MULTILINE_TERNARY_OPERATION, null); |
| } |
| if (nodeType == JavaElementType.FOR_STATEMENT) { |
| return createAlignment(mySettings.ALIGN_MULTILINE_FOR, null); |
| } |
| if (nodeType == JavaElementType.EXTENDS_LIST || nodeType == JavaElementType.IMPLEMENTS_LIST) { |
| return createAlignment(mySettings.ALIGN_MULTILINE_EXTENDS_LIST, null); |
| } |
| if (nodeType == JavaElementType.THROWS_LIST) { |
| return createAlignment(mySettings.ALIGN_MULTILINE_THROWS_LIST, null); |
| } |
| if (nodeType == JavaElementType.PARAMETER_LIST) { |
| return createAlignment(mySettings.ALIGN_MULTILINE_PARAMETERS, null); |
| } |
| if (nodeType == JavaElementType.RESOURCE_LIST) { |
| return createAlignment(mySettings.ALIGN_MULTILINE_RESOURCES, null); |
| } |
| if (nodeType == JavaElementType.BINARY_EXPRESSION) { |
| Alignment defaultAlignment = null; |
| if (shouldInheritAlignment()) { |
| defaultAlignment = myAlignment; |
| } |
| return createAlignment(mySettings.ALIGN_MULTILINE_BINARY_OPERATION, defaultAlignment); |
| } |
| if (nodeType == JavaElementType.CLASS || nodeType == JavaElementType.METHOD) { |
| return Alignment.createAlignment(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| protected Alignment createChildAlignment2(@Nullable Alignment base) { |
| final IElementType nodeType = myNode.getElementType(); |
| if (nodeType == JavaElementType.CONDITIONAL_EXPRESSION) { |
| return base == null ? createAlignment(mySettings.ALIGN_MULTILINE_TERNARY_OPERATION, null) : createAlignment(base, mySettings.ALIGN_MULTILINE_TERNARY_OPERATION, null); |
| } |
| return null; |
| } |
| |
| @Nullable |
| protected Alignment chooseAlignment(@Nullable Alignment alignment, @Nullable Alignment alignment2, @NotNull ASTNode child) { |
| if (preferSlaveAlignment(child)) { |
| return alignment2; |
| } |
| return alignment; |
| } |
| |
| private boolean preferSlaveAlignment(@NotNull final ASTNode child) { |
| final IElementType nodeType = myNode.getElementType(); |
| |
| if (nodeType == JavaElementType.CONDITIONAL_EXPRESSION) { |
| IElementType childType = child.getElementType(); |
| return childType == JavaTokenType.QUEST || childType ==JavaTokenType.COLON; |
| } |
| else { |
| return false; |
| } |
| } |
| |
| private boolean shouldInheritAlignment() { |
| if (myNode instanceof PsiPolyadicExpression) { |
| final ASTNode treeParent = myNode.getTreeParent(); |
| if (treeParent instanceof PsiPolyadicExpression) { |
| return JavaFormatterUtil.areSamePriorityBinaryExpressions(myNode, treeParent); |
| } |
| } |
| return false; |
| } |
| |
| |
| @Nullable |
| protected ASTNode processChild(@NotNull final List<Block> result, |
| @NotNull ASTNode child, |
| Alignment defaultAlignment, |
| final Wrap defaultWrap, |
| final Indent childIndent) { |
| return processChild(result, child, AlignmentStrategy.wrap(defaultAlignment), defaultWrap, childIndent, -1); |
| } |
| |
| @Nullable |
| protected ASTNode processChild(@NotNull final List<Block> result, |
| @NotNull ASTNode child, |
| @NotNull AlignmentStrategy alignmentStrategy, |
| @Nullable final Wrap defaultWrap, |
| final Indent childIndent) { |
| return processChild(result, child, alignmentStrategy, defaultWrap, childIndent, -1); |
| } |
| |
| @Nullable |
| protected ASTNode processChild(@NotNull final List<Block> result, |
| @NotNull ASTNode child, |
| @NotNull AlignmentStrategy alignmentStrategy, |
| final Wrap defaultWrap, |
| final Indent childIndent, |
| int childOffset) { |
| final IElementType childType = child.getElementType(); |
| if (childType == JavaTokenType.CLASS_KEYWORD || childType == JavaTokenType.INTERFACE_KEYWORD) { |
| myIsAfterClassKeyword = true; |
| } |
| if (childType == JavaElementType.METHOD_CALL_EXPRESSION) { |
| result.add(createMethodCallExpressionBlock(child, |
| arrangeChildWrap(child, defaultWrap), |
| arrangeChildAlignment(child, alignmentStrategy))); |
| } |
| else { |
| IElementType nodeType = myNode.getElementType(); |
| if (nodeType == JavaElementType.POLYADIC_EXPRESSION) nodeType = JavaElementType.BINARY_EXPRESSION; |
| |
| if (childType == JavaTokenType.LBRACE && nodeType == JavaElementType.ARRAY_INITIALIZER_EXPRESSION) { |
| final Wrap wrap = Wrap.createWrap(getWrapType(mySettings.ARRAY_INITIALIZER_WRAP), false); |
| child = processParenthesisBlock(JavaTokenType.LBRACE, JavaTokenType.RBRACE, |
| result, |
| child, |
| WrappingStrategy.createDoNotWrapCommaStrategy(wrap), |
| mySettings.ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION); |
| } |
| else if (childType == JavaTokenType.LBRACE && nodeType == JavaElementType.ANNOTATION_ARRAY_INITIALIZER) { |
| final Wrap wrap = Wrap.createWrap(getWrapType(mySettings.ARRAY_INITIALIZER_WRAP), false); |
| child = processParenthesisBlock(JavaTokenType.LBRACE, JavaTokenType.RBRACE, |
| result, |
| child, |
| WrappingStrategy.createDoNotWrapCommaStrategy(wrap), |
| mySettings.ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION); |
| } |
| else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.EXPRESSION_LIST) { |
| final Wrap wrap = Wrap.createWrap(getWrapType(mySettings.CALL_PARAMETERS_WRAP), false); |
| if (mySettings.PREFER_PARAMETERS_WRAP) { |
| wrap.ignoreParentWraps(); |
| } |
| child = processParenthesisBlock(result, |
| child, |
| WrappingStrategy.createDoNotWrapCommaStrategy(wrap), |
| mySettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS); |
| } |
| else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.PARAMETER_LIST) { |
| final Wrap wrap; |
| Wrap reservedWrap = getReservedWrap(JavaElementType.MODIFIER_LIST); |
| // There is a possible case that particular annotated method definition is too long. We may wrap either after annotation |
| // or after opening lbrace then. Our strategy is to wrap after annotation whenever possible. |
| if (reservedWrap == null) { |
| wrap = Wrap.createWrap(getWrapType(mySettings.METHOD_PARAMETERS_WRAP), false); |
| } |
| else { |
| wrap = Wrap.createChildWrap(reservedWrap, getWrapType(mySettings.METHOD_PARAMETERS_WRAP), false); |
| } |
| child = processParenthesisBlock(result, child, |
| WrappingStrategy.createDoNotWrapCommaStrategy(wrap), |
| mySettings.ALIGN_MULTILINE_PARAMETERS); |
| } |
| else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.RESOURCE_LIST) { |
| final Wrap reservedWrap = getReservedWrap(JavaElementType.MODIFIER_LIST); |
| final Wrap wrap = reservedWrap != null |
| ? Wrap.createChildWrap(reservedWrap, getWrapType(mySettings.RESOURCE_LIST_WRAP), false) |
| : Wrap.createWrap(getWrapType(mySettings.RESOURCE_LIST_WRAP), false); |
| child = processParenthesisBlock(result, child, WrappingStrategy.createDoNotWrapCommaStrategy(wrap), |
| mySettings.ALIGN_MULTILINE_RESOURCES); |
| } |
| else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.ANNOTATION_PARAMETER_LIST) { |
| final Wrap wrap = Wrap.createWrap(getWrapType(mySettings.CALL_PARAMETERS_WRAP), false); |
| child = processParenthesisBlock(result, child, |
| WrappingStrategy.createDoNotWrapCommaStrategy(wrap), |
| mySettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS); |
| } |
| else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.PARENTH_EXPRESSION) { |
| child = processParenthesisBlock(result, child, |
| WrappingStrategy.DO_NOT_WRAP, |
| mySettings.ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION); |
| } |
| else if (childType == JavaElementType.ENUM_CONSTANT && myNode instanceof ClassElement) { |
| child = processEnumBlock(result, child, ((ClassElement)myNode).findEnumConstantListDelimiterPlace()); |
| } |
| else if (mySettings.TERNARY_OPERATION_SIGNS_ON_NEXT_LINE && isTernaryOperationSign(child)) { |
| child = processTernaryOperationRange(result, child, defaultWrap, childIndent); |
| } |
| else if (childType == JavaElementType.FIELD) { |
| child = processField(result, child, alignmentStrategy, defaultWrap, childIndent); |
| } |
| else if (childType == JavaElementType.LOCAL_VARIABLE |
| || childType == JavaElementType.DECLARATION_STATEMENT && myNode.getElementType() == JavaElementType.METHOD) |
| { |
| result.add(new SimpleJavaBlock(child, defaultWrap, alignmentStrategy, childIndent, mySettings)); |
| } |
| else { |
| AlignmentStrategy alignmentStrategyToUse = AlignmentStrategy.wrap(arrangeChildAlignment(child, alignmentStrategy)); |
| if (myAlignmentStrategy.getAlignment(nodeType, childType) != null |
| && (nodeType == JavaElementType.IMPLEMENTS_LIST || nodeType == JavaElementType.CLASS)) |
| { |
| alignmentStrategyToUse = myAlignmentStrategy; |
| } |
| final Block block = createJavaBlock( |
| child, mySettings, childIndent, arrangeChildWrap(child, defaultWrap), alignmentStrategyToUse, childOffset |
| ); |
| |
| if (childType == JavaElementType.MODIFIER_LIST && containsAnnotations(child)) { |
| myAnnotationWrap = Wrap.createWrap(getWrapType(getAnnotationWrapType(child)), true); |
| } |
| |
| if (block instanceof AbstractJavaBlock) { |
| final AbstractJavaBlock javaBlock = (AbstractJavaBlock)block; |
| if (nodeType == JavaElementType.METHOD_CALL_EXPRESSION && childType == JavaElementType.REFERENCE_EXPRESSION |
| || nodeType == JavaElementType.REFERENCE_EXPRESSION && childType == JavaElementType.METHOD_CALL_EXPRESSION) |
| { |
| javaBlock.setReservedWrap(getReservedWrap(nodeType), nodeType); |
| javaBlock.setReservedWrap(getReservedWrap(childType), childType); |
| } |
| else if (nodeType == JavaElementType.BINARY_EXPRESSION) { |
| javaBlock.setReservedWrap(defaultWrap, nodeType); |
| } |
| else if (childType == JavaElementType.MODIFIER_LIST) { |
| javaBlock.setReservedWrap(myAnnotationWrap, JavaElementType.MODIFIER_LIST); |
| if (!lastChildIsAnnotation(child)) { |
| myAnnotationWrap = null; |
| } |
| } |
| else if (childType == JavaElementType.PARAMETER_LIST && nodeType == JavaElementType.METHOD) { |
| // We prefer wrapping after method annotation to wrapping method parameter list, hence, deliver target wrap object |
| // to child block if necessary. |
| if (!result.isEmpty()) { |
| Block firstChildBlock = result.get(0); |
| if (firstChildBlock instanceof AbstractJavaBlock) { |
| AbstractJavaBlock childJavaBlock = (AbstractJavaBlock)firstChildBlock; |
| if (firstChildIsAnnotation(childJavaBlock.getNode())) { |
| javaBlock.setReservedWrap(childJavaBlock.getReservedWrap(JavaElementType.MODIFIER_LIST), JavaElementType.MODIFIER_LIST); |
| } |
| } |
| } |
| } |
| } |
| |
| result.add(block); |
| } |
| } |
| |
| |
| return child; |
| } |
| |
| private ASTNode processField(@NotNull final List<Block> result, |
| ASTNode child, |
| @NotNull final AlignmentStrategy alignmentStrategy, |
| final Wrap defaultWrap, |
| final Indent childIndent) { |
| ASTNode lastFieldInGroup = findLastFieldInGroup(child); |
| if (lastFieldInGroup == child) { |
| result.add(createJavaBlock(child, getSettings(), childIndent, arrangeChildWrap(child, defaultWrap), alignmentStrategy)); |
| return child; |
| } |
| else { |
| final ArrayList<Block> localResult = new ArrayList<Block>(); |
| while (child != null) { |
| if (!FormatterUtil.containsWhiteSpacesOnly(child)) { |
| localResult.add(createJavaBlock(child, |
| getSettings(), |
| Indent.getContinuationWithoutFirstIndent(myIndentSettings.USE_RELATIVE_INDENTS), |
| arrangeChildWrap(child, defaultWrap), |
| alignmentStrategy) |
| ); |
| } |
| if (child == lastFieldInGroup) break; |
| |
| child = child.getTreeNext(); |
| } |
| if (!localResult.isEmpty()) { |
| result.add(new SyntheticCodeBlock(localResult, null, getSettings(), childIndent, null)); |
| } |
| return lastFieldInGroup; |
| } |
| } |
| |
| /** |
| * Serves for processing composite field definitions as a single formatting block. |
| * <p/> |
| * <code>'Composite field definition'</code> looks like {@code 'int i1, i2 = 2'}. It produces two nodes of type |
| * {@link JavaElementType#FIELD} - {@code 'int i1'} and {@code 'i2 = 2'}. This method returns the second node if the first one |
| * is given (the given node is returned for <code>'single'</code> fields). |
| * |
| * @param child child field node to check |
| * @return last child field node at the field group identified by the given node if any; given child otherwise |
| */ |
| @NotNull |
| private static ASTNode findLastFieldInGroup(@NotNull final ASTNode child) { |
| PsiElement psi = child.getPsi(); |
| if (psi == null) { |
| return child; |
| } |
| final PsiTypeElement typeElement = ((PsiVariable)psi).getTypeElement(); |
| if (typeElement == null) return child; |
| |
| ASTNode lastChildNode = child.getLastChildNode(); |
| if (lastChildNode == null) return child; |
| |
| if (lastChildNode.getElementType() == JavaTokenType.SEMICOLON) return child; |
| |
| ASTNode currentResult = child; |
| ASTNode currentNode = child.getTreeNext(); |
| |
| while (currentNode != null) { |
| if (currentNode.getElementType() == TokenType.WHITE_SPACE |
| || currentNode.getElementType() == JavaTokenType.COMMA |
| || StdTokenSets.COMMENT_BIT_SET.contains(currentNode.getElementType())) { |
| } |
| else if (currentNode.getElementType() == JavaElementType.FIELD) { |
| if (compoundFieldPart(currentNode)) { |
| currentResult = currentNode; |
| } |
| else { |
| return currentResult; |
| } |
| } |
| else { |
| return currentResult; |
| } |
| |
| currentNode = currentNode.getTreeNext(); |
| } |
| return currentResult; |
| } |
| |
| @Nullable |
| private ASTNode processTernaryOperationRange(@NotNull final List<Block> result, |
| @NotNull final ASTNode child, |
| final Wrap defaultWrap, |
| final Indent childIndent) { |
| final ArrayList<Block> localResult = new ArrayList<Block>(); |
| final Wrap wrap = arrangeChildWrap(child, defaultWrap); |
| final Alignment alignment = myReservedAlignment; |
| final Alignment alignment2 = myReservedAlignment2; |
| localResult.add(new LeafBlock(child, wrap, chooseAlignment(alignment, alignment2, child), childIndent)); |
| |
| ASTNode current = child.getTreeNext(); |
| while (current != null) { |
| if (!FormatterUtil.containsWhiteSpacesOnly(current) && current.getTextLength() > 0) { |
| if (isTernaryOperationSign(current)) break; |
| current = processChild(localResult, current, chooseAlignment(alignment, alignment2, current), defaultWrap, childIndent); |
| } |
| if (current != null) { |
| current = current.getTreeNext(); |
| } |
| } |
| |
| result.add(new SyntheticCodeBlock(localResult, chooseAlignment(alignment, alignment2, child), getSettings(), null, wrap)); |
| |
| if (current == null) { |
| return null; |
| } |
| return current.getTreePrev(); |
| } |
| |
| private boolean isTernaryOperationSign(@NotNull final ASTNode child) { |
| if (myNode.getElementType() != JavaElementType.CONDITIONAL_EXPRESSION) return false; |
| final int role = ((CompositeElement)child.getTreeParent()).getChildRole(child); |
| return role == ChildRole.OPERATION_SIGN || role == ChildRole.COLON; |
| } |
| |
| @NotNull |
| private Block createMethodCallExpressionBlock(@NotNull final ASTNode node, final Wrap blockWrap, final Alignment alignment) { |
| final ArrayList<ASTNode> nodes = new ArrayList<ASTNode>(); |
| final ArrayList<Block> subBlocks = new ArrayList<Block>(); |
| collectNodes(nodes, node); |
| |
| final Wrap wrap = Wrap.createWrap(getWrapType(mySettings.METHOD_CALL_CHAIN_WRAP), false); |
| |
| // We use this alignment object to align chained method calls to the first method invocation place if necessary (see IDEA-30369) |
| Alignment chainedCallsAlignment = createAlignment(mySettings.ALIGN_MULTILINE_CHAINED_METHODS, null); |
| |
| // We want to align chained method calls only if method target is explicitly specified, i.e. we don't want to align methods |
| // chain like 'recursive().recursive().recursive()' but want to align calls like 'foo.recursive().recursive().recursive()' |
| boolean callPointDefined = false; |
| List<ASTNode> lookAheadNodes = null; |
| boolean afterIdentifier = false; |
| |
| while (!nodes.isEmpty() || lookAheadNodes != null) { |
| final List<ASTNode> subNodes; |
| if (lookAheadNodes == null) { |
| subNodes = readToNextDot(nodes); |
| } |
| else { |
| subNodes = new ArrayList<ASTNode>(lookAheadNodes); |
| lookAheadNodes = null; |
| } |
| Alignment alignmentToUseForSubBlock = null; |
| |
| // 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(createSyntheticBlock(subNodes, wrap, alignmentToUseForSubBlock)); |
| continue; |
| } |
| |
| IElementType lastNodeType = subNodes.get(subNodes.size() - 1).getElementType(); |
| boolean currentSubBlockIsMethodCall = lastNodeType == JavaElementType.EXPRESSION_LIST; |
| |
| // Update information about chained method alignment point if necessary. I.e. we want to align only continuous method calls |
| // like 'foo.bar().bar().bar()' but not 'foo.bar().foo.bar()' |
| if (callPointDefined && !currentSubBlockIsMethodCall) { |
| chainedCallsAlignment = createAlignment(mySettings.ALIGN_MULTILINE_CHAINED_METHODS, null); |
| } |
| callPointDefined |= !currentSubBlockIsMethodCall; |
| |
| // We want to align method call only if call target is defined for the first chained method and current block is a method call. |
| if (callPointDefined && currentSubBlockIsMethodCall) { |
| alignmentToUseForSubBlock = chainedCallsAlignment; |
| } |
| else if (afterIdentifier && lastNodeType == JavaTokenType.IDENTIFIER) { |
| // Align method call to the last field access. Example: |
| // MyClass.staticField |
| // .foo(); |
| lookAheadNodes = readToNextDot(nodes); |
| if (!lookAheadNodes.isEmpty() && lookAheadNodes.get(lookAheadNodes.size() - 1).getElementType() == JavaElementType.EXPRESSION_LIST) { |
| alignmentToUseForSubBlock = chainedCallsAlignment; |
| } |
| } |
| afterIdentifier = lastNodeType == JavaTokenType.IDENTIFIER; |
| subBlocks.add(createSyntheticBlock(subNodes, wrap, alignmentToUseForSubBlock)); |
| } |
| |
| return new SyntheticCodeBlock(subBlocks, alignment, mySettings, |
| Indent.getContinuationWithoutFirstIndent(myIndentSettings.USE_RELATIVE_INDENTS), blockWrap); |
| } |
| |
| @NotNull |
| private Block createSyntheticBlock(@NotNull final List<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(createSyntheticBlock(subNodes, wrap, null)); |
| } |
| return new SyntheticCodeBlock(subBlocks, alignment, mySettings, |
| Indent.getContinuationIndent(myIndentSettings.USE_RELATIVE_INDENTS), wrap); |
| } |
| return new SyntheticCodeBlock(createJavaBlocks(subNodes), alignment, mySettings, |
| Indent.getContinuationWithoutFirstIndent(myIndentSettings.USE_RELATIVE_INDENTS), null); |
| } |
| |
| @NotNull |
| private List<Block> createJavaBlocks(@NotNull final List<ASTNode> subNodes) { |
| final ArrayList<Block> result = new ArrayList<Block>(); |
| for (ASTNode node : subNodes) { |
| result.add(createJavaBlock(node, getSettings(), Indent.getContinuationWithoutFirstIndent(myIndentSettings.USE_RELATIVE_INDENTS), null, |
| AlignmentStrategy.getNullStrategy())); |
| } |
| return result; |
| } |
| |
| @NotNull |
| private static List<ASTNode> readToNextDot(@NotNull List<ASTNode> nodes) { |
| final ArrayList<ASTNode> result = new ArrayList<ASTNode>(); |
| result.add(nodes.remove(0)); |
| for (Iterator<ASTNode> iterator = nodes.iterator(); iterator.hasNext();) { |
| ASTNode node = iterator.next(); |
| if (node.getElementType() == JavaTokenType.DOT) break; |
| result.add(node); |
| iterator.remove(); |
| } |
| return result; |
| } |
| |
| private static void collectNodes(@NotNull List<ASTNode> nodes, @NotNull ASTNode node) { |
| ASTNode child = node.getFirstChildNode(); |
| while (child != null) { |
| if (!FormatterUtil.containsWhiteSpacesOnly(child)) { |
| if (child.getElementType() == JavaElementType.METHOD_CALL_EXPRESSION || child.getElementType() == |
| JavaElementType |
| .REFERENCE_EXPRESSION) { |
| collectNodes(nodes, child); |
| } |
| else { |
| nodes.add(child); |
| } |
| } |
| child = child.getTreeNext(); |
| } |
| } |
| |
| private static boolean firstChildIsAnnotation(@NotNull final ASTNode child) { |
| ASTNode current = child.getFirstChildNode(); |
| while (current != null && current.getElementType() == TokenType.WHITE_SPACE) { |
| current = current.getTreeNext(); |
| } |
| return current != null && current.getElementType() == JavaElementType.ANNOTATION; |
| } |
| |
| private static boolean lastChildIsAnnotation(@NotNull final ASTNode child) { |
| ASTNode current = child.getLastChildNode(); |
| while (current != null && current.getElementType() == TokenType.WHITE_SPACE) { |
| current = current.getTreePrev(); |
| } |
| return current != null && current.getElementType() == JavaElementType.ANNOTATION; |
| } |
| |
| private static boolean containsAnnotations(@NotNull final ASTNode child) { |
| PsiElement psi = child.getPsi(); |
| return psi instanceof PsiModifierList && ((PsiModifierList)psi).getAnnotations().length > 0; |
| } |
| |
| private int getAnnotationWrapType(@NotNull ASTNode child) { |
| final IElementType nodeType = myNode.getElementType(); |
| if (nodeType == JavaElementType.METHOD) { |
| return mySettings.METHOD_ANNOTATION_WRAP; |
| } |
| 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 javadoc' processing. Example: |
| // class Test { |
| // @NotNull Object |
| // } |
| // Here '@NotNull' has a 'class' node as a parent but we want to use field annotation setting value. Hence, we check if subsequent |
| // parsed info is valid. |
| for (ASTNode node = child.getTreeNext(); node != null; node = node.getTreeNext()) { |
| if (TokenType.WHITE_SPACE == node.getElementType() || node instanceof PsiTypeElement) { |
| continue; |
| } |
| if (node instanceof PsiErrorElement) { |
| return mySettings.FIELD_ANNOTATION_WRAP; |
| } |
| } |
| return mySettings.CLASS_ANNOTATION_WRAP; |
| } |
| if (nodeType == JavaElementType.FIELD) { |
| return mySettings.FIELD_ANNOTATION_WRAP; |
| } |
| if (nodeType == JavaElementType.PARAMETER) { |
| return mySettings.PARAMETER_ANNOTATION_WRAP; |
| } |
| if (nodeType == JavaElementType.LOCAL_VARIABLE) { |
| return mySettings.VARIABLE_ANNOTATION_WRAP; |
| } |
| return CommonCodeStyleSettings.DO_NOT_WRAP; |
| } |
| |
| @Nullable |
| private Alignment arrangeChildAlignment(@NotNull final ASTNode child, @NotNull final AlignmentStrategy alignmentStrategy) { |
| int role = getChildRole(child); |
| final IElementType nodeType = myNode.getElementType(); |
| Alignment defaultAlignment = alignmentStrategy.getAlignment(child.getElementType()); |
| |
| if (nodeType == JavaElementType.FOR_STATEMENT) { |
| if (role == ChildRole.FOR_INITIALIZATION || role == ChildRole.CONDITION || role == ChildRole.FOR_UPDATE) { |
| return defaultAlignment; |
| } |
| return null; |
| } |
| else if (nodeType == JavaElementType.EXTENDS_LIST || nodeType == JavaElementType.IMPLEMENTS_LIST) { |
| if (role == ChildRole.REFERENCE_IN_LIST || role == ChildRole.IMPLEMENTS_KEYWORD) { |
| return defaultAlignment; |
| } |
| return null; |
| } |
| else if (nodeType == JavaElementType.THROWS_LIST) { |
| if (role == ChildRole.REFERENCE_IN_LIST) { |
| return defaultAlignment; |
| } |
| return null; |
| } |
| else if (nodeType == JavaElementType.CLASS) { |
| if (role == ChildRole.CLASS_OR_INTERFACE_KEYWORD) return defaultAlignment; |
| if (myIsAfterClassKeyword) return null; |
| if (role == ChildRole.MODIFIER_LIST) return defaultAlignment; |
| return null; |
| } |
| else if (ALIGN_IN_COLUMNS_ELEMENT_TYPES.contains(nodeType)) { |
| return getVariableDeclarationSubElementAlignment(child); |
| } |
| else if (nodeType == JavaElementType.METHOD) { |
| if (role == ChildRole.MODIFIER_LIST) return defaultAlignment; |
| if (role == ChildRole.TYPE_PARAMETER_LIST) return defaultAlignment; |
| if (role == ChildRole.TYPE) return defaultAlignment; |
| if (role == ChildRole.NAME) return defaultAlignment; |
| if (role == ChildRole.THROWS_LIST && mySettings.ALIGN_THROWS_KEYWORD) return defaultAlignment; |
| return null; |
| } |
| |
| else if (nodeType == JavaElementType.ASSIGNMENT_EXPRESSION) { |
| if (role == ChildRole.LOPERAND) return defaultAlignment; |
| if (role == ChildRole.ROPERAND && child.getElementType() == JavaElementType.ASSIGNMENT_EXPRESSION) { |
| return defaultAlignment; |
| } |
| return null; |
| } |
| |
| else if (child.getElementType() == JavaTokenType.END_OF_LINE_COMMENT) { |
| ASTNode previous = child.getTreePrev(); |
| // There is a special case - comment block that is located at the very start of the line. We don't reformat such a blocks, |
| // hence, no alignment should be applied to them in order to avoid subsequent blocks aligned with the same alignment to |
| // be located at the left editor edge as well. |
| CharSequence prevChars; |
| if (previous != null && previous.getElementType() == TokenType.WHITE_SPACE && (prevChars = previous.getChars()).length() > 0 |
| && prevChars.charAt(prevChars.length() - 1) == '\n') { |
| return null; |
| } |
| return defaultAlignment; |
| } |
| |
| else if (nodeType == JavaElementType.MODIFIER_LIST) { |
| // There is a possible case that modifier list contains from more than one elements, e.g. 'private final'. It's also possible |
| // that the list is aligned. We want to apply alignment rule only to the first element then. |
| ASTNode previous = child.getTreePrev(); |
| if (previous == null || previous.getTreeParent() != myNode) { |
| return defaultAlignment; |
| } |
| return null; |
| } |
| |
| else { |
| return defaultAlignment; |
| } |
| } |
| |
| private static int getChildRole(@NotNull ASTNode child) { |
| return ((CompositeElement)child.getTreeParent()).getChildRole(child); |
| } |
| |
| /** |
| * Encapsulates alignment retrieval logic for variable declaration use-case assuming that given node is a child node |
| * of basic variable declaration node. |
| * |
| * @param child variable declaration child node which alignment is to be defined |
| * @return alignment to use for the given node |
| * @see CodeStyleSettings#ALIGN_GROUP_FIELD_DECLARATIONS |
| */ |
| @Nullable |
| private Alignment getVariableDeclarationSubElementAlignment(@NotNull ASTNode child) { |
| // The whole idea of variable declarations alignment is that complete declaration blocks which children are to be aligned hold |
| // reference to the same AlignmentStrategy object, hence, reuse the same Alignment objects. So, there is no point in checking |
| // if it's necessary to align sub-blocks if shared strategy is not defined. |
| if (!mySettings.ALIGN_GROUP_FIELD_DECLARATIONS) { |
| return null; |
| } |
| |
| IElementType childType = child.getElementType(); |
| |
| // We don't want to align subsequent identifiers in single-line declarations like 'int i1, i2, i3'. I.e. only 'i1' |
| // should be aligned then. |
| ASTNode previousNode = FormatterUtil.getPreviousNonWhitespaceSibling(child); |
| if (childType == JavaTokenType.IDENTIFIER && (previousNode == null || previousNode.getElementType() == JavaTokenType.COMMA)) { |
| return null; |
| } |
| |
| return myAlignmentStrategy.getAlignment(childType); |
| } |
| |
| /* |
| private boolean isAfterClassKeyword(final ASTNode child) { |
| ASTNode treePrev = child.getTreePrev(); |
| while (treePrev != null) { |
| if (treePrev.getElementType() == ElementType.CLASS_KEYWORD || |
| treePrev.getElementType() == ElementType.INTERFACE_KEYWORD) { |
| return true; |
| } |
| treePrev = treePrev.getTreePrev(); |
| } |
| return false; |
| } |
| |
| */ |
| |
| @Nullable |
| private static Alignment createAlignment(final boolean alignOption, @Nullable final Alignment defaultAlignment) { |
| return alignOption ? createAlignmentOrDefault(null, defaultAlignment) : defaultAlignment; |
| } |
| |
| @Nullable |
| private static Alignment createAlignment(Alignment base, final boolean alignOption, @Nullable final Alignment defaultAlignment) { |
| return alignOption ? createAlignmentOrDefault(base, defaultAlignment) : defaultAlignment; |
| } |
| |
| @Nullable |
| protected Wrap arrangeChildWrap(final ASTNode child, Wrap defaultWrap) { |
| if (myAnnotationWrap != null) { |
| try { |
| return myAnnotationWrap; |
| } |
| finally { |
| myAnnotationWrap = null; |
| } |
| } |
| return myWrapManager.arrangeChildWrap(child, myNode, getSettings(), defaultWrap, this); |
| } |
| |
| @NotNull |
| private static WrapType getWrapType(final int wrap) { |
| switch (wrap) { |
| case CommonCodeStyleSettings.WRAP_ALWAYS: |
| return WrapType.ALWAYS; |
| case CommonCodeStyleSettings.WRAP_AS_NEEDED: |
| return WrapType.NORMAL; |
| case CommonCodeStyleSettings.DO_NOT_WRAP: |
| return WrapType.NONE; |
| default: |
| return WrapType.CHOP_DOWN_IF_LONG; |
| } |
| } |
| |
| @NotNull |
| private ASTNode processParenthesisBlock(@NotNull List<Block> result, |
| @NotNull ASTNode child, |
| @NotNull WrappingStrategy wrappingStrategy, |
| final boolean doAlign) { |
| |
| myUseChildAttributes = true; |
| |
| final IElementType from = JavaTokenType.LPARENTH; |
| final IElementType to = JavaTokenType.RPARENTH; |
| |
| return processParenthesisBlock(from, to, result, child, wrappingStrategy, doAlign); |
| } |
| |
| |
| @NotNull |
| private ASTNode processParenthesisBlock(@NotNull IElementType from, |
| @Nullable final IElementType to, |
| @NotNull final List<Block> result, |
| @NotNull ASTNode child, |
| @NotNull final WrappingStrategy wrappingStrategy, |
| final boolean doAlign) { |
| final Indent externalIndent = Indent.getNoneIndent(); |
| final Indent internalIndent = Indent.getContinuationWithoutFirstIndent(myIndentSettings.USE_RELATIVE_INDENTS); |
| final Indent internalIndentEnforcedToChildren = Indent.getIndent(Indent.Type.CONTINUATION, myIndentSettings.USE_RELATIVE_INDENTS, true); |
| AlignmentStrategy alignmentStrategy = AlignmentStrategy.wrap(createAlignment(doAlign, null), JavaTokenType.COMMA); |
| setChildIndent(internalIndent); |
| setChildAlignment(alignmentStrategy.getAlignment(null)); |
| boolean methodParametersBlock = true; |
| ASTNode lBracketParent = child.getTreeParent(); |
| if (lBracketParent != null) { |
| ASTNode methodCandidate = lBracketParent.getTreeParent(); |
| methodParametersBlock = methodCandidate != null && (methodCandidate.getElementType() == JavaElementType.METHOD |
| || 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; |
| |
| ASTNode prev = child; |
| boolean afterAnonymousClass = false; |
| while (child != null) { |
| isAfterIncomplete = isAfterIncomplete || child.getElementType() == TokenType.ERROR_ELEMENT || |
| child.getElementType() == JavaElementType.EMPTY_EXPRESSION; |
| if (!FormatterUtil.containsWhiteSpacesOnly(child) && child.getTextLength() > 0) { |
| if (child.getElementType() == from) { |
| result.add(createJavaBlock(child, mySettings, externalIndent, null, bracketAlignment)); |
| } |
| else if (child.getElementType() == to) { |
| result.add(createJavaBlock(child, mySettings, |
| isAfterIncomplete && !afterAnonymousClass ? internalIndent : externalIndent, |
| null, |
| isAfterIncomplete ? alignmentStrategy.getAlignment(null) : bracketAlignment)); |
| return child; |
| } |
| else { |
| final IElementType elementType = child.getElementType(); |
| 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; |
| } |
| } |
| isAfterIncomplete = false; |
| if (child.getElementType() != JavaTokenType.COMMA) { |
| afterAnonymousClass = isAnonymousClass(child); |
| } |
| } |
| prev = child; |
| child = child.getTreeNext(); |
| } |
| |
| return prev; |
| } |
| |
| 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; |
| } |
| ASTNode parent = myNode.getTreeParent(); |
| if (parent == null || parent.getElementType() != JavaElementType.METHOD_CALL_EXPRESSION) { |
| return false; |
| } |
| if (!isAnonymousClass(node) || !JavaFormatterUtil.hasAnonymousClassesArguments((PsiExpressionList)myNode.getPsi(), 2)) { |
| return false; |
| } |
| |
| // Enforce indent only if anonymous class instance expression doesn't start new line and have anonymous class expression sibling. |
| ASTNode prev = node.getTreePrev(); |
| return prev != null && !(StringUtil.containsLineBreak(prev.getChars()) && prev.getElementType() != TokenType.WHITE_SPACE); |
| } |
| |
| private static boolean isAnonymousClass(@Nullable ASTNode node) { |
| if (node == null || node.getElementType() != JavaElementType.NEW_EXPRESSION) { |
| return false; |
| } |
| ASTNode lastChild = node.getLastChildNode(); |
| return lastChild != null && lastChild.getElementType() == JavaElementType.ANONYMOUS_CLASS; |
| } |
| |
| @Nullable |
| private ASTNode processEnumBlock(@NotNull List<Block> result, |
| @Nullable ASTNode child, |
| ASTNode last) |
| { |
| final WrappingStrategy wrappingStrategy = WrappingStrategy.createDoNotWrapCommaStrategy(Wrap |
| .createWrap(getWrapType(mySettings.ENUM_CONSTANTS_WRAP), true)); |
| while (child != null) { |
| if (!FormatterUtil.containsWhiteSpacesOnly(child) && child.getTextLength() > 0) { |
| result.add(createJavaBlock(child, mySettings, Indent.getNormalIndent(), |
| wrappingStrategy.getWrap(child.getElementType()), AlignmentStrategy.getNullStrategy())); |
| if (child == last) return child; |
| } |
| child = child.getTreeNext(); |
| } |
| return null; |
| } |
| |
| private void setChildAlignment(final Alignment alignment) { |
| myChildAlignment = alignment; |
| } |
| |
| private void setChildIndent(final Indent internalIndent) { |
| myChildIndent = internalIndent; |
| } |
| |
| @Nullable |
| private static Alignment createAlignmentOrDefault(@Nullable Alignment base, @Nullable final Alignment defaultAlignment) { |
| if (defaultAlignment == null) { |
| return base == null ? Alignment.createAlignment() : Alignment.createChildAlignment(base); |
| } |
| return defaultAlignment; |
| } |
| |
| private int getBraceStyle() { |
| final PsiElement psiNode = SourceTreeToPsiMap.treeElementToPsi(myNode); |
| if (psiNode instanceof PsiClass) { |
| return mySettings.CLASS_BRACE_STYLE; |
| } |
| if (psiNode instanceof PsiMethod |
| || psiNode instanceof PsiCodeBlock && psiNode.getParent() != null && psiNode.getParent() instanceof PsiMethod) { |
| return mySettings.METHOD_BRACE_STYLE; |
| } |
| return mySettings.BRACE_STYLE; |
| } |
| |
| protected Indent getCodeBlockInternalIndent(final int baseChildrenIndent) { |
| return getCodeBlockInternalIndent(baseChildrenIndent, false); |
| } |
| |
| protected Indent getCodeBlockInternalIndent(final int baseChildrenIndent, boolean enforceParentIndent) { |
| if (isTopLevelClass() && mySettings.DO_NOT_INDENT_TOP_LEVEL_CLASS_MEMBERS) { |
| return Indent.getNoneIndent(); |
| } |
| |
| final int braceStyle = getBraceStyle(); |
| return braceStyle == CommonCodeStyleSettings.NEXT_LINE_SHIFTED ? |
| createNormalIndent(baseChildrenIndent - 1, enforceParentIndent) |
| : createNormalIndent(baseChildrenIndent, enforceParentIndent); |
| } |
| |
| protected static Indent createNormalIndent(final int baseChildrenIndent) { |
| return createNormalIndent(baseChildrenIndent, false); |
| } |
| |
| protected static Indent createNormalIndent(final int baseChildrenIndent, boolean enforceIndentToChildren) { |
| if (baseChildrenIndent == 1) { |
| return Indent.getIndent(Indent.Type.NORMAL, false, enforceIndentToChildren); |
| } |
| else if (baseChildrenIndent <= 0) { |
| return Indent.getNoneIndent(); |
| } |
| else { |
| LOG.assertTrue(false); |
| return Indent.getIndent(Indent.Type.NORMAL, false, enforceIndentToChildren); |
| } |
| } |
| |
| private boolean isTopLevelClass() { |
| return myNode.getElementType() == JavaElementType.CLASS && |
| SourceTreeToPsiMap.treeElementToPsi(myNode.getTreeParent()) instanceof PsiFile; |
| } |
| |
| protected Indent getCodeBlockExternalIndent() { |
| final int braceStyle = getBraceStyle(); |
| if (braceStyle == CommonCodeStyleSettings.END_OF_LINE || braceStyle == CommonCodeStyleSettings.NEXT_LINE || |
| braceStyle == CommonCodeStyleSettings.NEXT_LINE_IF_WRAPPED) { |
| return Indent.getNoneIndent(); |
| } |
| return Indent.getNormalIndent(); |
| } |
| |
| protected Indent getCodeBlockChildExternalIndent(final int newChildIndex) { |
| final int braceStyle = getBraceStyle(); |
| if (!isAfterCodeBlock(newChildIndex)) { |
| return Indent.getNormalIndent(); |
| } |
| if (braceStyle == CommonCodeStyleSettings.NEXT_LINE || |
| braceStyle == CommonCodeStyleSettings.NEXT_LINE_IF_WRAPPED || |
| braceStyle == CommonCodeStyleSettings.END_OF_LINE) { |
| return Indent.getNoneIndent(); |
| } |
| return Indent.getNormalIndent(); |
| } |
| |
| private boolean isAfterCodeBlock(final int newChildIndex) { |
| if (newChildIndex == 0) return false; |
| Block blockBefore = getSubBlocks().get(newChildIndex - 1); |
| return blockBefore instanceof CodeBlockBlock; |
| } |
| |
| /** |
| * <b>Note:</b> this method is considered to be a legacy heritage and is assumed to be removed as soon as formatting processing |
| * is refactored |
| * |
| * @param elementType target element type |
| * @return <code>null</code> all the time |
| */ |
| @Nullable |
| @Override |
| public Wrap getReservedWrap(IElementType elementType) { |
| return null; |
| } |
| |
| /** |
| * Defines contract for associating operation type and particular wrap instance. I.e. given wrap object <b>may</b> be returned |
| * from subsequent {@link #getReservedWrap(IElementType)} call if given operation type is used as an argument there. |
| * <p/> |
| * Default implementation ({@link AbstractJavaBlock#setReservedWrap(Wrap, IElementType)}) does nothing. |
| * <p/> |
| * <b>Note:</b> this method is considered to be a legacy heritage and is assumed to be removed as soon as formatting processing |
| * is refactored |
| * |
| * @param reservedWrap reserved wrap instance |
| * @param operationType target operation type to associate with the given wrap instance |
| */ |
| protected void setReservedWrap(final Wrap reservedWrap, final IElementType operationType) { |
| } |
| |
| @Nullable |
| protected static ASTNode getTreeNode(final Block child2) { |
| if (child2 instanceof JavaBlock) { |
| return ((JavaBlock)child2).getFirstTreeNode(); |
| } |
| if (child2 instanceof LeafBlock) { |
| return ((LeafBlock)child2).getTreeNode(); |
| } |
| return null; |
| } |
| |
| @Override |
| @NotNull |
| public ChildAttributes getChildAttributes(final int newChildIndex) { |
| if (myUseChildAttributes) { |
| return new ChildAttributes(myChildIndent, myChildAlignment); |
| } |
| if (isAfter(newChildIndex, new IElementType[]{JavaDocElementType.DOC_COMMENT})) { |
| return new ChildAttributes(Indent.getNoneIndent(), myChildAlignment); |
| } |
| return super.getChildAttributes(newChildIndex); |
| } |
| |
| @Override |
| @Nullable |
| protected Indent getChildIndent() { |
| return getChildIndent(myNode, myIndentSettings); |
| } |
| |
| @NotNull |
| public CommonCodeStyleSettings getSettings() { |
| return mySettings; |
| } |
| |
| protected boolean isAfter(final int newChildIndex, @NotNull final IElementType[] elementTypes) { |
| if (newChildIndex == 0) return false; |
| final Block previousBlock = getSubBlocks().get(newChildIndex - 1); |
| if (!(previousBlock instanceof AbstractBlock)) return false; |
| final IElementType previousElementType = ((AbstractBlock)previousBlock).getNode().getElementType(); |
| for (IElementType elementType : elementTypes) { |
| if (previousElementType == elementType) return true; |
| } |
| return false; |
| } |
| |
| @Nullable |
| protected Alignment getUsedAlignment(final int newChildIndex) { |
| final List<Block> subBlocks = getSubBlocks(); |
| for (int i = 0; i < newChildIndex; i++) { |
| if (i >= subBlocks.size()) return null; |
| final Block block = subBlocks.get(i); |
| final Alignment alignment = block.getAlignment(); |
| if (alignment != null) return alignment; |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean isLeaf() { |
| return ShiftIndentInsideHelper.mayShiftIndentInside(myNode); |
| } |
| |
| @Nullable |
| protected ASTNode composeCodeBlock(@NotNull final List<Block> result, |
| ASTNode child, |
| final Indent indent, |
| final int childrenIndent, |
| @Nullable final Wrap childWrap) { |
| final ArrayList<Block> localResult = new ArrayList<Block>(); |
| processChild(localResult, child, AlignmentStrategy.getNullStrategy(), null, Indent.getNoneIndent()); |
| child = child.getTreeNext(); |
| |
| AlignmentStrategy varDeclarationAlignmentStrategy |
| = AlignmentStrategy.createAlignmentPerTypeStrategy(VAR_DECLARATION_ELEMENT_TYPES_TO_ALIGN, JavaElementType.FIELD, true); |
| |
| while (child != null) { |
| // We consider that subsequent fields shouldn't be aligned if they are separated by blank line(s). |
| if (!FormatterUtil.containsWhiteSpacesOnly(child)) { |
| if (!ElementType.JAVA_COMMENT_BIT_SET.contains(child.getElementType()) && !shouldUseVarDeclarationAlignment(child)) { |
| // Reset var declaration alignment. |
| varDeclarationAlignmentStrategy = AlignmentStrategy.createAlignmentPerTypeStrategy( |
| VAR_DECLARATION_ELEMENT_TYPES_TO_ALIGN, JavaElementType.FIELD, true |
| ); |
| } |
| final boolean rBrace = isRBrace(child); |
| Indent childIndent = rBrace ? Indent.getNoneIndent() : getCodeBlockInternalIndent(childrenIndent, false); |
| if (!rBrace && child.getElementType() == JavaElementType.CODE_BLOCK |
| && (getBraceStyle() == CommonCodeStyleSettings.NEXT_LINE_SHIFTED |
| || getBraceStyle() == CommonCodeStyleSettings.NEXT_LINE_SHIFTED2)) |
| { |
| childIndent = Indent.getNormalIndent(); |
| } |
| AlignmentStrategy alignmentStrategyToUse = ALIGN_IN_COLUMNS_ELEMENT_TYPES.contains(child.getElementType()) |
| ? varDeclarationAlignmentStrategy : AlignmentStrategy.getNullStrategy(); |
| child = processChild(localResult, child, alignmentStrategyToUse, childWrap, childIndent); |
| if (rBrace) { |
| result.add(createCodeBlockBlock(localResult, indent, childrenIndent)); |
| return child; |
| } |
| } |
| |
| if (child != null) { |
| child = child.getTreeNext(); |
| } |
| } |
| result.add(createCodeBlockBlock(localResult, indent, childrenIndent)); |
| return null; |
| } |
| |
| /** |
| * Allows to answer if special 'variable declaration alignment' strategy should be used for the given node. |
| * I.e. given node is supposed to be parent node of sub-nodes that should be aligned 'by-columns'. |
| * <p/> |
| * The main idea of that strategy is to provide alignment in columns like the one below: |
| * <pre> |
| * public int i = 1; |
| * public double ddd = 2; |
| * </pre> |
| * |
| * @param node node |
| * @return |
| */ |
| protected boolean shouldUseVarDeclarationAlignment(@NotNull ASTNode node) { |
| return mySettings.ALIGN_GROUP_FIELD_DECLARATIONS && ALIGN_IN_COLUMNS_ELEMENT_TYPES.contains(node.getElementType()) |
| && (!myAlignmentInColumnsHelper.useDifferentVarDeclarationAlignment( |
| node, ALIGNMENT_IN_COLUMNS_CONFIG, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS) |
| || compoundFieldPart(node)); |
| } |
| |
| /** |
| * Allows to answer if given node corresponds to part of composite field definition. Example: |
| * <p/> |
| * <pre> |
| * int i1, i2 = 2; |
| * </pre> |
| * <p/> |
| * Parsing such a code produces two fields - {@code 'int i1'} and {@code 'i2 = 2'}. This method returns <code>true</code> |
| * for the second one. |
| * |
| * @param node node to check |
| * @return <code>true</code> if given node is a non-first part of composite field definition; <code>false</code> otherwise |
| */ |
| protected static boolean compoundFieldPart(@NotNull ASTNode node) { |
| if (node.getElementType() != JavaElementType.FIELD) { |
| return false; |
| } |
| ASTNode firstChild = node.getFirstChildNode(); |
| if (firstChild == null || firstChild.getElementType() != JavaTokenType.IDENTIFIER) { |
| return false; |
| } |
| |
| ASTNode prev = node.getTreePrev(); |
| return prev == null || !JavaJspElementType.WHITE_SPACE_BIT_SET.contains(prev.getElementType()) |
| || StringUtil.countNewLines(prev.getChars()) <= 1; |
| } |
| |
| @NotNull |
| public SyntheticCodeBlock createCodeBlockBlock(final List<Block> localResult, final Indent indent, final int childrenIndent) { |
| final SyntheticCodeBlock result = new SyntheticCodeBlock(localResult, null, getSettings(), indent, null); |
| result.setChildAttributes(new ChildAttributes(getCodeBlockInternalIndent(childrenIndent), null)); |
| return result; |
| } |
| } |