[java] Support Sealed Classes with Java 15 Preview

This commit is contained in:
Andreas Dangel
2020-08-14 22:16:41 +02:00
parent cda155891a
commit 0d9b5a7c3b
20 changed files with 291 additions and 0 deletions

View File

@ -4,6 +4,7 @@
* Support Pattern Matching for instanceof with Java 15 Preview.
* Support Records with Java 15 Preview.
* Support Local Records with Java 15 Preview.
* Support Sealed Classes with Java 15 Preview.
* Andreas Dangel 08/2020
*====================================================================
* Add support for record types introduced as a preview language
@ -420,9 +421,18 @@ public class JavaParser {
if (jdkVersion >= 10 && "var".equals(image)) {
throwParseException("With JDK 10, 'var' is a restricted local variable type and cannot be used for type declarations!");
}
if (jdkVersion >= 14 && "yield".equals(image)) {
throwParseException("With JDK 14, 'yield' is a restricted local variable type and cannot be used for type declarations!");
}
if (jdkVersion >= 14 && preview && "record".equals(image)) {
throwParseException("With JDK 14 Preview and JDK 15 Preview, 'record' is a restricted identifier and cannot be used for type declarations!");
}
if (jdkVersion >= 15 && preview && "sealed".equals(image)) {
throwParseException("With JDK 15 Preview, 'sealed' is a restricted identifier and cannot be used for type declarations!");
}
if (jdkVersion >= 15 && preview && "permits".equals(image)) {
throwParseException("With JDK 15 Preview, 'permits' is a restricted identifier and cannot be used for type declarations!");
}
}
private void checkForMultipleCaseLabels() {
if (jdkVersion < 14) {
@ -494,6 +504,12 @@ public class JavaParser {
return false;
}
private void checkForSealedClassUsage() {
if (jdkVersion != 15 || !preview) {
throwParseException("Sealed Classes are only supported with Java 15 Preview");
}
}
// This is a semantic LOOKAHEAD to determine if we're dealing with an assert
// Note that this can't be replaced with a syntactic lookahead
// since "assert" isn't a string literal token
@ -519,6 +535,12 @@ public class JavaParser {
/**
* Semantic lookahead to check if the next identifier is a
* specific restricted keyword.
*
* <p>Restricted keywords are:
* var, yield, record, sealed, permits
*
* <p>enum and assert is used like restricted keywords, as they were not keywords
* in the early java versions.
*/
private boolean isKeyword(String keyword) {
return getToken(1).kind == IDENTIFIER && getToken(1).image.equals(keyword);
@ -1002,6 +1024,14 @@ TOKEN :
| < GT: ">" >
}
// Note: New token need to be added at the very end in order to
// keep (binary) compatibility with the generated token ids
// see JavaParserConstants
TOKEN :
{
< NON_SEALED: "non-sealed" >
}
/*****************************************
* THE JAVA LANGUAGE GRAMMAR STARTS HERE *
*****************************************/
@ -1073,6 +1103,8 @@ int Modifiers() #void:
| "volatile" { modifiers |= AccessNode.VOLATILE; }
| "strictfp" { modifiers |= AccessNode.STRICTFP; }
| "default" { modifiers |= AccessNode.DEFAULT; checkForBadDefaultImplementationUsage(); }
| LOOKAHEAD({isKeyword("sealed")}) <IDENTIFIER> { modifiers |= AccessNode.SEALED; checkForSealedClassUsage(); }
| "non-sealed" { modifiers |= AccessNode.NON_SEALED; checkForSealedClassUsage(); }
| Annotation()
)
)*
@ -1114,6 +1146,7 @@ void ClassOrInterfaceDeclaration(int modifiers):
[ TypeParameters() ]
[ ExtendsList() ]
[ ImplementsList() ]
[ LOOKAHEAD({isKeyword("permits")}) PermittedSubclasses() ]
ClassOrInterfaceBody()
{ inInterface = inInterfaceOld; } // always restore the flag after leaving the node
}
@ -1134,6 +1167,21 @@ void ImplementsList():
( "," (TypeAnnotation())* ClassOrInterfaceType() )*
}
void PermittedSubclasses():
{
Token t;
checkForSealedClassUsage();
}
{
t = <IDENTIFIER> {
if (!"permits".equals(t.image)) {
throw new ParseException("ERROR: expecting permits");
}
}
(TypeAnnotation())* ClassOrInterfaceType()
( "," (TypeAnnotation())* ClassOrInterfaceType() )*
}
void EnumDeclaration(int modifiers):
{

View File

@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.java.ast;
import java.util.Collections;
import java.util.List;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.CollectionUtil;
@ -140,4 +141,23 @@ public class ASTClassOrInterfaceDeclaration extends AbstractAnyTypeDeclaration {
return it == null ? Collections.<ASTClassOrInterfaceType>emptyList() : CollectionUtil.toList(it.iterator());
}
@Experimental
public List<ASTClassOrInterfaceType> getPermittedSubclasses() {
ASTPermittedSubclasses permitted = getFirstChildOfType(ASTPermittedSubclasses.class);
return permitted == null
? Collections.<ASTClassOrInterfaceType>emptyList()
: CollectionUtil.toList(permitted.iterator());
}
@Experimental
public boolean isSealed() {
int modifiers = getModifiers();
return (modifiers & AccessNode.SEALED) == AccessNode.SEALED;
}
@Experimental
public boolean isNonSealed() {
int modifiers = getModifiers();
return (modifiers & AccessNode.NON_SEALED) == AccessNode.NON_SEALED;
}
}

View File

@ -0,0 +1,45 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import java.util.Iterator;
import net.sourceforge.pmd.annotation.Experimental;
/**
* Represents the {@code permits} clause of a (sealed) class declaration.
*
* <p>This is a Java 15 Preview feature.
*
* <p>See https://openjdk.java.net/jeps/360
*
* <pre class="grammar">
* PermittedSubclasses ::= "permits" (TypeAnnotation)* ClassOrInterfaceType
* ( "," (TypeAnnotation)* ClassOrInterfaceType )*
* </pre>
*/
@Experimental
public final class ASTPermittedSubclasses extends AbstractJavaNode implements Iterable<ASTClassOrInterfaceType> {
ASTPermittedSubclasses(int id) {
super(id);
}
ASTPermittedSubclasses(JavaParser p, int id) {
super(p, id);
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
@Override
public Iterator<ASTClassOrInterfaceType> iterator() {
return new NodeChildrenIterator<>(this, ASTClassOrInterfaceType.class);
}
}

View File

@ -24,6 +24,8 @@ public interface AccessNode extends Node {
int VOLATILE = 0x0200;
int STRICTFP = 0x1000;
int DEFAULT = 0x2000;
int SEALED = 0x4000;
int NON_SEALED = 0x8000;
int getModifiers();

View File

@ -942,4 +942,11 @@ public class JavaParserDecoratedVisitor implements JavaParserVisitor {
visitor.visit(node, data);
return visit((JavaNode) node, data);
}
@Override
@Experimental
public Object visit(ASTPermittedSubclasses node, Object data) {
visitor.visit(node, data);
return visit((JavaNode) node, data);
}
}

View File

@ -663,4 +663,10 @@ public class JavaParserVisitorAdapter implements JavaParserVisitor {
public Object visit(ASTRecordConstructorDeclaration node, Object data) {
return visit((JavaNode) node, data);
}
@Override
@Experimental
public Object visit(ASTPermittedSubclasses node, Object data) {
return visit((JavaNode) node, data);
}
}

View File

@ -795,4 +795,10 @@ public class JavaParserVisitorDecorator implements JavaParserControllessVisitor
public Object visit(ASTRecordConstructorDeclaration node, Object data) {
return visitor.visit(node, data);
}
@Override
@Experimental
public Object visit(ASTPermittedSubclasses node, Object data) {
return visitor.visit(node, data);
}
}

View File

@ -87,6 +87,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTNameList;
import net.sourceforge.pmd.lang.java.ast.ASTNormalAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTPermittedSubclasses;
import net.sourceforge.pmd.lang.java.ast.ASTPostfixExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPreDecrementExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPreIncrementExpression;
@ -871,4 +872,10 @@ public abstract class AbstractJavaRule extends AbstractRule implements JavaParse
public Object visit(ASTRecordConstructorDeclaration node, Object data) {
return visit((JavaNode) node, data);
}
@Override
@Experimental
public Object visit(ASTPermittedSubclasses node, Object data) {
return visit((JavaNode) node, data);
}
}

View File

@ -164,4 +164,47 @@ public class Java15PreviewTest {
Assert.assertEquals("MerchantSales", records.get(0).getSimpleName());
Assert.assertTrue(records.get(0).isLocal());
}
@Test(expected = ParseException.class)
public void sealedClassBeforeJava15Preview() {
java15.parseResource("geometry/Shape.java");
}
@Test
public void sealedClass() {
ASTCompilationUnit compilationUnit = java15p.parseResource("geometry/Shape.java");
ASTClassOrInterfaceDeclaration classDecl = compilationUnit.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class);
Assert.assertTrue(classDecl.isSealed());
List<ASTClassOrInterfaceType> permittedSubclasses = classDecl.getPermittedSubclasses();
Assert.assertEquals(3, permittedSubclasses.size());
Assert.assertEquals("Circle", permittedSubclasses.get(0).getImage());
Assert.assertEquals("Rectangle", permittedSubclasses.get(1).getImage());
Assert.assertEquals("Square", permittedSubclasses.get(2).getImage());
}
@Test
public void nonSealedClass() {
ASTCompilationUnit compilationUnit = java15p.parseResource("geometry/Square.java");
ASTClassOrInterfaceDeclaration classDecl = compilationUnit.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class);
Assert.assertTrue(classDecl.isNonSealed());
Assert.assertEquals(0, classDecl.getPermittedSubclasses().size());
}
@Test(expected = ParseException.class)
public void sealedInterfaceBeforeJava15Preview() {
java15.parseResource("expression/Expr.java");
}
@Test
public void sealedInterface() {
ASTCompilationUnit compilationUnit = java15p.parseResource("expression/Expr.java");
ASTClassOrInterfaceDeclaration interfaceDecl = compilationUnit.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class);
Assert.assertTrue(interfaceDecl.isSealed());
List<ASTClassOrInterfaceType> permittedSubclasses = interfaceDecl.getPermittedSubclasses();
Assert.assertEquals(4, permittedSubclasses.size());
Assert.assertEquals("ConstantExpr", permittedSubclasses.get(0).getImage());
Assert.assertEquals("PlusExpr", permittedSubclasses.get(1).getImage());
Assert.assertEquals("TimesExpr", permittedSubclasses.get(2).getImage());
Assert.assertEquals("NegExpr", permittedSubclasses.get(3).getImage());
}
}

View File

@ -0,0 +1,9 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.expression;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public final class ConstantExpr implements Expr { }

View File

@ -0,0 +1,10 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.expression;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { }

View File

@ -0,0 +1,9 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.expression;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public final class NegExpr implements Expr { }

View File

@ -0,0 +1,9 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.expression;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public final class PlusExpr implements Expr { }

View File

@ -0,0 +1,9 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.expression;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public final class TimesExpr implements Expr { }

View File

@ -0,0 +1,9 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.geometry;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public final class Circle extends Shape { }

View File

@ -0,0 +1,10 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.geometry;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public final class FilledRectangle extends Rectangle { }

View File

@ -0,0 +1,11 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.geometry;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public sealed class Rectangle extends Shape
permits TransparentRectangle, FilledRectangle { }

View File

@ -0,0 +1,11 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.geometry;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public sealed class Shape
permits Circle, Rectangle, Square { }

View File

@ -0,0 +1,10 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.geometry;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public non-sealed class Square extends Shape { }

View File

@ -0,0 +1,10 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package com.example.geometry;
/**
* @see <a href="https://openjdk.java.net/jeps/360">JEP 360: Sealed Classes (Preview)</a>
*/
public final class TransparentRectangle extends Rectangle { }