RecordsTest.java

package com.thoughtworks.qdox;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaConstructor;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;
import com.thoughtworks.qdox.model.JavaType;

import java.util.LinkedList;
import java.io.StringReader;

/**
 * Examples from <a href="https://docs.oracle.com/en/java/javase/16/language/records.html">https://docs.oracle.com/en/java/javase/16/language/records.html</a>
 * 
 * @author Robert Scholte
 */
public class RecordsTest
{

    @Test
    public void withTwoFields() {
        String source = 
         "/* comment */" +
         "record Rectangle(double length, double width) { }";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );

        JavaClass cls = javaDocBuilder.getClassByName("Rectangle");
        Assertions.assertTrue( cls.getLineNumber() == 1 );
        Assertions.assertTrue( cls.isRecord() );
        JavaField field = cls.getFieldByName("length");
        Assertions.assertTrue( field.getLineNumber() == 1 );
        Assertions.assertTrue( field.getType().isA("double") );
        JavaConstructor constructor = cls.getConstructors().get(0);
        JavaParameter lengthParam = constructor.getParameterByName("length");
        Assertions.assertTrue( lengthParam != null );
        JavaMethod lengthGetter = cls.getMethod( "length", new LinkedList(), false );
        Assertions.assertTrue( lengthGetter.getReturns().isA( "double" ) );
        JavaMethod widthGetter = cls.getMethod( "width", new LinkedList(), false );
        Assertions.assertTrue( widthGetter.getLineNumber() == 1 );
    }

    @Test
    public void withImplements() {
        String source = "record Rectangle(double length, double width) implements java.util.List<String> { }";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );

        JavaClass cls = javaDocBuilder.getClassByName("Rectangle");
        Assertions.assertTrue( cls.isRecord() );
        Assertions.assertTrue( cls.isA("java.util.List") );
    }
    
    @Test
    public void withCanonicalConstructor() {
        String source = "record Rectangle(double length, double width) {\n"
            + "    public Rectangle(double length, double width) {\n"
            + "        if (length <= 0 || width <= 0) {\n"
            + "            throw new java.lang.IllegalArgumentException(\n"
            + "                String.format(\"Invalid dimensions: %f, %f\", length, width));\n"
            + "        }\n"
            + "        this.length = length;\n"
            + "        this.width = width;\n"
            + "    }\n"
            + "}";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
    }
    
    @Test
    public void withCompactConstructor() {
        String source = "record Rectangle(double length, double width) {\n"
            + "    public Rectangle {\n"
            + "        if (length <= 0 || width <= 0) {\n"
            + "            throw new java.lang.IllegalArgumentException(\n"
            + "                String.format(\"Invalid dimensions: %f, %f\", length, width));\n"
            + "        }\n"
            + "    }\n"
            + "}";

        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
        JavaClass cls = javaDocBuilder.getClassByName( "Rectangle" );
        JavaConstructor constructor = cls.getConstructors().get(0);
        Assertions.assertTrue( constructor.getSourceCode() != null );
    }

    @Test
    public void withPublicAccessorMethod() {
        String source = "record Rectangle(double length, double width) {\n"
            + " \n"
            + "    // Public accessor method\n"
            + "    public double length() {\n"
            + "        System.out.println(\"Length is \" + length);\n"
            + "        return length;\n"
            + "    }\n"
            + "}";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
        JavaClass cls = javaDocBuilder.getClassByName( "Rectangle" );
        Assertions.assertTrue( cls.getMethods().size() == 2 );
        JavaMethod mth = cls.getMethod("length", new LinkedList(), false);
        Assertions.assertTrue( mth.getLineNumber() == 4 );
    }
    
    @Test
    public void withStaticMembers() {
        String source = "record Rectangle(double length, double width) {\n"
            + "    \n"
            + "    // Static field\n"
            + "    static double goldenRatio;\n"
            + "\n"
            + "    // Static initializer\n"
            + "    static {\n"
            + "        goldenRatio = (1 + Math.sqrt(5)) / 2;\n"
            + "    }\n"
            + "\n"
            + "    // Static method\n"
            + "    public static Rectangle createGoldenRectangle(double width) {\n"
            + "        return new Rectangle(width, width * goldenRatio);\n"
            + "    }\n"
            + "}";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
    }
    
    @Test
    public void withNonStaticMembers() {
        String source = "record Rectangle(double length, double width) {\n"
            + "\n"
            + "    // Field declarations must be static:\n"
            + "    BiFunction<Double, Double, Double> diagonal;\n"
            + "\n"
            + "    // Instance initializers are not allowed in records:\n"
            + "    {\n"
            + "        diagonal = (x, y) -> Math.sqrt(x*x + y*y);\n"
            + "    }\n"
            + "}";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
    }

    @Test
    public void withNestedRecord() {
        String source = "record Rectangle(double length, double width) {\n"
            + "\n"
            + "    // Nested record class\n"
            + "    record RotationAngle(double angle) {\n"
            + "        public RotationAngle {\n"
            + "            angle = Math.toRadians(angle);\n"
            + "        }\n"
            + "    }\n"
            + "    \n"
            + "    // Public instance method\n"
            + "    public Rectangle getRotatedRectangleBoundingBox(double angle) {\n"
            + "        RotationAngle ra = new RotationAngle(angle);\n"
            + "        double x = Math.abs(length * Math.cos(ra.angle())) +\n"
            + "                   Math.abs(width * Math.sin(ra.angle()));\n"
            + "        double y = Math.abs(length * Math.sin(ra.angle())) +\n"
            + "                   Math.abs(width * Math.cos(ra.angle()));\n"
            + "        return new Rectangle(x, y);\n"
            + "    }\n"
            + "}";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
        JavaClass cls = javaDocBuilder.getClassByName( "Rectangle" );
        JavaClass nestedCls = cls.getNestedClassByName( "RotationAngle" );
        JavaConstructor constructor = nestedCls.getConstructors().get(0);
        Assertions.assertTrue( constructor.getSourceCode() != null );
    }

    @Test
    public void withGenerics() {
        String source = "record Triangle<C extends Coordinate> (C top, C left, C right) { }";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
    }

    @Test
    public void withInterface() {
        String source = "record Customer(String... data) implements Billable { }";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
    }

    @Test
    public void withAnnotatedParameters() {
        String source = "record Rectangle(\n"
            + "    @GreaterThanZero double length,\n"
            + "    @GreaterThanZero double width) { }";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
    }

    @Test
    public void withAnnotation() {
        String source = "@Deprecated\n"
            + "record Line(int lenght) { }";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
    }

    
    @Test
    public void recordAsTypeAndIdentifiers() {
        String source = "package record.record.record;\n"
            + "\n"
            + "public class record\n"
            + "{\n"
            + "    private Object record;\n"
            + "    \n"
            + "    public record() {\n"
            + "    }\n"
            + "    \n"
            + "    private Object record(Object record) {\n"
            + "        return record;\n"
            + "    }\n"
            + "}";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
    }

    @Test
    public void parametersContainingRecord() {
        String source = "interface Example{\n"
            + " void apply(Object recordList);"
            + "}";
        JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder();
        javaDocBuilder.addSource( new StringReader(source) );
    }
}