/*
* Copyright 2000-2015 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.formatting.engine;
import com.intellij.diagnostic.LogMessageEx;
import com.intellij.formatting.*;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class AlignmentHelper {
private static final Logger LOG = Logger.getInstance(AlignmentHelper.class);
private static final Map<Alignment.Anchor, BlockAlignmentProcessor> ALIGNMENT_PROCESSORS = new EnumMap<>(Alignment.Anchor.class);
static {
ALIGNMENT_PROCESSORS.put(Alignment.Anchor.LEFT, new LeftEdgeAlignmentProcessor());
ALIGNMENT_PROCESSORS.put(Alignment.Anchor.RIGHT, new RightEdgeAlignmentProcessor());
}
private final Set<Alignment> myAlignmentsToSkip = ContainerUtil.newHashSet();
private final Document myDocument;
private final BlockIndentOptions myBlockIndentOptions;
private final AlignmentCyclesDetector myCyclesDetector;
private final Map<LeafBlockWrapper, Set<LeafBlockWrapper>> myBackwardShiftedAlignedBlocks = ContainerUtil.newHashMap();
private final Map<AbstractBlockWrapper, Set<AbstractBlockWrapper>> myAlignmentMappings = ContainerUtil.newHashMap();
public AlignmentHelper(Document document, MultiMap<Alignment, Block> blocksToAlign, BlockIndentOptions options) {
myDocument = document;
myBlockIndentOptions = options;
int totalBlocks = blocksToAlign.values().size();
myCyclesDetector = new AlignmentCyclesDetector(totalBlocks);
}
private static void reportAlignmentProcessingError(BlockAlignmentProcessor.Context context) {
ASTNode node = context.targetBlock.getNode();
Language language = node != null ? node.getPsi().getLanguage() : null;
LogMessageEx.error(LOG,
(language != null ? language.getDisplayName() + ": " : "") +
"Can't align block " + context.targetBlock, context.document.getText());
}
public LeafBlockWrapper applyAlignment(final AlignmentImpl alignment, final LeafBlockWrapper currentBlock) {
BlockAlignmentProcessor alignmentProcessor = ALIGNMENT_PROCESSORS.get(alignment.getAnchor());
if (alignmentProcessor == null) {
LOG.error(String.format("Can't find alignment processor for alignment anchor %s", alignment.getAnchor()));
return null;
}
BlockAlignmentProcessor.Context context = new BlockAlignmentProcessor.Context(
myDocument, alignment, currentBlock, myAlignmentMappings, myBackwardShiftedAlignedBlocks,
myBlockIndentOptions.getIndentOptions(currentBlock));
final LeafBlockWrapper offsetResponsibleBlock = alignment.getOffsetRespBlockBefore(currentBlock);
myCyclesDetector.registerOffsetResponsibleBlock(offsetResponsibleBlock);
BlockAlignmentProcessor.Result result = alignmentProcessor.applyAlignment(context);
switch (result) {
case TARGET_BLOCK_PROCESSED_NOT_ALIGNED:
return null;
case TARGET_BLOCK_ALIGNED:
storeAlignmentMapping(currentBlock);
return null;
case BACKWARD_BLOCK_ALIGNED:
if (offsetResponsibleBlock == null) {
return null;
}
Set<LeafBlockWrapper> blocksCausedRealignment = new HashSet<>();
myBackwardShiftedAlignedBlocks.clear();
myBackwardShiftedAlignedBlocks.put(offsetResponsibleBlock, blocksCausedRealignment);
blocksCausedRealignment.add(currentBlock);
storeAlignmentMapping(currentBlock, offsetResponsibleBlock);
if (myCyclesDetector.isCycleDetected()) {
reportAlignmentProcessingError(context);
return null;
}
myCyclesDetector.registerBlockRollback(currentBlock);
return offsetResponsibleBlock.getNextBlock();
case RECURSION_DETECTED:
myAlignmentsToSkip.add(alignment);
return offsetResponsibleBlock; // Fall through to the 'register alignment to skip'.
case UNABLE_TO_ALIGN_BACKWARD_BLOCK:
myAlignmentsToSkip.add(alignment);
return null;
default:
return null;
}
}
public boolean shouldSkip(AlignmentImpl alignment) {
return myAlignmentsToSkip.contains(alignment);
}
private void storeAlignmentMapping(AbstractBlockWrapper block1, AbstractBlockWrapper block2) {
doStoreAlignmentMapping(block1, block2);
doStoreAlignmentMapping(block2, block1);
}
private void doStoreAlignmentMapping(AbstractBlockWrapper key, AbstractBlockWrapper value) {
Set<AbstractBlockWrapper> wrappers = myAlignmentMappings.get(key);
if (wrappers == null) {
myAlignmentMappings.put(key, wrappers = new HashSet<>());
}
wrappers.add(value);
}
private void storeAlignmentMapping(LeafBlockWrapper currentBlock) {
AlignmentImpl alignment = null;
AbstractBlockWrapper block = currentBlock;
while (alignment == null && block != null) {
alignment = block.getAlignment();
block = block.getParent();
}
if (alignment != null) {
block = alignment.getOffsetRespBlockBefore(currentBlock);
if (block != null) {
storeAlignmentMapping(currentBlock, block);
}
}
}
public void reset() {
myBackwardShiftedAlignedBlocks.clear();
myAlignmentMappings.clear();
}
}