Skip to content

Commit 2c63af6

Browse files
authored
Merge pull request #67 from openjavaformat/non-sealed-modifier-order
Reorder non-sealed as one modifier
2 parents d6d4a71 + 6282d12 commit 2c63af6

5 files changed

Lines changed: 153 additions & 26 deletions

File tree

‎open-java-format/src/main/java/com/palantir/javaformat/java/ModifierOrderer.java‎

Lines changed: 124 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package com.palantir.javaformat.java;
1818

19+
import static com.google.common.base.Preconditions.checkState;
20+
import static com.google.common.collect.Iterables.getLast;
21+
1922
import com.google.common.collect.ImmutableList;
2023
import com.google.common.collect.Ordering;
2124
import com.google.common.collect.Range;
@@ -26,10 +29,10 @@
2629
import com.sun.tools.javac.parser.Tokens.TokenKind;
2730
import java.util.ArrayList;
2831
import java.util.Collection;
29-
import java.util.Collections;
3032
import java.util.Iterator;
3133
import java.util.List;
3234
import java.util.Map;
35+
import javax.annotation.Nullable;
3336
import javax.lang.model.element.Modifier;
3437

3538
/** Fixes sequences of modifiers to be in JLS order. */
@@ -40,6 +43,74 @@ static JavaInput reorderModifiers(String text) throws FormatterException {
4043
return reorderModifiers(new JavaInput(text), ImmutableList.of(Range.closedOpen(0, text.length())));
4144
}
4245

46+
/**
47+
* The tokens that make up one modifier. Usually a single token (e.g. for {@code public}), but a modifier that
48+
* contains a {@code -} (e.g. {@code non-sealed}) is lexed as three tokens.
49+
*/
50+
static final class ModifierTokens implements Comparable<ModifierTokens> {
51+
private final ImmutableList<Token> tokens;
52+
53+
@Nullable
54+
private final Modifier modifier;
55+
56+
static ModifierTokens create(ImmutableList<Token> tokens) {
57+
return new ModifierTokens(tokens, asModifier(tokens));
58+
}
59+
60+
static ModifierTokens empty() {
61+
return new ModifierTokens(ImmutableList.of(), null);
62+
}
63+
64+
private ModifierTokens(ImmutableList<Token> tokens, @Nullable Modifier modifier) {
65+
this.tokens = tokens;
66+
this.modifier = modifier;
67+
}
68+
69+
boolean isEmpty() {
70+
return tokens.isEmpty() || modifier == null;
71+
}
72+
73+
@SuppressWarnings("for-rollout:NullAway")
74+
Modifier modifier() {
75+
return modifier;
76+
}
77+
78+
ImmutableList<Token> tokens() {
79+
return tokens;
80+
}
81+
82+
private Token first() {
83+
return tokens.get(0);
84+
}
85+
86+
private Token last() {
87+
return getLast(tokens);
88+
}
89+
90+
int startPosition() {
91+
return first().getTok().getPosition();
92+
}
93+
94+
int endPosition() {
95+
return last().getTok().getPosition() + last().getTok().length();
96+
}
97+
98+
ImmutableList<? extends Tok> getToksBefore() {
99+
return first().getToksBefore();
100+
}
101+
102+
ImmutableList<? extends Tok> getToksAfter() {
103+
return last().getToksAfter();
104+
}
105+
106+
@Override
107+
@SuppressWarnings("for-rollout:NullAway")
108+
public int compareTo(ModifierTokens o) {
109+
checkState(!isEmpty()); // empty ModifierTokens are filtered out prior to sorting
110+
return modifier.compareTo(o.modifier);
111+
}
112+
}
113+
43114
/** Reorders all modifiers in the given text and within the given character ranges to be in JLS order. */
44115
static JavaInput reorderModifiers(JavaInput javaInput, Collection<Range<Integer>> characterRanges)
45116
throws FormatterException {
@@ -52,43 +123,37 @@ static JavaInput reorderModifiers(JavaInput javaInput, Collection<Range<Integer>
52123
Iterator<? extends Token> it = javaInput.getTokens().iterator();
53124
TreeRangeMap<Integer, String> replacements = TreeRangeMap.create();
54125
while (it.hasNext()) {
55-
Token token = it.next();
56-
if (!tokenRanges.contains(token.getTok().getIndex())) {
57-
continue;
58-
}
59-
Modifier mod = asModifier(token);
60-
if (mod == null) {
126+
ModifierTokens tokens = getModifierTokens(it);
127+
if (tokens.isEmpty()
128+
|| !tokens.tokens().stream()
129+
.allMatch(token -> tokenRanges.contains(token.getTok().getIndex()))) {
61130
continue;
62131
}
63132

64-
List<Token> modifierTokens = new ArrayList<>();
65-
List<Modifier> mods = new ArrayList<>();
133+
List<ModifierTokens> modifierTokens = new ArrayList<>();
66134

67-
int begin = token.getTok().getPosition();
68-
mods.add(mod);
69-
modifierTokens.add(token);
135+
int begin = tokens.startPosition();
136+
modifierTokens.add(tokens);
70137

71138
int end = -1;
72139
while (it.hasNext()) {
73-
token = it.next();
74-
mod = asModifier(token);
75-
if (mod == null) {
140+
tokens = getModifierTokens(it);
141+
if (tokens.isEmpty()) {
76142
break;
77143
}
78-
mods.add(mod);
79-
modifierTokens.add(token);
80-
end = token.getTok().getPosition() + token.getTok().length();
144+
modifierTokens.add(tokens);
145+
end = tokens.endPosition();
81146
}
82147

83-
if (!Ordering.natural().isOrdered(mods)) {
84-
Collections.sort(mods);
148+
if (!Ordering.natural().isOrdered(modifierTokens)) {
149+
List<ModifierTokens> sorted = Ordering.natural().sortedCopy(modifierTokens);
85150
StringBuilder replacement = new StringBuilder();
86-
for (int i = 0; i < mods.size(); i++) {
151+
for (int i = 0; i < sorted.size(); i++) {
87152
if (i > 0) {
88153
addTrivia(replacement, modifierTokens.get(i).getToksBefore());
89154
}
90-
replacement.append(mods.get(i).toString());
91-
if (i < (modifierTokens.size() - 1)) {
155+
replacement.append(sorted.get(i).modifier());
156+
if (i < (sorted.size() - 1)) {
92157
addTrivia(replacement, modifierTokens.get(i).getToksAfter());
93158
}
94159
}
@@ -104,9 +169,45 @@ private static void addTrivia(StringBuilder replacement, ImmutableList<? extends
104169
}
105170
}
106171

172+
/**
173+
* Consumes the tokens of one modifier from the iterator: the next token, plus the two after it when it is the
174+
* {@code non} of a hyphenated modifier such as {@code non-sealed}. The result is empty if they are not a modifier.
175+
*/
176+
private static ModifierTokens getModifierTokens(Iterator<? extends Token> it) {
177+
Token token = it.next();
178+
ImmutableList.Builder<Token> result = ImmutableList.builder();
179+
result.add(token);
180+
if (!token.getTok().getText().equals("non")) {
181+
return ModifierTokens.create(result.build());
182+
}
183+
if (!it.hasNext()) {
184+
return ModifierTokens.empty();
185+
}
186+
Token dash = it.next();
187+
result.add(dash);
188+
if (!dash.getTok().getText().equals("-") || !it.hasNext()) {
189+
return ModifierTokens.empty();
190+
}
191+
result.add(it.next());
192+
return ModifierTokens.create(result.build());
193+
}
194+
195+
@Nullable
196+
private static Modifier asModifier(ImmutableList<Token> tokens) {
197+
if (tokens.size() == 1) {
198+
return asModifier(tokens.get(0));
199+
}
200+
Modifier modifier = asModifier(getLast(tokens));
201+
if (modifier == null) {
202+
return null;
203+
}
204+
return Modifier.valueOf("NON_" + modifier.name());
205+
}
206+
107207
/**
108208
* Returns the given token as a {@link javax.lang.model.element.Modifier}, or {@code null} if it is not a modifier.
109209
*/
210+
@Nullable
110211
@SuppressWarnings("for-rollout:NullAway")
111212
private static Modifier asModifier(Token token) {
112213
TokenKind kind = ((JavaInput.Tok) token.getTok()).kind();
@@ -140,8 +241,6 @@ private static Modifier asModifier(Token token) {
140241
}
141242
}
142243
switch (token.getTok().getText()) {
143-
case "non-sealed":
144-
return Modifier.valueOf("NON_SEALED");
145244
case "sealed":
146245
return Modifier.valueOf("SEALED");
147246
default:

‎open-java-format/src/test/java/com/palantir/javaformat/java/FileBasedTests.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public final class FileBasedTests {
4949
ImmutableMultimap.<Integer, String>builder()
5050
.putAll(14, "Records", "RSL", "Var", "ExpressionSwitch", "I574", "I594")
5151
.putAll(15, "I603")
52-
.putAll(16, "I588")
52+
.putAll(16, "I588", "Sealed")
5353
.putAll(17, "I683", "I684", "I696")
5454
.putAll(
5555
21,

‎open-java-format/src/test/java/com/palantir/javaformat/java/ModifierOrdererTest.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,17 @@ public void whitespace() throws FormatterException {
9999
.getText();
100100
assertThat(output).contains("public\n static int a;");
101101
}
102+
103+
@Test
104+
public void sealedClass() throws FormatterException {
105+
assertThat(ModifierOrderer.reorderModifiers("non-sealed sealed public").getText())
106+
.isEqualTo("public sealed non-sealed");
107+
}
108+
109+
@Test
110+
public void nonSealedBeforeAccessModifier() throws FormatterException {
111+
assertThat(ModifierOrderer.reorderModifiers("non-sealed private interface B extends I {}")
112+
.getText())
113+
.isEqualTo("private non-sealed interface B extends I {}");
114+
}
102115
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class T {
2+
sealed interface I extends A permits C, B {}
3+
final class C implements I {}
4+
sealed private interface A permits I {}
5+
non-sealed private interface B extends I {}
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class T {
2+
sealed interface I extends A permits C, B {}
3+
4+
final class C implements I {}
5+
6+
private sealed interface A permits I {}
7+
8+
private non-sealed interface B extends I {}
9+
}

0 commit comments

Comments
 (0)