[java] Support Sealed Classes with Java 15 Preview
This commit is contained in:
@ -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):
|
||||
{
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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 { }
|
@ -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 { }
|
@ -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 { }
|
@ -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 { }
|
@ -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 { }
|
@ -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 { }
|
@ -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 { }
|
||||
|
@ -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 { }
|
||||
|
@ -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 { }
|
||||
|
@ -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 { }
|
||||
|
@ -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 { }
|
||||
|
Reference in New Issue
Block a user