1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 8225055 27 * @summary Record types 28 * @library /tools/lib ../../lib 29 * @modules jdk.javadoc/jdk.javadoc.internal.tool 30 * @build toolbox.ToolBox javadoc.tester.* 31 * @compile --enable-preview --source ${jdk.version} TestRecordTypes.java 32 * @run main/othervm --enable-preview TestRecordTypes 33 */ 34 35 36 import java.io.IOException; 37 import java.lang.annotation.ElementType; 38 import java.nio.file.Path; 39 import java.util.EnumSet; 40 import java.util.Locale; 41 import java.util.Set; 42 import java.util.stream.Collectors; 43 44 import javadoc.tester.JavadocTester; 45 import toolbox.ToolBox; 46 47 public class TestRecordTypes extends JavadocTester { 48 public static void main(String... args) throws Exception { 49 TestRecordTypes tester = new TestRecordTypes(); 50 tester.runTests(m -> new Object[] { Path.of(m.getName()) }); 51 } 52 53 private final ToolBox tb = new ToolBox(); 54 55 // The following constants are set up for use with -linkoffline 56 // (but note: JDK 11 does not include java.lang.Record, so expect 57 // some 404 broken links until we can update this to a stable version.) 58 private static final String externalDocs = 59 "https://docs.oracle.com/en/java/javase/11/docs/api"; 60 private static final String localDocs = 61 Path.of(testSrc).resolve("jdk11").toUri().toString(); 62 63 @Test 64 public void testRecordKeywordUnnamedPackage(Path base) throws IOException { 65 Path src = base.resolve("src"); 66 tb.writeJavaFiles(src, 67 "public record R(int r1) { }"); 68 69 javadoc("-d", base.resolve("out").toString(), 70 "-quiet", "-noindex", 71 "-sourcepath", src.toString(), 72 "--enable-preview", "--source", thisRelease, 73 src.resolve("R.java").toString()); 74 checkExit(Exit.OK); 75 76 checkOutput("R.html", true, 77 "<h1 title=\"Record R\" class=\"title\">Record R</h1>", 78 "public record <span class=\"typeNameLabel\">R</span>", 79 "<code><span class=\"memberNameLink\"><a href=\"#%3Cinit%3E(int)\">R</a></span>(int r1)</code>"); 80 } 81 82 @Test 83 public void testRecordKeywordNamedPackage(Path base) throws IOException { 84 Path src = base.resolve("src"); 85 tb.writeJavaFiles(src, 86 "package p; public record R(int r1) { }"); 87 88 javadoc("-d", base.resolve("out").toString(), 89 "-quiet", "-noindex", 90 "-sourcepath", src.toString(), 91 "--enable-preview", "--source", thisRelease, 92 "p"); 93 checkExit(Exit.OK); 94 95 checkOutput("p/R.html", true, 96 "<h1 title=\"Record R\" class=\"title\">Record R</h1>", 97 "public record <span class=\"typeNameLabel\">R</span>", 98 "<code><span class=\"memberNameLink\"><a href=\"#%3Cinit%3E(int)\">R</a></span>(int r1)</code>"); 99 } 100 101 @Test 102 public void testEmptyRecord(Path base) throws IOException { 103 Path src = base.resolve("src"); 104 tb.writeJavaFiles(src, 105 "package p; public record R() { }"); 106 107 javadoc("-d", base.resolve("out").toString(), 108 "-quiet", "-noindex", 109 "-sourcepath", src.toString(), 110 "--enable-preview", "--source", thisRelease, 111 "p"); 112 checkExit(Exit.OK); 113 114 checkOutput("p/R.html", true, 115 "<h1 title=\"Record R\" class=\"title\">Record R</h1>", 116 "public record <span class=\"typeNameLabel\">R</span>", 117 "<code><span class=\"memberNameLink\"><a href=\"#%3Cinit%3E()\">R</a></span>()</code>"); 118 } 119 120 @Test 121 public void testAtParam(Path base) throws IOException { 122 Path src = base.resolve("src"); 123 tb.writeJavaFiles(src, 124 "package p; /** This is record R. \n" 125 + " * @param r1 This is a component.\n" 126 + " */\n" 127 + "public record R(int r1) { }"); 128 129 javadoc("-d", base.resolve("out").toString(), 130 "-quiet", "-noindex", 131 "-sourcepath", src.toString(), 132 "--enable-preview", "--source", thisRelease, 133 "p"); 134 checkExit(Exit.OK); 135 136 checkOutput("p/R.html", true, 137 "<h1 title=\"Record R\" class=\"title\">Record R</h1>", 138 "public record <span class=\"typeNameLabel\">R</span>", 139 "<dl>\n" 140 + "<dt><span class=\"paramLabel\">Record Components:</span></dt>\n" 141 + "<dd><code><a id=\"param-r1\">r1</a></code> - This is a component.</dd>\n" 142 + "</dl>", 143 "<code><span class=\"memberNameLink\"><a href=\"#%3Cinit%3E(int)\">R</a></span>(int r1)</code>"); 144 } 145 146 @Test 147 public void testAtParamTyParam(Path base) throws IOException { 148 Path src = base.resolve("src"); 149 tb.writeJavaFiles(src, 150 "package p; /** This is record R. \n" 151 + " * @param r1 This is a component.\n" 152 + " * @param <T> This is a type parameter.\n" 153 + " */\n" 154 + "public record R<T>(int r1) { }"); 155 156 javadoc("-d", base.resolve("out").toString(), 157 "-quiet", "-noindex", 158 "-sourcepath", src.toString(), 159 "--enable-preview", "--source", thisRelease, 160 "p"); 161 checkExit(Exit.OK); 162 163 checkOutput("p/R.html", true, 164 "<h1 title=\"Record R\" class=\"title\">Record R<T></h1>", 165 "public record <span class=\"typeNameLabel\">R<T></span>", 166 "<dl>\n" 167 + "<dt><span class=\"paramLabel\">Type Parameters:</span></dt>\n" 168 + "<dd><code>T</code> - This is a type parameter.</dd>\n" 169 + "<dt><span class=\"paramLabel\">Record Components:</span></dt>\n" 170 + "<dd><code><a id=\"param-r1\">r1</a></code> - This is a component.</dd>\n" 171 + "</dl>", 172 "<code><span class=\"memberNameLink\"><a href=\"#%3Cinit%3E(int)\">R</a></span>(int r1)</code>"); 173 } 174 175 @Test 176 public void testGeneratedComments(Path base) throws IOException { 177 Path src = base.resolve("src"); 178 tb.writeJavaFiles(src, 179 "package p; /** This is record R. \n" 180 + " * @param r1 This is a component.\n" 181 + " */\n" 182 + "public record R(int r1) { }"); 183 184 javadoc("-d", base.resolve("out").toString(), 185 "-quiet", "-noindex", 186 "-sourcepath", src.toString(), 187 "--enable-preview", "--source", thisRelease, 188 "p"); 189 checkExit(Exit.OK); 190 191 // While we don't normally test values that just come from resource files, 192 // in these cases, we want to verify that something non-empty was put into 193 // the documentation for the generated members. 194 checkOrder("p/R.html", 195 "<section class=\"constructorSummary\">", 196 "<a href=\"#%3Cinit%3E(int)\">R</a>", 197 "Creates an instance of a <code>R</code> record.", 198 "<section class=\"methodSummary\">", 199 "<a href=\"#equals(java.lang.Object)\">equals</a>", 200 "Indicates whether some other object is \"equal to\" this one.", 201 "<a href=\"#hashCode()\">hashCode</a>", 202 "Returns a hash code value for this object.", 203 "<a href=\"#r1()\">r1</a>", 204 "Returns the value of the <a href=\"#param-r1\"><code>r1</code></a> record component.", 205 "<a href=\"#toString()\">toString</a>", 206 "Returns a string representation of this record.", 207 "Method Details", 208 "<span class=\"memberName\">toString</span>", 209 "Returns a string representation of this record. The representation " 210 + "contains the name of the type, followed by the name and value of " 211 + "each of the record components.", 212 "<span class=\"memberName\">hashCode</span>", 213 "Returns a hash code value for this object. The value is derived " 214 + "from the hash code of each of the record components.", 215 "<span class=\"memberName\">equals</span>", 216 "Indicates whether some other object is \"equal to\" this one. " 217 + "The objects are equal if the other object is of the same class " 218 + "and if all the record components are equal. All components " 219 + "in this record are compared with '=='.", 220 "<span class=\"memberName\">r1</span>", 221 "Returns the value of the <a href=\"#param-r1\"><code>r1</code></a> " 222 + "record component." 223 ); 224 } 225 226 @Test 227 public void testGeneratedCommentsWithLinkOffline(Path base) throws IOException { 228 Path src = base.resolve("src"); 229 tb.writeJavaFiles(src, 230 "package p; /** This is record R. \n" 231 + " * @param r1 This is a component.\n" 232 + " */\n" 233 + "public record R(int r1) { }"); 234 235 javadoc("-d", base.resolve("out").toString(), 236 "-quiet", "-noindex", 237 "-sourcepath", src.toString(), 238 "-linkoffline", externalDocs, localDocs, 239 "--enable-preview", "--source", thisRelease, 240 "p"); 241 checkExit(Exit.OK); 242 243 // While we don't normally test values that just come from resource files, 244 // in these cases, we want to verify that something non-empty was put into 245 // the documentation for the generated members. 246 checkOrder("p/R.html", 247 "<section class=\"constructorSummary\">", 248 "<a href=\"#%3Cinit%3E(int)\">R</a>", 249 "Creates an instance of a <code>R</code> record.", 250 "<section class=\"methodSummary\">", 251 "<a href=\"#equals(java.lang.Object)\">equals</a>", 252 "Indicates whether some other object is \"equal to\" this one.", 253 "<a href=\"#hashCode()\">hashCode</a>", 254 "Returns a hash code value for this object.", 255 "<a href=\"#r1()\">r1</a>", 256 "Returns the value of the <a href=\"#param-r1\"><code>r1</code></a> record component.", 257 "<a href=\"#toString()\">toString</a>", 258 "Returns a string representation of this record.", 259 "Method Details", 260 "<span class=\"memberName\">toString</span>", 261 "Returns a string representation of this record. The representation " 262 + "contains the name of the type, followed by the name and value of " 263 + "each of the record components.", 264 "<span class=\"memberName\">hashCode</span>", 265 "Returns a hash code value for this object. The value is derived " 266 + "from the hash code of each of the record components.", 267 "<span class=\"memberName\">equals</span>", 268 "Indicates whether some other object is \"equal to\" this one. " 269 + "The objects are equal if the other object is of the same class " 270 + "and if all the record components are equal. All components " 271 + "in this record are compared with '=='.", 272 "<span class=\"memberName\">r1</span>", 273 "Returns the value of the <a href=\"#param-r1\"><code>r1</code></a> " 274 + "record component." 275 ); 276 } 277 278 @Test 279 public void testGeneratedEqualsPrimitive(Path base) throws IOException { 280 testGeneratedEquals(base, "int a, int b", 281 "All components in this record are compared with '=='."); 282 } 283 284 @Test 285 public void testGeneratedEqualsReference(Path base) throws IOException { 286 testGeneratedEquals(base, "Object a, Object b", 287 "All components in this record are compared with <code>Objects::equals(Object,Object)</code>"); 288 } 289 290 @Test 291 public void testGeneratedEqualsMixed(Path base) throws IOException { 292 testGeneratedEquals(base, "int a, Object b", 293 "Reference components are compared with <code>Objects::equals(Object,Object)</code>; " 294 + "primitive components are compared with '=='."); 295 } 296 297 private void testGeneratedEquals(Path base, String comps, String expect) throws IOException { 298 Path src = base.resolve("src"); 299 tb.writeJavaFiles(src, 300 "package p; /** This is record R. \n" 301 + " */\n" 302 + "public record R(" + comps + ") { }"); 303 304 javadoc("-d", base.resolve("out").toString(), 305 "-quiet", "-noindex", 306 "-sourcepath", src.toString(), 307 "--enable-preview", "--source", thisRelease, 308 "p"); 309 checkExit(Exit.OK); 310 311 checkOrder("p/R.html", expect); 312 } 313 314 @Test 315 public void testUserComments(Path base) throws IOException { 316 Path src = base.resolve("src"); 317 tb.writeJavaFiles(src, 318 "package p; /** This is record R. \n" 319 + " * @param r1 This is a component.\n" 320 + " */\n" 321 + "public record R(int r1) {\n" 322 + "/** User constructor. */ public R { }\n" 323 + "/** User equals. */ public boolean equals(Object other) { return false; }\n" 324 + "/** User hashCode. */ public int hashCode() { return 0; }\n" 325 + "/** User toString. */ public String toString() { return \"\"; }\n" 326 + "/** User accessor. */ public int r1() { return r1; }\n" 327 + "}"); 328 329 javadoc("-d", base.resolve("out").toString(), 330 "-quiet", "-noindex", 331 "-sourcepath", src.toString(), 332 "--enable-preview", "--source", thisRelease, 333 "p"); 334 checkExit(Exit.OK); 335 336 checkOrder("p/R.html", 337 "<section class=\"constructorSummary\">", 338 "<a href=\"#%3Cinit%3E(int)\">R</a>", 339 "User constructor.", 340 "<section class=\"methodSummary\">", 341 "<a href=\"#equals(java.lang.Object)\">equals</a>", 342 "User equals.", 343 "<a href=\"#hashCode()\">hashCode</a>", 344 "User hashCode.", 345 "<a href=\"#r1()\">r1</a>", 346 "User accessor.", 347 "<a href=\"#toString()\">toString</a>", 348 "User toString." 349 ); 350 } 351 352 @Test 353 public void testExamples(Path base) throws IOException { 354 javadoc("-d", base.resolve("out-no-link").toString(), 355 "-quiet", "-noindex", 356 "-sourcepath", testSrc.toString(), 357 "-linksource", 358 "--enable-preview", "--source", thisRelease, 359 "examples"); 360 361 checkExit(Exit.OK); 362 javadoc("-d", base.resolve("out-with-link").toString(), 363 "-quiet", "-noindex", 364 "-sourcepath", testSrc.toString(), 365 "-linksource", 366 "-linkoffline", externalDocs, localDocs, 367 "--enable-preview", "--source", thisRelease, 368 "examples"); 369 checkExit(Exit.OK); 370 } 371 372 @Test 373 @SuppressWarnings("preview") 374 public void testAnnotations(Path base) throws IOException { 375 ElementType[] types = { 376 ElementType.FIELD, 377 ElementType.METHOD, 378 ElementType.PARAMETER, 379 ElementType.RECORD_COMPONENT 380 }; 381 for (int i = 0; i < (1 << types.length); i++) { 382 Set<ElementType> set = EnumSet.noneOf(ElementType.class); 383 for (int b = 0; b < types.length; b++) { 384 if ((i & (1 << b)) != 0) { 385 set.add(types[b]); 386 } 387 } 388 testAnnotations(base, set); 389 } 390 } 391 392 void testAnnotations(Path base, Set<ElementType> types) throws IOException { 393 out.println("test " + types); 394 String name = types.isEmpty() ? "none" : types.stream() 395 .map(k -> k.name().toLowerCase(Locale.US)) 396 .collect(Collectors.joining("-")); 397 Path dir = base.resolve(name); 398 Path src = dir.resolve("src"); 399 String target = types.isEmpty() ? "" : types.stream() 400 .map(s -> "ElementType." + s) 401 .collect(Collectors.joining(", ", "@Target({", "})")); 402 tb.writeJavaFiles(src, 403 "package p;\n" 404 + "import java.lang.annotation.*;\n" 405 + "@Documented\n" 406 + target + "\n" 407 + " public @interface Anno { }\n", 408 "package p; public @interface UndocAnno { }", 409 "package p; public record R(@Anno int i) { }\n"); 410 411 javadoc("-d", dir.resolve("out").toString(), 412 "-quiet", "-noindex", 413 "-sourcepath", src.toString(), 414 "-private", 415 "--enable-preview", "--source", thisRelease, 416 "p"); 417 checkExit(Exit.OK); 418 419 checkOutput("p/R.html", false, 420 "UndocAnno"); 421 422 Set<ElementType> t = types.isEmpty() ? EnumSet.allOf(ElementType.class) : types; 423 String anno = "<a href=\"Anno.html\" title=\"annotation in p\">@Anno</a>"; 424 String rcAnno = t.contains(ElementType.RECORD_COMPONENT) ? anno + " " : ""; 425 String fAnno = t.contains(ElementType.FIELD) ? "<span class=\"annotations\">" + anno + "\n</span>" : ""; 426 String pAnno = t.contains(ElementType.PARAMETER) ? anno + "\n" : ""; 427 String mAnno= t.contains(ElementType.METHOD) ? "<span class=\"annotations\">" + anno + "\n</span>" : ""; 428 429 checkOutput("p/R.html", true, 430 "<pre>public record <span class=\"typeNameLabel\">R</span>(" 431 + rcAnno 432 + "int i)\n" + 433 "extends java.lang.Record</pre>", 434 "<div class=\"memberSignature\">" 435 + fAnno 436 + "<span class=\"modifiers\">private final</span> <span class=\"returnType\">int</span>" 437 + " <span class=\"memberName\">i</span></div>", 438 "<div class=\"memberSignature\"><span class=\"modifiers\">public</span> <span class=\"memberName\">R</span>" 439 + "(<span class=\"arguments\">" 440 + pAnno 441 + "int i)</span></div>", 442 "<div class=\"memberSignature\">" 443 + mAnno 444 + "<span class=\"modifiers\">public</span> <span class=\"returnType\">int</span>" 445 + " <span class=\"memberName\">i</span>()</div>"); 446 447 } 448 }