blob: 0be9a469a03e9e3df98fb00e1e9b4cd42c9aafc8 [file] [log] [blame]
/*
* Copyright 2000-2014 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.alignment;
import com.intellij.formatting.Alignment;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static java.util.Arrays.asList;
/** <code>GoF 'Strategy'</code> for {@link Alignment} retrieval. */
public abstract class AlignmentStrategy {
private static final AlignmentStrategy NULL_STRATEGY = wrap(null);
/** @return shared strategy instance that returns <code>null</code> all the time */
public static AlignmentStrategy getNullStrategy() {
return NULL_STRATEGY;
}
/**
* Delegates the processing to {@link #wrap(Alignment, boolean, IElementType...)} with <code>'true'</code> as the second argument
*
* @param alignment target alignment to wrap
* @param filterTypes types to use as a filter
* @return alignment strategy for the given parameters
*/
public static AlignmentStrategy wrap(@Nullable Alignment alignment, IElementType... filterTypes) {
return new SharedAlignmentStrategy(alignment, true, filterTypes);
}
/**
* Constructs strategy that returns given alignment for all elements which types pass through the target filter.
*
* @param alignment target alignment to wrap
* @param ignoreFilterTypes flag that defines if given alignment should be returned for all elements with given types or
* all elements except those with the given types
* @param filterTypes element types that should be used for filtering on subsequent calls
* to {@link #getAlignment(IElementType)}
* @return strategy that returns given alignment all the time for elements which types pass through the target
* filter; <code>null</code> otherwise
*/
public static AlignmentStrategy wrap(Alignment alignment, boolean ignoreFilterTypes, IElementType... filterTypes) {
return new SharedAlignmentStrategy(alignment, ignoreFilterTypes, filterTypes);
}
/**
* Delegates to {@link #createAlignmentPerTypeStrategy(Collection, IElementType, boolean, Alignment.Anchor)} with no parent type
* check (<code>null</code> is delivered as a parent type) and {@link Alignment.Anchor#LEFT left anchor}.
*
* @param targetTypes target child types
* @param allowBackwardShift flag that defines if backward alignment shift is allowed
* @return alignment strategy for the given arguments
*/
public static AlignmentPerTypeStrategy createAlignmentPerTypeStrategy(@NotNull Collection<IElementType> targetTypes,
boolean allowBackwardShift) {
return new AlignmentPerTypeStrategy(targetTypes, null, allowBackwardShift, Alignment.Anchor.LEFT);
}
/**
* Delegates the processing to {@link #createAlignmentPerTypeStrategy(Collection, IElementType, boolean, Alignment.Anchor)}
* with the given arguments and {@link Alignment.Anchor#LEFT left anchor}.
*
* @param targetTypes target types for which cached alignment should be returned
* @param parentType target parent type
* @param allowBackwardShift flag that specifies if former aligned element may be shifted to right in order to align
* to subsequent element
* @return alignment retrieval strategy that follows the rules described above
*/
public static AlignmentPerTypeStrategy createAlignmentPerTypeStrategy(
@NotNull Collection<IElementType> targetTypes, @Nullable IElementType parentType, boolean allowBackwardShift) {
return createAlignmentPerTypeStrategy(targetTypes, parentType, allowBackwardShift, Alignment.Anchor.LEFT);
}
/**
* Creates strategy that creates and caches one alignment per given type internally and returns it on subsequent calls
* to {@link #getAlignment(IElementType, IElementType)} for elements which type is listed at the given collection and parent type
* (if defined) is the same as the given one; <code>null</code> is returned from {@link #getAlignment(IElementType, IElementType)} for all
* other elements.
* <p/>
* This strategy is assumed to be used at following situations - suppose we want to align code blocks that doesn't belong
* to the same parent but have similar structure, e.g. variable declaration assignments like the one below:
* <pre>
* int start = 1;
* int finish = 2;
* </pre>
* We can provide parent blocks of that target blocks with the same instance of this alignment strategy and let them eventually
* reuse the same alignment objects for target sub-blocks of the same type.
*
* @param targetTypes target types for which cached alignment should be returned
* @param parentType target parent type
* @param allowBackwardShift flag that specifies if former aligned element may be shifted to right in order to align
* to subsequent element (e.g. <code>'='</code> block of <code>'int start = 1'</code> statement
* below is shifted one symbol right in order to align to the <code>'='</code> block
* of <code>'int finish = 1'</code> statement)
* @return alignment retrieval strategy that follows the rules described above
*/
public static AlignmentPerTypeStrategy createAlignmentPerTypeStrategy(
@NotNull Collection<IElementType> targetTypes, @Nullable IElementType parentType, boolean allowBackwardShift,
@NotNull Alignment.Anchor anchor) {
return new AlignmentPerTypeStrategy(targetTypes, parentType, allowBackwardShift, anchor);
}
/**
* Delegates the processing to {@link #getAlignment(IElementType, IElementType)} without parent element type
* filtering (<code>null</code> is used as parent element type).
*
* @param childType target child type
* @return alignment to use
*/
@Nullable
public Alignment getAlignment(@Nullable IElementType childType) {
return getAlignment(null, childType);
}
/**
* Requests current strategy for alignment to use for the child of the given type assuming that parent node has the given type.
*
* @param parentType parent type to use for filtering (if not <code>null</code>)
* @param childType child type to use for filtering (if not <code>null</code>)
* @return alignment to use for the given arguments
*/
@Nullable
public abstract Alignment getAlignment(@Nullable IElementType parentType, @Nullable IElementType childType);
/**
* Stands for {@link AlignmentStrategy} implementation that is configured to return single pre-configured {@link Alignment} object
* or <code>null</code> for all calls to {@link #getAlignment(IElementType)}.
*/
private static class SharedAlignmentStrategy extends AlignmentStrategy {
private final Set<IElementType> myFilterElementTypes = new HashSet<IElementType>();
private final Alignment myAlignment;
private final boolean myIgnoreFilterTypes;
private SharedAlignmentStrategy(Alignment alignment, boolean ignoreFilterTypes, IElementType... disabledElementTypes) {
myAlignment = alignment;
myIgnoreFilterTypes = ignoreFilterTypes;
myFilterElementTypes.addAll(asList(disabledElementTypes));
}
@Override
@Nullable
public Alignment getAlignment(@Nullable IElementType parentType, @Nullable IElementType childType) {
return myFilterElementTypes.contains(childType) ^ myIgnoreFilterTypes ? myAlignment : null;
}
}
/**
* Alignment strategy that creates and caches alignments for target element types and returns them for elements with the
* same types.
*/
public static class AlignmentPerTypeStrategy extends AlignmentStrategy {
private final Map<IElementType, Alignment> myAlignments = new HashMap<IElementType, Alignment>();
private final IElementType myParentType;
private final boolean myAllowBackwardShift;
AlignmentPerTypeStrategy(Collection<IElementType> targetElementTypes,
IElementType parentType,
boolean allowBackwardShift,
Alignment.Anchor anchor) {
myParentType = parentType;
myAllowBackwardShift = allowBackwardShift;
for (IElementType elementType : targetElementTypes) {
myAlignments.put(elementType, Alignment.createAlignment(myAllowBackwardShift, anchor));
}
}
@Override
public Alignment getAlignment(@Nullable IElementType parentType, @Nullable IElementType childType) {
if (myParentType != null && parentType != null && myParentType != parentType) {
return null;
}
return myAlignments.get(childType);
}
public void renewAlignment(IElementType elementType) {
myAlignments.put(elementType, Alignment.createAlignment(myAllowBackwardShift));
}
}
}