[java] Support Records with Java 15 Preview
This commit is contained in:
@ -2,6 +2,7 @@
|
|||||||
* Remove support for Java 13 preview language features.
|
* Remove support for Java 13 preview language features.
|
||||||
* Promote text blocks as a permanent language features with Java 15.
|
* Promote text blocks as a permanent language features with Java 15.
|
||||||
* Support Pattern Matching for instanceof with Java 15 Preview.
|
* Support Pattern Matching for instanceof with Java 15 Preview.
|
||||||
|
* Support Records with Java 15 Preview.
|
||||||
* Andreas Dangel 08/2020
|
* Andreas Dangel 08/2020
|
||||||
*====================================================================
|
*====================================================================
|
||||||
* Add support for record types introduced as a preview language
|
* 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!");
|
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)) {
|
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() {
|
private void checkForMultipleCaseLabels() {
|
||||||
@ -467,8 +468,8 @@ public class JavaParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkForRecordType() {
|
private void checkForRecordType() {
|
||||||
if (jdkVersion != 14 || !preview) {
|
if (jdkVersion != 14 && jdkVersion != 15 || !preview) {
|
||||||
throwParseException("Records are only supported with Java 14 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();
|
return isNested();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinal() {
|
||||||
|
// A record is implicitly final
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Renamed to {@link #getRecordComponents()}
|
* @deprecated Renamed to {@link #getRecordComponents()}
|
||||||
*/
|
*/
|
||||||
|
@ -7,10 +7,12 @@ package net.sourceforge.pmd.lang.java.ast;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import net.sourceforge.pmd.lang.ast.ParseException;
|
import net.sourceforge.pmd.lang.ast.ParseException;
|
||||||
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
|
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
|
||||||
|
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind;
|
||||||
|
|
||||||
public class Java15PreviewTest {
|
public class Java15PreviewTest {
|
||||||
private final JavaParsingHelper java15p =
|
private final JavaParsingHelper java15p =
|
||||||
@ -42,4 +44,113 @@ public class Java15PreviewTest {
|
|||||||
java15.parseResource("PatternMatchingInstanceof.java");
|
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