[java] Support Records with Java 15 Preview
This commit is contained in:
@ -2,6 +2,7 @@
|
||||
* Remove support for Java 13 preview language features.
|
||||
* Promote text blocks as a permanent language features with Java 15.
|
||||
* Support Pattern Matching for instanceof with Java 15 Preview.
|
||||
* Support Records with Java 15 Preview.
|
||||
* Andreas Dangel 08/2020
|
||||
*====================================================================
|
||||
* Add support for record types introduced as a preview language
|
||||
@ -419,7 +420,7 @@ public class JavaParser {
|
||||
throwParseException("With JDK 10, 'var' 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, 'record' is a restricted identifier and cannot be used for type declarations!");
|
||||
throwParseException("With JDK 14 Preview and JDK 15 Preview, 'record' is a restricted identifier and cannot be used for type declarations!");
|
||||
}
|
||||
}
|
||||
private void checkForMultipleCaseLabels() {
|
||||
@ -467,8 +468,8 @@ public class JavaParser {
|
||||
}
|
||||
|
||||
private void checkForRecordType() {
|
||||
if (jdkVersion != 14 || !preview) {
|
||||
throwParseException("Records are only supported with Java 14 Preview");
|
||||
if (jdkVersion != 14 && jdkVersion != 15 || !preview) {
|
||||
throwParseException("Records are only supported with Java 14 Preview and Java 15 Preview");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,12 @@ public final class ASTRecordDeclaration extends AbstractAnyTypeDeclaration {
|
||||
return isNested();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinal() {
|
||||
// A record is implicitly final
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Renamed to {@link #getRecordComponents()}
|
||||
*/
|
||||
|
@ -7,10 +7,12 @@ package net.sourceforge.pmd.lang.java.ast;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.ParseException;
|
||||
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind;
|
||||
|
||||
public class Java15PreviewTest {
|
||||
private final JavaParsingHelper java15p =
|
||||
@ -42,4 +44,113 @@ public class Java15PreviewTest {
|
||||
java15.parseResource("PatternMatchingInstanceof.java");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recordPoint() {
|
||||
ASTCompilationUnit compilationUnit = java15p.parseResource("Point.java");
|
||||
ASTRecordDeclaration recordDecl = compilationUnit.getFirstDescendantOfType(ASTRecordDeclaration.class);
|
||||
Assert.assertEquals("Point", recordDecl.getImage());
|
||||
Assert.assertFalse(recordDecl.isNested());
|
||||
Assert.assertTrue("Records are implicitly always final", recordDecl.isFinal());
|
||||
List<ASTRecordComponent> components = recordDecl.getFirstChildOfType(ASTRecordComponentList.class)
|
||||
.findChildrenOfType(ASTRecordComponent.class);
|
||||
Assert.assertEquals(2, components.size());
|
||||
Assert.assertEquals("x", components.get(0).getVarId().getImage());
|
||||
Assert.assertEquals("y", components.get(1).getVarId().getImage());
|
||||
Assert.assertNull(components.get(0).getVarId().getNameDeclaration().getAccessNodeParent());
|
||||
Assert.assertEquals(Integer.TYPE, components.get(0).getVarId().getNameDeclaration().getType());
|
||||
Assert.assertEquals("int", components.get(0).getVarId().getNameDeclaration().getTypeImage());
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void recordPointBeforeJava15PreviewShouldFail() {
|
||||
java15.parseResource("Point.java");
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void recordCtorWithThrowsShouldFail() {
|
||||
java15p.parse(" record R {"
|
||||
+ " R throws IOException {}"
|
||||
+ " }");
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void recordMustNotExtend() {
|
||||
java15p.parse("record RecordEx(int x) extends Number { }");
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
@Ignore("Should we check this?")
|
||||
public void recordCannotBeAbstract() {
|
||||
java15p.parse("abstract record RecordEx(int x) { }");
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
@Ignore("Should we check this?")
|
||||
public void recordCannotHaveInstanceFields() {
|
||||
java15p.parse("record RecordFields(int x) { private int y = 1; }");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void innerRecords() {
|
||||
ASTCompilationUnit compilationUnit = java15p.parseResource("Records.java");
|
||||
List<ASTRecordDeclaration> recordDecls = compilationUnit.findDescendantsOfType(ASTRecordDeclaration.class, true);
|
||||
Assert.assertEquals(7, recordDecls.size());
|
||||
|
||||
ASTRecordDeclaration complex = recordDecls.get(0);
|
||||
Assert.assertEquals("MyComplex", complex.getSimpleName());
|
||||
Assert.assertTrue(complex.isNested());
|
||||
Assert.assertEquals(0, getComponent(complex, 0).findChildrenOfType(ASTAnnotation.class).size());
|
||||
Assert.assertEquals(1, getComponent(complex, 1).findChildrenOfType(ASTAnnotation.class).size());
|
||||
Assert.assertEquals(2, complex.getDeclarations().size());
|
||||
Assert.assertTrue(complex.getDeclarations().get(0).getChild(1) instanceof ASTConstructorDeclaration);
|
||||
Assert.assertTrue(complex.getDeclarations().get(1).getChild(0) instanceof ASTRecordDeclaration);
|
||||
Assert.assertTrue(complex.getParent() instanceof ASTClassOrInterfaceBodyDeclaration);
|
||||
ASTClassOrInterfaceBodyDeclaration complexParent = complex.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
|
||||
Assert.assertEquals(DeclarationKind.RECORD, complexParent.getKind());
|
||||
Assert.assertSame(complex, complexParent.getDeclarationNode());
|
||||
|
||||
ASTRecordDeclaration nested = recordDecls.get(1);
|
||||
Assert.assertEquals("Nested", nested.getSimpleName());
|
||||
Assert.assertTrue(nested.isNested());
|
||||
|
||||
ASTRecordDeclaration range = recordDecls.get(2);
|
||||
Assert.assertEquals("Range", range.getSimpleName());
|
||||
Assert.assertEquals(2, range.getComponentList().size());
|
||||
List<ASTRecordConstructorDeclaration> rangeConstructors = range.findDescendantsOfType(ASTRecordConstructorDeclaration.class);
|
||||
Assert.assertEquals(1, rangeConstructors.size());
|
||||
Assert.assertEquals("Range", rangeConstructors.get(0).getImage());
|
||||
Assert.assertTrue(rangeConstructors.get(0).getChild(0) instanceof ASTAnnotation);
|
||||
Assert.assertEquals(2, range.getDeclarations().size());
|
||||
|
||||
ASTRecordDeclaration varRec = recordDecls.get(3);
|
||||
Assert.assertEquals("VarRec", varRec.getSimpleName());
|
||||
Assert.assertEquals("x", getComponent(varRec, 0).getVarId().getImage());
|
||||
Assert.assertTrue(getComponent(varRec, 0).isVarargs());
|
||||
Assert.assertEquals(2, getComponent(varRec, 0).findChildrenOfType(ASTAnnotation.class).size());
|
||||
Assert.assertEquals(1, getComponent(varRec, 0).getTypeNode().findDescendantsOfType(ASTAnnotation.class).size());
|
||||
|
||||
ASTRecordDeclaration arrayRec = recordDecls.get(4);
|
||||
Assert.assertEquals("ArrayRec", arrayRec.getSimpleName());
|
||||
Assert.assertEquals("x", getComponent(arrayRec, 0).getVarId().getImage());
|
||||
Assert.assertTrue(getComponent(arrayRec, 0).getVarId().hasArrayType());
|
||||
|
||||
ASTRecordDeclaration emptyRec = recordDecls.get(5);
|
||||
Assert.assertEquals("EmptyRec", emptyRec.getSimpleName());
|
||||
Assert.assertEquals(0, emptyRec.getComponentList().size());
|
||||
|
||||
ASTRecordDeclaration personRec = recordDecls.get(6);
|
||||
Assert.assertEquals("PersonRecord", personRec.getSimpleName());
|
||||
ASTImplementsList impl = personRec.getFirstChildOfType(ASTImplementsList.class);
|
||||
Assert.assertEquals(2, impl.findChildrenOfType(ASTClassOrInterfaceType.class).size());
|
||||
}
|
||||
|
||||
private ASTRecordComponent getComponent(ASTRecordDeclaration arrayRec, int index) {
|
||||
return (ASTRecordComponent) arrayRec.getComponentList().getChild(index);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void recordIsARestrictedIdentifier() {
|
||||
java15p.parse("public class record {}");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @see <a href="https://openjdk.java.net/jeps/384">JEP 384: Records (Second Preview)</a>
|
||||
*/
|
||||
public record Point(int x, int y) {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Point p = new Point(1, 2);
|
||||
System.out.println("p = " + p);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
/**
|
||||
* @see <a href="https://openjdk.java.net/jeps/384">JEP 384: Records (Second Preview)</a>
|
||||
*/
|
||||
public class Records {
|
||||
|
||||
@Target(ElementType.TYPE_USE)
|
||||
@interface Nullable { }
|
||||
|
||||
@Target({ElementType.CONSTRUCTOR, ElementType.PARAMETER})
|
||||
@interface MyAnnotation { }
|
||||
|
||||
public record MyComplex(int real, @Deprecated int imaginary) {
|
||||
// explicit declaration of a canonical constructor
|
||||
@MyAnnotation
|
||||
public MyComplex(@MyAnnotation int real, int imaginary) {
|
||||
if (real > 100) throw new IllegalArgumentException("too big");
|
||||
this.real = real;
|
||||
this.imaginary = imaginary;
|
||||
}
|
||||
public record Nested(int a) {}
|
||||
}
|
||||
|
||||
|
||||
public record Range(int lo, int hi) {
|
||||
// compact record constructor
|
||||
@MyAnnotation
|
||||
public Range {
|
||||
if (lo > hi) /* referring here to the implicit constructor parameters */
|
||||
throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi));
|
||||
}
|
||||
|
||||
public void foo() { }
|
||||
}
|
||||
|
||||
public record VarRec(@Nullable @Deprecated String @Nullable ... x) {}
|
||||
|
||||
public record ArrayRec(int x[]) {}
|
||||
|
||||
public record EmptyRec<Type>() {
|
||||
public void foo() { }
|
||||
public Type bar() { return null; }
|
||||
public static void baz() {
|
||||
EmptyRec<String> r = new EmptyRec<>();
|
||||
System.out.println(r);
|
||||
}
|
||||
}
|
||||
|
||||
// see https://www.javaspecialists.eu/archive/Issue276.html
|
||||
public interface Person {
|
||||
String firstName();
|
||||
String lastName();
|
||||
}
|
||||
public record PersonRecord(String firstName, String lastName)
|
||||
implements Person, java.io.Serializable {
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user