Skip to content

Commit

Permalink
Support sealed classes
Browse files Browse the repository at this point in the history
This implements support for JEP 409: Sealed Classes, see
https://openjdk.java.net/jeps/409.

The implementation handles the sealed and non-sealed keywords and sorts them
according to their JLS order.  This requires us to look at the actual text of
the Tok, as there’s no TokenKind for sealed and non-sealed.

The optional permits clause is handled in the same manner as the implements
clause. A new private method, classDeclarationTypeList, has been added to
facilitate this.

This fixes #603.

I implemented this in a manner that I deemed fit with the surrounding code, but if I missed something, don’t hesitate to get back to me and I’ll adjust this PR to suit.

Fixes #629

COPYBARA_INTEGRATE_REVIEW=google/google-java-format#629 from now:I603 4825e3310e00a1145304d64b4b73e9d3d03d08b3
PiperOrigin-RevId: 387160750
  • Loading branch information
now authored and fawind committed Jan 7, 2022
1 parent d98a48d commit bab393c
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1981,8 +1981,10 @@ public void visitClassDeclaration(ClassTree node) {
sync(node);
List<Op> breaks = visitModifiers(
node.getModifiers(), Direction.VERTICAL, /* declarationAnnotationBreak= */ Optional.empty());
List<? extends Tree> permitsTypes = getPermitsClause(node);
boolean hasSuperclassType = node.getExtendsClause() != null;
boolean hasSuperInterfaceTypes = !node.getImplementsClause().isEmpty();
boolean hasPermitsTypes = !permitsTypes.isEmpty();
builder.addAll(breaks);
token(node.getKind() == Tree.Kind.INTERFACE ? "interface" : "class");
builder.space();
Expand All @@ -1994,30 +1996,18 @@ public void visitClassDeclaration(ClassTree node) {
{
if (!node.getTypeParameters().isEmpty()) {
typeParametersRest(
node.getTypeParameters(), hasSuperclassType || hasSuperInterfaceTypes ? plusFour : ZERO);
node.getTypeParameters(),
hasSuperclassType || hasSuperInterfaceTypes || hasPermitsTypes ? plusFour : ZERO);
}
if (hasSuperclassType) {
builder.breakToFill(" ");
token("extends");
builder.space();
scan(node.getExtendsClause(), null);
}
if (hasSuperInterfaceTypes) {
builder.breakToFill(" ");
builder.open(node.getImplementsClause().size() > 1 ? plusFour : ZERO);
token(node.getKind() == Tree.Kind.INTERFACE ? "extends" : "implements");
builder.space();
boolean first = true;
for (Tree superInterfaceType : node.getImplementsClause()) {
if (!first) {
token(",");
builder.breakOp(" ");
}
scan(superInterfaceType, null);
first = false;
}
builder.close();
}
classDeclarationTypeList(
node.getKind() == Tree.Kind.INTERFACE ? "extends" : "implements", node.getImplementsClause());
classDeclarationTypeList("permits", permitsTypes);
}
builder.close();
if (node.getMembers() == null) {
Expand Down Expand Up @@ -2290,6 +2280,8 @@ boolean nextIsModifier() {
case "native":
case "strictfp":
case "default":
case "sealed":
case "non-sealed":
return true;
default:
return false;
Expand Down Expand Up @@ -3673,6 +3665,30 @@ protected void addBodyDeclarations(
}
}
}
/** Gets the permits clause for the given node. This is only available in Java 15 and later. */
protected List<? extends Tree> getPermitsClause(ClassTree node) {
return ImmutableList.of();
}

private void classDeclarationTypeList(String token, List<? extends Tree> types) {
if (types.isEmpty()) {
return;
}
builder.breakToFill(" ");
builder.open(types.size() > 1 ? plusFour : ZERO);
token(token);
builder.space();
boolean first = true;
for (Tree type : types) {
if (!first) {
token(",");
builder.breakOp(" ");
}
scan(type, null);
first = false;
}
builder.close();
}

/**
* The parser expands multi-variable declarations into separate single-variable declarations. All of the fragments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,41 +35,6 @@
/** Fixes sequences of modifiers to be in JLS order. */
final class ModifierOrderer {

/** Returns the {@link javax.lang.model.element.Modifier} for the given token kind, or {@code null}. */
private static Modifier getModifier(TokenKind kind) {
if (kind == null) {
return null;
}
switch (kind) {
case PUBLIC:
return Modifier.PUBLIC;
case PROTECTED:
return Modifier.PROTECTED;
case PRIVATE:
return Modifier.PRIVATE;
case ABSTRACT:
return Modifier.ABSTRACT;
case STATIC:
return Modifier.STATIC;
case DEFAULT:
return Modifier.DEFAULT;
case FINAL:
return Modifier.FINAL;
case TRANSIENT:
return Modifier.TRANSIENT;
case VOLATILE:
return Modifier.VOLATILE;
case SYNCHRONIZED:
return Modifier.SYNCHRONIZED;
case NATIVE:
return Modifier.NATIVE;
case STRICTFP:
return Modifier.STRICTFP;
default:
return null;
}
}

/** Reorders all modifiers in the given text to be in JLS order. */
static JavaInput reorderModifiers(String text) throws FormatterException {
return reorderModifiers(new JavaInput(text), ImmutableList.of(Range.closedOpen(0, text.length())));
Expand Down Expand Up @@ -143,7 +108,44 @@ private static void addTrivia(StringBuilder replacement, ImmutableList<? extends
* Returns the given token as a {@link javax.lang.model.element.Modifier}, or {@code null} if it is not a modifier.
*/
private static Modifier asModifier(Token token) {
return getModifier(((JavaInput.Tok) token.getTok()).kind());
TokenKind kind = ((JavaInput.Tok) token.getTok()).kind();
if (kind != null) {
switch (kind) {
case PUBLIC:
return Modifier.PUBLIC;
case PROTECTED:
return Modifier.PROTECTED;
case PRIVATE:
return Modifier.PRIVATE;
case ABSTRACT:
return Modifier.ABSTRACT;
case STATIC:
return Modifier.STATIC;
case DEFAULT:
return Modifier.DEFAULT;
case FINAL:
return Modifier.FINAL;
case TRANSIENT:
return Modifier.TRANSIENT;
case VOLATILE:
return Modifier.VOLATILE;
case SYNCHRONIZED:
return Modifier.SYNCHRONIZED;
case NATIVE:
return Modifier.NATIVE;
case STRICTFP:
return Modifier.STRICTFP;
default: // fall out
}
}
switch (token.getTok().getText()) {
case "non-sealed":
return Modifier.valueOf("NON_SEALED");
case "sealed":
return Modifier.valueOf("SEALED");
default:
return null;
}
}

/** Applies replacements to the given string. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ protected void handleModule(boolean first, CompilationUnitTree node) {
}
}

@Override
protected List<? extends Tree> getPermitsClause(ClassTree node) {
try {
return (List<? extends Tree>)
ClassTree.class.getMethod("getPermitsClause").invoke(node);
} catch (ReflectiveOperationException e) {
// Java < 15
return super.getPermitsClause(node);
}
}

@Override
public Void visitBindingPattern(BindingPatternTree node, Void unused) {
sync(node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public final class FileBasedTests {
// Test files that are only used when run with a minimum Java version
private static final ImmutableSet<String> JAVA_14_TESTS =
ImmutableSet.of("ExpressionSwitch", "RSL", "Records", "Var", "I574", "I594");
private static final ImmutableSet<String> JAVA_15_TESTS = ImmutableSet.of("I603");
private static final ImmutableSet<String> JAVA_16_TESTS = ImmutableSet.of("I588");

private final Class<?> testClass;
Expand All @@ -66,6 +67,8 @@ public FileBasedTests(Class<?> testClass, String testDirName) {
public static void assumeJavaVersionForTest(String testName) {
if (JAVA_14_TESTS.contains(testName)) {
Assumptions.assumeTrue(Formatter.getRuntimeVersion() >= 14, "Not running on jdk 14 or later");
} else if (JAVA_15_TESTS.contains(testName)) {
Assumptions.assumeTrue(Formatter.getRuntimeVersion() >= 15, "Not running on jdk 15 or later");
} else if (JAVA_16_TESTS.contains(testName)) {
Assumptions.assumeTrue(Formatter.getRuntimeVersion() >= 16, "Not running on jdk 16 or later");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class I603 {
sealed abstract class T1 {}

sealed class T2 extends X implements Y permits Z {}

sealed class T3
permits
Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}

sealed class T4
implements
Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
permits
Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
Yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class I603 {
abstract sealed class T1 {}

sealed class T2 extends X implements Y permits Z {}

sealed class T3
permits Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {}

sealed class T4
implements Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
permits Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
Yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy {}
}

0 comments on commit bab393c

Please sign in to comment.