/*
 * Copyright 2000-2017 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.AlignmentStrategy;
import com.intellij.formatting.blocks.CStyleCommentBlock;
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.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
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.util.containers.ContainerUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.intellij.psi.formatter.java.JavaFormatterUtil.getWrapType;
import static com.intellij.psi.formatter.java.MultipleFieldDeclarationHelper.findLastFieldInGroup;

public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlock, ReservedWrapsProvider {
  private static final Logger LOG = Logger.getInstance("#com.intellij.psi.formatter.java.AbstractJavaBlock");

  @NotNull protected final CommonCodeStyleSettings mySettings;
  @NotNull protected final JavaCodeStyleSettings myJavaSettings;
  protected final CommonCodeStyleSettings.IndentOptions myIndentSettings;
  private final Indent myIndent;
  protected Indent myChildIndent;
  protected Alignment myChildAlignment;
  protected boolean myUseChildAttributes;
  @NotNull protected final AlignmentStrategy myAlignmentStrategy;
  private boolean myIsAfterClassKeyword;

  protected Alignment myReservedAlignment;
  protected Alignment myReservedAlignment2;

  private final JavaWrapManager myWrapManager;
  private Map<IElementType, Wrap> myPreferredWraps;
  private AbstractJavaBlock myParentBlock;

  private final FormattingMode myFormattingMode;

  private final BlockFactory myBlockFactory = new BlockFactory() {
    @Override
    public Block createBlock(ASTNode node, Indent indent, Alignment alignment, Wrap wrap, @NotNull FormattingMode formattingMode) {
      return new SimpleJavaBlock(node, wrap, AlignmentStrategy.wrap(alignment), indent, mySettings, myJavaSettings, formattingMode);
    }

    @Override
    public CommonCodeStyleSettings getSettings() {
      return mySettings;
    }

    @Override
    public JavaCodeStyleSettings getJavaSettings() {
      return myJavaSettings;
    }

    @Override
    public FormattingMode getFormattingMode() {
      return myFormattingMode;
    }
  };

  protected AbstractJavaBlock(@NotNull final ASTNode node,
                              final Wrap wrap,
                              final Alignment alignment,
                              final Indent indent,
                              @NotNull final CommonCodeStyleSettings settings,
                              @NotNull JavaCodeStyleSettings javaSettings,
                              @NotNull FormattingMode formattingMode)
  {
    this(node, wrap, indent, settings, javaSettings, JavaWrapManager.INSTANCE, AlignmentStrategy.wrap(alignment), formattingMode);
  }

  protected AbstractJavaBlock(@NotNull final ASTNode node,
                              final Wrap wrap,
                              @NotNull final AlignmentStrategy alignmentStrategy,
                              final Indent indent,
                              @NotNull final CommonCodeStyleSettings settings,
                              @NotNull JavaCodeStyleSettings javaSettings,
                              @NotNull FormattingMode formattingMode)
  {
    this(node, wrap, indent, settings, javaSettings, JavaWrapManager.INSTANCE, alignmentStrategy, formattingMode);
  }

  private AbstractJavaBlock(@NotNull ASTNode ignored,
                            @NotNull CommonCodeStyleSettings commonSettings,
                            @NotNull JavaCodeStyleSettings javaSettings,
                            @NotNull FormattingMode formattingMode) {
    super(ignored, null, null);
    mySettings = commonSettings;
    myJavaSettings = javaSettings;
    myIndentSettings = commonSettings.getIndentOptions();
    myIndent = null;
    myWrapManager = JavaWrapManager.INSTANCE;
    myAlignmentStrategy = AlignmentStrategy.getNullStrategy();
    myFormattingMode = formattingMode;
  }

  protected AbstractJavaBlock(@NotNull final ASTNode node,
                              final Wrap wrap,
                              final Indent indent,
                              @NotNull final CommonCodeStyleSettings settings,
                              @NotNull JavaCodeStyleSettings javaSettings,
                              final JavaWrapManager wrapManager,
                              @NotNull final AlignmentStrategy alignmentStrategy,
                              @NotNull final FormattingMode formattingMode) {
    super(node, wrap, createBlockAlignment(alignmentStrategy, node));
    mySettings = settings;
    myJavaSettings = javaSettings;
    myIndentSettings = settings.getIndentOptions();
    myIndent = indent;
    myWrapManager = wrapManager;
    myAlignmentStrategy = alignmentStrategy;
    myFormattingMode = formattingMode;
  }

  @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 Block createJavaBlock(@NotNull ASTNode child,
                               @NotNull CommonCodeStyleSettings settings,
                               @NotNull JavaCodeStyleSettings javaSettings,
                               @Nullable Indent indent,
                               @Nullable Wrap wrap,
                               Alignment alignment,
                               @NotNull FormattingMode formattingMode) {
    return createJavaBlock(child, settings, javaSettings,indent, wrap, AlignmentStrategy.wrap(alignment), formattingMode);
  }

  @NotNull
  public Block createJavaBlock(@NotNull ASTNode child,
                               @NotNull CommonCodeStyleSettings settings,
                               @NotNull JavaCodeStyleSettings javaSettings,
                               final Indent indent,
                               @Nullable Wrap wrap,
                               @NotNull AlignmentStrategy alignmentStrategy,
                               @NotNull FormattingMode formattingMode) {
    return createJavaBlock(child, settings, javaSettings, indent, wrap, alignmentStrategy, -1, formattingMode);
  }

  @NotNull
  private Block createJavaBlock(@NotNull ASTNode child,
                                @NotNull CommonCodeStyleSettings settings,
                                @NotNull JavaCodeStyleSettings javaSettings,
                                @Nullable Indent indent,
                                Wrap wrap,
                                @NotNull AlignmentStrategy alignmentStrategy,
                                int startOffset,
                                @NotNull FormattingMode formattingMode) {
    Indent actualIndent = indent == null ? getDefaultSubtreeIndent(child, getJavaIndentOptions(settings)) : indent;
    IElementType elementType = child.getElementType();
    Alignment alignment = alignmentStrategy.getAlignment(elementType);
    PsiElement childPsi = child.getPsi();

    if (childPsi 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);
      TextRange range = new TextRange(start + child.getStartOffset(), end + child.getStartOffset());
      return new PartialWhitespaceBlock(child, range, wrap, alignment, actualIndent, settings, javaSettings, myFormattingMode);
    }

    if (childPsi instanceof PsiClass || childPsi instanceof PsiJavaModule) {
      return new CodeBlockBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
    }
    if (child.getElementType() == JavaElementType.METHOD) {
      return new BlockContainingJavaBlock(child, actualIndent, alignmentStrategy, mySettings, myJavaSettings, formattingMode);
    }
    if (isBlockType(elementType)) {
      return new BlockContainingJavaBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
    }
    if (isStatement(child, child.getTreeParent())) {
      return new CodeBlockBlock(child, wrap, alignment, actualIndent, settings, javaSettings, myFormattingMode);
    }
    if (!isBuildIndentsOnly() &&
        child instanceof PsiComment &&
        child instanceof PsiLanguageInjectionHost &&
        InjectedLanguageUtil.hasInjections((PsiLanguageInjectionHost)child)) {
      return new CommentWithInjectionBlock(child, wrap, alignment, indent, settings, javaSettings, formattingMode);
    }
    if (child instanceof LeafElement || childPsi instanceof PsiJavaModuleReferenceElement) {
      if (child.getElementType() == JavaTokenType.C_STYLE_COMMENT) {
        return new CStyleCommentBlock(child, actualIndent);
      }
      final LeafBlock block = new LeafBlock(child, wrap, alignment, actualIndent);
      block.setStartOffset(startOffset);
      return block;
    }
    if (isLikeExtendsList(elementType)) {
      return new ExtendsListBlock(child, wrap, alignmentStrategy, settings, javaSettings, formattingMode);
    }
    if (elementType == JavaElementType.CODE_BLOCK) {
      return new CodeBlockBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
    }
    if (elementType == JavaElementType.LABELED_STATEMENT) {
      return new LabeledJavaBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
    }
    if (elementType == JavaDocElementType.DOC_COMMENT) {
      return new DocCommentBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
    }

    final SimpleJavaBlock simpleJavaBlock = new SimpleJavaBlock(child, wrap, alignmentStrategy, actualIndent, settings, javaSettings, myFormattingMode);
    simpleJavaBlock.setStartOffset(startOffset);
    return simpleJavaBlock;
  }

  @NotNull
  public static Block newJavaBlock(@NotNull ASTNode child,
                                   @NotNull CommonCodeStyleSettings settings,
                                   @NotNull JavaCodeStyleSettings javaSettings,
                                   @NotNull FormattingMode formattingMode) {
    final Indent indent = getDefaultSubtreeIndent(child, getJavaIndentOptions(settings));
    return newJavaBlock(child, settings, javaSettings, indent, null, AlignmentStrategy.getNullStrategy(), formattingMode);
  }

  @NotNull
  public static Block newJavaBlock(@NotNull ASTNode child,
                                   @NotNull CommonCodeStyleSettings settings,
                                   @NotNull JavaCodeStyleSettings javaSettings,
                                   @Nullable Indent indent,
                                   @Nullable Wrap wrap,
                                   @NotNull AlignmentStrategy strategy,
                                   @NotNull FormattingMode formattingMode) {
    return new AbstractJavaBlock(child, settings, javaSettings, formattingMode) {
      @Override
      protected List<Block> buildChildren() {
        return null;
      }
    }.createJavaBlock(child, settings, javaSettings, indent, wrap, strategy, formattingMode);
  }

  @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 && !isMethodParameterAnnotation(prevElement)) {
      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 (parent.getPsi() instanceof PsiLambdaExpression && child instanceof PsiCodeBlock) {
        return Indent.getNoneIndent();
      }
    }

    return null;
  }

  private static boolean isMethodParameterAnnotation(@NotNull ASTNode element) {
    ASTNode parent = element.getTreeParent();
    if (parent != null && parent.getElementType() == JavaElementType.PARAMETER) {
      ASTNode lastChild = element.getLastChildNode();
      return lastChild != null && lastChild.getElementType() == JavaElementType.ANNOTATION;
    }
    return false;
  }

  @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(child2, mySettings, myJavaSettings);
  }

  @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() {
    //when detecting indent we do not care about wraps
    return isBuildIndentsOnly() ? null : 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);
    }
    return null;
  }

  @Nullable
  protected Alignment chooseAlignment(@Nullable Alignment alignment, @Nullable Alignment alignment2, @NotNull ASTNode child) {
    if (isTernaryOperatorToken(child)) {
      return alignment2;
    }
    return alignment;
  }

  private boolean isTernaryOperatorToken(@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,
                                 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) {
      Alignment alignment = shouldAlignChild(child) ? alignmentStrategy.getAlignment(childType) : null;
      result.add(createMethodCallExpressionBlock(child, arrangeChildWrap(child, defaultWrap), alignment, childIndent));
    }
    else {
      IElementType nodeType = myNode.getElementType();
      if (nodeType == JavaElementType.POLYADIC_EXPRESSION) nodeType = JavaElementType.BINARY_EXPRESSION;

      if (childType == JavaTokenType.LBRACE && nodeType == JavaElementType.ARRAY_INITIALIZER_EXPRESSION) {
        ArrayInitializerBlocksBuilder builder = new ArrayInitializerBlocksBuilder(myNode, myBlockFactory);
        List<Block> newlyCreated = builder.buildBlocks();

        child = myNode.getLastChildNode();
        result.addAll(newlyCreated);
      }
      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 && !isInsideMethodCall(myNode.getPsi())) {
            wrap.ignoreParentWraps();
        }
        child = processParenthesisBlock(result, child,
                                  WrappingStrategy.createDoNotWrapCommaStrategy(wrap),
                                  mySettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS);
      }
      else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.PARAMETER_LIST) {
        ASTNode parent = myNode.getTreeParent();
        boolean isLambdaParameterList = parent != null && parent.getElementType() == JavaElementType.LAMBDA_EXPRESSION;
        Wrap wrapToUse = isLambdaParameterList ? null : getMethodParametersWrap();
        WrappingStrategy wrapStrategy = WrappingStrategy.createDoNotWrapCommaStrategy(wrapToUse);
        child = processParenthesisBlock(result, child, wrapStrategy, mySettings.ALIGN_MULTILINE_PARAMETERS);
      }
      else if (childType == JavaTokenType.LPARENTH && nodeType == JavaElementType.RESOURCE_LIST) {
        Wrap wrap = 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) {
        AnnotationInitializerBlocksBuilder builder = new AnnotationInitializerBlocksBuilder(myNode, myBlockFactory);
        List<Block> newlyCreated = builder.buildBlocks();

        child = myNode.getLastChildNode();
        result.addAll(newlyCreated);
      }
      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
                  && (nodeType == JavaElementType.METHOD || nodeType == JavaElementType.CODE_BLOCK))
      {
        result.add(new SimpleJavaBlock(child, defaultWrap, alignmentStrategy, childIndent, mySettings, myJavaSettings, myFormattingMode));
      }
      else if (childType == JavaElementType.METHOD) {
        Wrap wrap = arrangeChildWrap(child, defaultWrap);
        Block block = createJavaBlock(child, mySettings, myJavaSettings, childIndent, wrap, alignmentStrategy, myFormattingMode);
        result.add(block);
      }
      else {
        Alignment alignment = alignmentStrategy.getAlignment(childType);

        ChildAttributes delegateAttributes = getDelegateAttributes(result);
        if (delegateAttributes != null) {
          alignment = delegateAttributes.getAlignment();
          if (delegateAttributes.getChildIndent() != null) {
            childIndent = delegateAttributes.getChildIndent();
          }
        }

        AlignmentStrategy alignmentStrategyToUse = shouldAlignChild(child)
                                                   ? AlignmentStrategy.wrap(alignment)
                                                   : AlignmentStrategy.getNullStrategy();

        if (myAlignmentStrategy.getAlignment(nodeType, childType) != null &&
            (nodeType == JavaElementType.IMPLEMENTS_LIST || nodeType == JavaElementType.CLASS)) {
          alignmentStrategyToUse = myAlignmentStrategy;
        }

        Wrap wrap = arrangeChildWrap(child, defaultWrap);

        Block block = createJavaBlock(child, mySettings, myJavaSettings, childIndent, wrap, alignmentStrategyToUse, childOffset, myFormattingMode);

        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);
          }
        }

        result.add(block);
      }
    }

    return child;
  }

  @Nullable
  private ChildAttributes getDelegateAttributes(@NotNull List<Block> result) {
    if (FormattingMode.ADJUST_INDENT_ON_ENTER.equals(myFormattingMode) && !result.isEmpty()) {
      final int lastIndex = result.size() - 1;
      Block lastBlock = result.get(lastIndex);
      if (lastBlock.isIncomplete()) {
        return lastBlock.getChildAttributes(lastBlock.getSubBlocks().size());
      }
    }
    return null;
  }

  private static boolean isAfterErrorElement(@NotNull ASTNode currNode) {
    ASTNode prev = currNode.getTreePrev();
    while (prev != null && (prev instanceof PsiWhiteSpace || prev.getTextLength() == 0)) {
      if (prev instanceof PsiErrorElement) return true;
      prev = prev.getTreePrev();
    }
    return false;
  }

  private static boolean isInsideMethodCall(@NotNull PsiElement element) {
    PsiElement e = element.getParent();
    int parentsVisited = 0;
    while (e != null && !(e instanceof PsiStatement) && parentsVisited < 5) {
      if (e instanceof PsiExpressionList) {
        return true;
      }
      e = e.getParent();
      parentsVisited++;
    }
    return false;
  }

  @NotNull
  private Wrap getMethodParametersWrap() {
    Wrap preferredWrap = getModifierListWrap();
    if (preferredWrap == null) {
      return Wrap.createWrap(getWrapType(mySettings.METHOD_PARAMETERS_WRAP), false);
    } else {
      return Wrap.createChildWrap(preferredWrap, getWrapType(mySettings.METHOD_PARAMETERS_WRAP), false);
    }
  }

  @Nullable
  private Wrap getModifierListWrap() {
    AbstractJavaBlock parentBlock = getParentBlock();
    if (parentBlock != null) {
      return parentBlock.getReservedWrap(JavaElementType.MODIFIER_LIST);
    }
    return null;
  }

  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(), myJavaSettings, childIndent, arrangeChildWrap(child, defaultWrap), alignmentStrategy, myFormattingMode));
      return child;
    }
    else {
      final ArrayList<Block> localResult = new ArrayList<>();
      while (child != null) {
        if (!FormatterUtil.containsWhiteSpacesOnly(child)) {
          localResult.add(createJavaBlock(
              child, getSettings(), myJavaSettings,
              Indent.getContinuationWithoutFirstIndent(myIndentSettings.USE_RELATIVE_INDENTS),
              arrangeChildWrap(child, defaultWrap),
              alignmentStrategy,
              myFormattingMode
            )
          );
        }
        if (child == lastFieldInGroup) break;

        child = child.getTreeNext();
      }
      if (!localResult.isEmpty()) {
        result.add(new SyntheticCodeBlock(localResult, null, getSettings(), myJavaSettings, childIndent, null));
      }
      return lastFieldInGroup;
    }
  }

  @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<>();
    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(), myJavaSettings, 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 ASTNode node, Wrap blockWrap, Alignment alignment, Indent indent) {
    final ArrayList<ASTNode> nodes = new ArrayList<>();
    collectNodes(nodes, node);
    return new ChainMethodCallsBlockBuilder(alignment, blockWrap, indent, mySettings, myJavaSettings, myFormattingMode).build(nodes);
  }

  private static void collectNodes(@NotNull List<ASTNode> nodes, @NotNull ASTNode node) {
    ASTNode child = node.getFirstChildNode();
    while (child != null) {
      if (!FormatterUtil.containsWhiteSpacesOnly(child)) {
        IElementType type = child.getElementType();
        if (type == JavaElementType.METHOD_CALL_EXPRESSION ||
            type == JavaElementType.REFERENCE_EXPRESSION) {
          collectNodes(nodes, child);
        }
        else {
          nodes.add(child);
        }
      }
      child = child.getTreeNext();
    }
  }

  private boolean shouldAlignChild(@NotNull final ASTNode child) {
    int role = getChildRole(child);
    final IElementType nodeType = myNode.getElementType();

    if (nodeType == JavaElementType.FOR_STATEMENT) {
      if (role == ChildRole.FOR_INITIALIZATION || role == ChildRole.CONDITION || role == ChildRole.FOR_UPDATE) {
        return true;
      }
      return false;
    }
    else if (nodeType == JavaElementType.EXTENDS_LIST ||
             nodeType == JavaElementType.IMPLEMENTS_LIST ||
             nodeType == JavaElementType.THROWS_LIST) {
      return role == ChildRole.REFERENCE_IN_LIST;
    }
    else if (nodeType == JavaElementType.CLASS) {
      if (role == ChildRole.CLASS_OR_INTERFACE_KEYWORD) return true;
      if (myIsAfterClassKeyword) return false;
      if (role == ChildRole.MODIFIER_LIST) return true;
      return false;
    }
    else if (JavaElementType.FIELD == nodeType) {
      return shouldAlignFieldInColumns(child);
    }
    else if (nodeType == JavaElementType.METHOD) {
      if (role == ChildRole.MODIFIER_LIST) return true;
      if (role == ChildRole.TYPE_PARAMETER_LIST) return true;
      if (role == ChildRole.TYPE) return true;
      if (role == ChildRole.NAME) return true;
      if (role == ChildRole.THROWS_LIST && mySettings.ALIGN_THROWS_KEYWORD) return true;
      if (role == ChildRole.METHOD_BODY) return !getNode().textContains('\n');
      return false;
    }

    else if (nodeType == JavaElementType.ASSIGNMENT_EXPRESSION) {
      if (role == ChildRole.LOPERAND) return true;
      if (role == ChildRole.ROPERAND && child.getElementType() == JavaElementType.ASSIGNMENT_EXPRESSION) {
        return true;
      }
      return false;
    }

    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 false;
      }
      return true;
    }

    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 true;
      }
      return false;
    }

    else {
      return true;
    }
  }

  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 CommonCodeStyleSettings#ALIGN_GROUP_FIELD_DECLARATIONS
   */
  private boolean shouldAlignFieldInColumns(@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 false;
    }

    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 false;
    }

    return true;
  }

  @Nullable
  public static Alignment createAlignment(final boolean alignOption, @Nullable final Alignment defaultAlignment) {
    return alignOption ? createAlignmentOrDefault(null, defaultAlignment) : defaultAlignment;
  }

  @Nullable
  public 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) {
    //when detecting indent we do not care about wraps
    return isBuildIndentsOnly() ? null : myWrapManager.arrangeChildWrap(child, myNode, mySettings, myJavaSettings, defaultWrap, this);
  }

  @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)
  {
    Indent externalIndent = Indent.getNoneIndent();
    Indent internalIndent = Indent.getContinuationWithoutFirstIndent(false);

    if (isInsideMethodCallParenthesis(child)) {
      internalIndent = Indent.getSmartIndent(Indent.Type.CONTINUATION);
    }

    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));


    ASTNode prev = child;
    while (child != null) {
      if (!FormatterUtil.containsWhiteSpacesOnly(child) && child.getTextLength() > 0) {
        if (child.getElementType() == from) {
          result.add(createJavaBlock(child, mySettings, myJavaSettings, externalIndent, null, bracketAlignment, myFormattingMode));
        }
        else if (child.getElementType() == to) {
          boolean isAfterIncomplete = isAfterErrorElement(child);
          Indent parenIndent = isAfterIncomplete ? internalIndent : externalIndent;
          Alignment parenAlignment =  isAfterIncomplete ? alignmentStrategy.getAlignment(null) : bracketAlignment;
          ChildAttributes attributes = getDelegateAttributes(result);
          if (attributes != null) {
            parenIndent = attributes.getChildIndent();
            parenAlignment = attributes.getAlignment();
          }
          Block block = createJavaBlock(child, mySettings, myJavaSettings,
                                        parenIndent,
                                        null,
                                        parenAlignment, myFormattingMode);
          result.add(block);
          return child;
        }
        else {
          final IElementType elementType = child.getElementType();
          AlignmentStrategy alignmentStrategyToUse = canUseAnonymousClassAlignment(child) ? anonymousClassStrategy : alignmentStrategy;
          processChild(result, child, alignmentStrategyToUse.getAlignment(elementType), wrappingStrategy.getWrap(elementType), internalIndent);
          if (to == null) {//process only one statement
            return child;
          }
        }
      }
      prev = child;
      child = child.getTreeNext();
    }

    return prev;
  }

  private static boolean isInsideMethodCallParenthesis(ASTNode child) {
    ASTNode currentPredecessor = child.getTreeParent();
    if (currentPredecessor != null) {
      currentPredecessor = currentPredecessor.getTreeParent();
      return currentPredecessor != null && currentPredecessor.getElementType() == JavaElementType.METHOD_CALL_EXPRESSION;
    }
    return false;
  }

  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 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 = new WrappingStrategy(Wrap.createWrap(getWrapType(mySettings.ENUM_CONSTANTS_WRAP), true)) {
      @Override
      protected boolean shouldWrap(IElementType type) {
        return type == JavaElementType.ENUM_CONSTANT;
      }
    };
    while (child != null) {
      if (!FormatterUtil.containsWhiteSpacesOnly(child) && child.getTextLength() > 0) {
        result.add(createJavaBlock(child, mySettings, myJavaSettings, Indent.getNormalIndent(),
                                   wrappingStrategy.getWrap(child.getElementType()), AlignmentStrategy.getNullStrategy(), myFormattingMode));
        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(int baseChildrenIndent) {
    if (isTopLevelClass() && mySettings.DO_NOT_INDENT_TOP_LEVEL_CLASS_MEMBERS) {
      return Indent.getNoneIndent();
    }

    final int braceStyle = getBraceStyle();
    final int shift = braceStyle == CommonCodeStyleSettings.NEXT_LINE_SHIFTED ? 1 : 0;
    return createNormalIndent(baseChildrenIndent - shift);
  }

  protected static Indent createNormalIndent() {
    return createNormalIndent(1);
  }

  protected static Indent createNormalIndent(int baseChildrenIndent) {
    assert baseChildrenIndent <= 1 : baseChildrenIndent;
    if (baseChildrenIndent <= 0) {
      return Indent.getNoneIndent();
    }
    else {
      return Indent.getIndent(Indent.Type.NORMAL, false, false);
    }
  }

  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} all the time
   */
  @Nullable
  @Override
  public Wrap getReservedWrap(IElementType elementType) {
    return myPreferredWraps != null ? myPreferredWraps.get(elementType) : 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
   */
  public void setReservedWrap(final Wrap reservedWrap, final IElementType operationType) {
    if (myPreferredWraps == null) {
      myPreferredWraps = ContainerUtil.newHashMap();
    }
    myPreferredWraps.put(operationType, reservedWrap);
  }

  @Nullable
  protected static ASTNode getTreeNode(final Block block) {
    if (block instanceof JavaBlock) {
      return ((JavaBlock)block).getFirstTreeNode();
    }
    if (block instanceof LeafBlock) {
      return ((LeafBlock)block).getTreeNode();
    }
    if (block instanceof CStyleCommentBlock) {
      return ((CStyleCommentBlock)block).getNode();
    }
    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<>();
    processChild(localResult, child, AlignmentStrategy.getNullStrategy(), null, Indent.getNoneIndent());
    child = child.getTreeNext();

    ChildAlignmentStrategyProvider alignmentStrategyProvider = getStrategyProvider();

    while (child != null) {
      if (FormatterUtil.containsWhiteSpacesOnly(child)) {
        child = child.getTreeNext();
        continue;
      }

      Indent childIndent = getIndentForCodeBlock(child, childrenIndent);
      AlignmentStrategy alignmentStrategyToUse = alignmentStrategyProvider.getNextChildStrategy(child);

      final boolean isRBrace = isRBrace(child);
      child = processChild(localResult, child, alignmentStrategyToUse, childWrap, childIndent);

      if (isRBrace) {
        result.add(createCodeBlockBlock(localResult, indent, childrenIndent));
        return child;
      }

      if (child != null) {
        child = child.getTreeNext();
      }
    }
    result.add(createCodeBlockBlock(localResult, indent, childrenIndent));
    return null;
  }

  protected ChildAlignmentStrategyProvider getStrategyProvider() {
    if (myNode.getElementType() == JavaElementType.CLASS) {
      return new SubsequentClassMemberAlignment(mySettings);
    }

    ASTNode parent = myNode.getTreeParent();
    IElementType parentType = parent != null ? parent.getElementType() : null;
    if (mySettings.ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS
        && (parentType == JavaElementType.METHOD || myNode instanceof PsiCodeBlock)) {
      return new SubsequentVariablesAligner();
    }

    return ChildAlignmentStrategyProvider.NULL_STRATEGY_PROVIDER;
  }

  private Indent getIndentForCodeBlock(ASTNode child, int childrenIndent) {
    if (child.getElementType() == JavaElementType.CODE_BLOCK
        && (getBraceStyle() == CommonCodeStyleSettings.NEXT_LINE_SHIFTED
            || getBraceStyle() == CommonCodeStyleSettings.NEXT_LINE_SHIFTED2))
    {
      return Indent.getNormalIndent();
    }

    return isRBrace(child) ? Indent.getNoneIndent() : getCodeBlockInternalIndent(childrenIndent);
  }

  public AbstractJavaBlock getParentBlock() {
    return myParentBlock;
  }

  public void setParentBlock(@NotNull AbstractJavaBlock parentBlock) {
    myParentBlock = parentBlock;
  }

  @NotNull
  public SyntheticCodeBlock createCodeBlockBlock(final List<Block> localResult, final Indent indent, final int childrenIndent) {
    final SyntheticCodeBlock result = new SyntheticCodeBlock(localResult, null, getSettings(), myJavaSettings, indent, null);
    result.setChildAttributes(new ChildAttributes(getCodeBlockInternalIndent(childrenIndent), null));
    return result;
  }

  protected FormattingMode getFormattingMode() {
    return myFormattingMode;
  }
}