summaryrefslogtreecommitdiff
path: root/platform/lang-impl/src/com/intellij/formatting/alignment/AlignmentStrategy.java
blob: 1844a0a217d2a7711a4e7c906b914d5a083c06a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
 * Copyright 2000-2009 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));
    }
  }
}