/* * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 8225055 * @summary Record types * @library /tools/lib ../../lib * @modules jdk.javadoc/jdk.javadoc.internal.tool * @build toolbox.ToolBox javadoc.tester.* * @compile --enable-preview --source ${jdk.version} TestRecordTypes.java * @run main/othervm --enable-preview TestRecordTypes */ import java.io.IOException; import java.lang.annotation.ElementType; import java.nio.file.Path; import java.util.EnumSet; import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; import javadoc.tester.JavadocTester; import toolbox.ToolBox; public class TestRecordTypes extends JavadocTester { public static void main(String... args) throws Exception { TestRecordTypes tester = new TestRecordTypes(); tester.runTests(m -> new Object[] { Path.of(m.getName()) }); } private final ToolBox tb = new ToolBox(); // The following constants are set up for use with -linkoffline // (but note: JDK 11 does not include java.lang.Record, so expect // some 404 broken links until we can update this to a stable version.) private static final String externalDocs = "https://docs.oracle.com/en/java/javase/11/docs/api"; private static final String localDocs = Path.of(testSrc).resolve("jdk11").toUri().toString(); @Test public void testRecordKeywordUnnamedPackage(Path base) throws IOException { Path src = base.resolve("src"); tb.writeJavaFiles(src, "public record R(int r1) { }"); javadoc("-d", base.resolve("out").toString(), "-quiet", "-noindex", "-sourcepath", src.toString(), "--enable-preview", "--source", thisRelease, src.resolve("R.java").toString()); checkExit(Exit.OK); checkOutput("R.html", true, "

Record R

", "public record R", "R​(int r1)"); } @Test public void testRecordKeywordNamedPackage(Path base) throws IOException { Path src = base.resolve("src"); tb.writeJavaFiles(src, "package p; public record R(int r1) { }"); javadoc("-d", base.resolve("out").toString(), "-quiet", "-noindex", "-sourcepath", src.toString(), "--enable-preview", "--source", thisRelease, "p"); checkExit(Exit.OK); checkOutput("p/R.html", true, "

Record R

", "public record R", "R​(int r1)"); } @Test public void testEmptyRecord(Path base) throws IOException { Path src = base.resolve("src"); tb.writeJavaFiles(src, "package p; public record R() { }"); javadoc("-d", base.resolve("out").toString(), "-quiet", "-noindex", "-sourcepath", src.toString(), "--enable-preview", "--source", thisRelease, "p"); checkExit(Exit.OK); checkOutput("p/R.html", true, "

Record R

", "public record R", "R()"); } @Test public void testAtParam(Path base) throws IOException { Path src = base.resolve("src"); tb.writeJavaFiles(src, "package p; /** This is record R. \n" + " * @param r1 This is a component.\n" + " */\n" + "public record R(int r1) { }"); javadoc("-d", base.resolve("out").toString(), "-quiet", "-noindex", "-sourcepath", src.toString(), "--enable-preview", "--source", thisRelease, "p"); checkExit(Exit.OK); checkOutput("p/R.html", true, "

Record R

", "public record R", "
\n" + "
Record Components:
\n" + "
r1 - This is a component.
\n" + "
", "R​(int r1)"); } @Test public void testAtParamTyParam(Path base) throws IOException { Path src = base.resolve("src"); tb.writeJavaFiles(src, "package p; /** This is record R. \n" + " * @param r1 This is a component.\n" + " * @param This is a type parameter.\n" + " */\n" + "public record R(int r1) { }"); javadoc("-d", base.resolve("out").toString(), "-quiet", "-noindex", "-sourcepath", src.toString(), "--enable-preview", "--source", thisRelease, "p"); checkExit(Exit.OK); checkOutput("p/R.html", true, "

Record R<T>

", "public record R<T>", "
\n" + "
Type Parameters:
\n" + "
T - This is a type parameter.
\n" + "
Record Components:
\n" + "
r1 - This is a component.
\n" + "
", "R​(int r1)"); } @Test public void testGeneratedComments(Path base) throws IOException { Path src = base.resolve("src"); tb.writeJavaFiles(src, "package p; /** This is record R. \n" + " * @param r1 This is a component.\n" + " */\n" + "public record R(int r1) { }"); javadoc("-d", base.resolve("out").toString(), "-quiet", "-noindex", "-sourcepath", src.toString(), "--enable-preview", "--source", thisRelease, "p"); checkExit(Exit.OK); // While we don't normally test values that just come from resource files, // in these cases, we want to verify that something non-empty was put into // the documentation for the generated members. checkOrder("p/R.html", "
", "R", "Creates an instance of a R record.", "
", "equals", "Indicates whether some other object is \"equal to\" this one.", "hashCode", "Returns a hash code value for this object.", "r1", "Returns the value of the r1 record component.", "toString", "Returns a string representation of this record.", "Method Details", "toString", "Returns a string representation of this record. The representation " + "contains the name of the type, followed by the name and value of " + "each of the record components.", "hashCode", "Returns a hash code value for this object. The value is derived " + "from the hash code of each of the record components.", "equals", "Indicates whether some other object is \"equal to\" this one. " + "The objects are equal if the other object is of the same class " + "and if all the record components are equal. All components " + "in this record are compared with '=='.", "r1", "Returns the value of the r1 " + "record component." ); } @Test public void testGeneratedCommentsWithLinkOffline(Path base) throws IOException { Path src = base.resolve("src"); tb.writeJavaFiles(src, "package p; /** This is record R. \n" + " * @param r1 This is a component.\n" + " */\n" + "public record R(int r1) { }"); javadoc("-d", base.resolve("out").toString(), "-quiet", "-noindex", "-sourcepath", src.toString(), "-linkoffline", externalDocs, localDocs, "--enable-preview", "--source", thisRelease, "p"); checkExit(Exit.OK); // While we don't normally test values that just come from resource files, // in these cases, we want to verify that something non-empty was put into // the documentation for the generated members. checkOrder("p/R.html", "
", "R", "Creates an instance of a R record.", "
", "equals", "Indicates whether some other object is \"equal to\" this one.", "hashCode", "Returns a hash code value for this object.", "r1", "Returns the value of the r1 record component.", "toString", "Returns a string representation of this record.", "Method Details", "toString", "Returns a string representation of this record. The representation " + "contains the name of the type, followed by the name and value of " + "each of the record components.", "hashCode", "Returns a hash code value for this object. The value is derived " + "from the hash code of each of the record components.", "equals", "Indicates whether some other object is \"equal to\" this one. " + "The objects are equal if the other object is of the same class " + "and if all the record components are equal. All components " + "in this record are compared with '=='.", "r1", "Returns the value of the r1 " + "record component." ); } @Test public void testGeneratedEqualsPrimitive(Path base) throws IOException { testGeneratedEquals(base, "int a, int b", "All components in this record are compared with '=='."); } @Test public void testGeneratedEqualsReference(Path base) throws IOException { testGeneratedEquals(base, "Object a, Object b", "All components in this record are compared with Objects::equals(Object,Object)"); } @Test public void testGeneratedEqualsMixed(Path base) throws IOException { testGeneratedEquals(base, "int a, Object b", "Reference components are compared with Objects::equals(Object,Object); " + "primitive components are compared with '=='."); } private void testGeneratedEquals(Path base, String comps, String expect) throws IOException { Path src = base.resolve("src"); tb.writeJavaFiles(src, "package p; /** This is record R. \n" + " */\n" + "public record R(" + comps + ") { }"); javadoc("-d", base.resolve("out").toString(), "-quiet", "-noindex", "-sourcepath", src.toString(), "--enable-preview", "--source", thisRelease, "p"); checkExit(Exit.OK); checkOrder("p/R.html", expect); } @Test public void testUserComments(Path base) throws IOException { Path src = base.resolve("src"); tb.writeJavaFiles(src, "package p; /** This is record R. \n" + " * @param r1 This is a component.\n" + " */\n" + "public record R(int r1) {\n" + "/** User constructor. */ public R { }\n" + "/** User equals. */ public boolean equals(Object other) { return false; }\n" + "/** User hashCode. */ public int hashCode() { return 0; }\n" + "/** User toString. */ public String toString() { return \"\"; }\n" + "/** User accessor. */ public int r1() { return r1; }\n" + "}"); javadoc("-d", base.resolve("out").toString(), "-quiet", "-noindex", "-sourcepath", src.toString(), "--enable-preview", "--source", thisRelease, "p"); checkExit(Exit.OK); checkOrder("p/R.html", "
", "R", "User constructor.", "
", "equals", "User equals.", "hashCode", "User hashCode.", "r1", "User accessor.", "toString", "User toString." ); } @Test public void testExamples(Path base) throws IOException { javadoc("-d", base.resolve("out-no-link").toString(), "-quiet", "-noindex", "-sourcepath", testSrc.toString(), "-linksource", "--enable-preview", "--source", thisRelease, "examples"); checkExit(Exit.OK); javadoc("-d", base.resolve("out-with-link").toString(), "-quiet", "-noindex", "-sourcepath", testSrc.toString(), "-linksource", "-linkoffline", externalDocs, localDocs, "--enable-preview", "--source", thisRelease, "examples"); checkExit(Exit.OK); } @Test @SuppressWarnings("preview") public void testAnnotations(Path base) throws IOException { ElementType[] types = { ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.RECORD_COMPONENT }; for (int i = 0; i < (1 << types.length); i++) { Set set = EnumSet.noneOf(ElementType.class); for (int b = 0; b < types.length; b++) { if ((i & (1 << b)) != 0) { set.add(types[b]); } } testAnnotations(base, set); } } void testAnnotations(Path base, Set types) throws IOException { out.println("test " + types); String name = types.isEmpty() ? "none" : types.stream() .map(k -> k.name().toLowerCase(Locale.US)) .collect(Collectors.joining("-")); Path dir = base.resolve(name); Path src = dir.resolve("src"); String target = types.isEmpty() ? "" : types.stream() .map(s -> "ElementType." + s) .collect(Collectors.joining(", ", "@Target({", "})")); tb.writeJavaFiles(src, "package p;\n" + "import java.lang.annotation.*;\n" + "@Documented\n" + target + "\n" + " public @interface Anno { }\n", "package p; public @interface UndocAnno { }", "package p; public record R(@Anno int i) { }\n"); javadoc("-d", dir.resolve("out").toString(), "-quiet", "-noindex", "-sourcepath", src.toString(), "-private", "--enable-preview", "--source", thisRelease, "p"); checkExit(Exit.OK); checkOutput("p/R.html", false, "UndocAnno"); Set t = types.isEmpty() ? EnumSet.allOf(ElementType.class) : types; String anno = "@Anno"; String rcAnno = t.contains(ElementType.RECORD_COMPONENT) ? anno + " " : ""; String fAnno = t.contains(ElementType.FIELD) ? "" + anno + "\n" : ""; String pAnno = t.contains(ElementType.PARAMETER) ? anno + "\n" : ""; String mAnno= t.contains(ElementType.METHOD) ? "" + anno + "\n" : ""; checkOutput("p/R.html", true, "
public record R("
                        + rcAnno
                        + "int i)\n" +
                        "extends java.lang.Record
", "
" + fAnno + "private final int" + " i
", "
public R" + "​(" + pAnno + "int i)
", "
" + mAnno + "public int" + " i()
"); } }