1 /* 2 * Copyright (c) 2019, 2020, 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 8218998 8219946 8219060 8241190 8242056 8254627 27 * @summary Add metadata to generated API documentation files 28 * @library /tools/lib ../../lib 29 * @modules jdk.javadoc/jdk.javadoc.internal.tool 30 * @modules jdk.compiler/com.sun.tools.javac.api 31 * jdk.compiler/com.sun.tools.javac.main 32 * jdk.javadoc/jdk.javadoc.internal.api 33 * jdk.javadoc/jdk.javadoc.internal.tool 34 * @build toolbox.ToolBox toolbox.JavacTask javadoc.tester.* 35 * @run main TestMetadata 36 */ 37 38 import java.io.IOException; 39 import java.nio.file.Path; 40 import java.util.ArrayList; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Set; 44 import java.util.TreeSet; 45 import java.util.regex.Matcher; 46 import java.util.regex.Pattern; 47 import java.util.stream.Collectors; 48 49 import toolbox.ModuleBuilder; 50 import toolbox.ToolBox; 51 52 import javadoc.tester.JavadocTester; 53 54 public class TestMetadata extends JavadocTester { main(String... args)55 public static void main(String... args) throws Exception { 56 TestMetadata tester = new TestMetadata(); 57 tester.runTests(); 58 } 59 60 enum Index { SINGLE, SPLIT }; 61 enum Source { PACKAGES, MODULES }; 62 63 final ToolBox tb = new ToolBox(); 64 final Set<String> allBodyClassesFound = new HashSet<>(); 65 final Set<String> allGeneratorsFound = new HashSet<>(); 66 runTests()67 public void runTests() throws Exception { 68 for (Source s : Source.values()) { 69 Path src = genSource(s); 70 for (Index i : Index.values()) { 71 List<String> args = new ArrayList<>(); 72 args.add("-d"); 73 args.add(String.format("out-%s-%s", s, i)); 74 args.add("-use"); 75 args.add("-linksource"); 76 if (i == Index.SPLIT) { 77 args.add("-splitIndex"); 78 } 79 if (s == Source.PACKAGES) { 80 args.add("-sourcepath"); 81 args.add(src.toString()); 82 args.add("pA"); 83 args.add("pB"); 84 } else { 85 args.add("--module-source-path"); 86 args.add(src.toString()); 87 args.add("--module"); 88 args.add("mA,mB"); 89 } 90 javadoc(args.toArray(new String[args.size()])); 91 checkExit(Exit.OK); 92 checkBodyClasses(); 93 checkMetadata(); 94 95 // spot check the descriptions for declarations 96 switch (s) { 97 case PACKAGES: 98 checkOutput("pA/package-summary.html", true, 99 """ 100 <meta name="description" content="declaration: package: pA">"""); 101 checkOutput("pA/CA.html", true, 102 """ 103 <meta name="description" content="declaration: package: pA, class: CA">"""); 104 break; 105 106 case MODULES: 107 checkOutput("mA/module-summary.html", true, 108 """ 109 <meta name="description" content="declaration: module: mA">"""); 110 checkOutput("mA/pA/package-summary.html", true, 111 """ 112 <meta name="description" content="declaration: module: mA, package: pA">"""); 113 checkOutput("mA/pA/CA.html", true, 114 """ 115 <meta name="description" content="declaration: module: mA, package: pA, class: CA">"""); 116 break; 117 } 118 } 119 } 120 121 checking ("all generators"); 122 if (allGeneratorsFound.equals(allGenerators)) { 123 passed("all generators found"); 124 } else { 125 Set<String> notFound = new TreeSet<>(allGenerators); 126 notFound.removeAll(allGeneratorsFound); 127 failed("not found: " + notFound); 128 } 129 130 checking ("all body classes"); 131 if (allBodyClassesFound.equals(allBodyClasses)) { 132 passed("all body classes found"); 133 } else { 134 Set<String> notFound = new TreeSet<>(allBodyClasses); 135 notFound.removeAll(allBodyClassesFound); 136 failed("not found: " + notFound); 137 } 138 139 printSummary(); 140 } 141 142 final Pattern nl = Pattern.compile("[\\r\\n]+"); 143 final Pattern bodyPattern = Pattern.compile("<body [^>]*class=\"([^\"]+)\""); 144 final Set<String> allBodyClasses = Set.of( 145 "all-classes-index-page", 146 "all-packages-index-page", 147 "class-declaration-page", 148 "class-use-page", 149 "constants-summary-page", 150 "deprecated-list-page", 151 "doc-file-page", 152 "help-page", 153 "index-page", 154 "index-redirect-page", 155 "module-declaration-page", 156 "module-index-page", 157 "package-declaration-page", 158 "package-index-page", 159 "package-tree-page", 160 "package-use-page", 161 "serialized-form-page", 162 "source-page", 163 "system-properties-page", 164 "tree-page" 165 ); 166 checkBodyClasses()167 void checkBodyClasses() throws IOException { 168 Path outputDirPath = outputDir.toPath(); 169 for (Path p : tb.findFiles(".html", outputDirPath)) { 170 checkBodyClass(outputDirPath.relativize(p)); 171 } 172 } 173 checkBodyClass(Path p)174 void checkBodyClass(Path p) { 175 checking("Check body: " + p); 176 177 List<String> bodyLines = nl.splitAsStream(readOutputFile(p.toString())) 178 .filter(s -> s.contains("<body class=")) 179 .collect(Collectors.toList()); 180 181 String bodyLine; 182 switch (bodyLines.size()) { 183 case 0: 184 failed("Not found: <body class="); 185 return; 186 case 1: 187 bodyLine = bodyLines.get(0); 188 break; 189 default: 190 failed("Multiple found: <body class="); 191 return; 192 } 193 194 Matcher m = bodyPattern.matcher(bodyLine); 195 if (m.find()) { 196 String bodyClass = m.group(1); 197 if (allBodyClasses.contains(bodyClass)) { 198 passed("found: " + bodyClass); 199 allBodyClassesFound.add(bodyClass); 200 } else { 201 failed("Unrecognized body class: " + bodyClass); 202 } 203 } else { 204 failed("Unrecognized line:\n" + bodyLine); 205 } 206 } 207 208 final Pattern contentPattern = Pattern.compile("content=\"([^\"]+)\">"); 209 final Pattern generatorPattern = Pattern.compile("content=\"javadoc/([^\"]+)\">"); 210 final Set<String> allGenerators = Set.of( 211 "AllClassesIndexWriter", 212 "AllPackagesIndexWriter", 213 "ClassUseWriter", 214 "ClassWriterImpl", 215 "ConstantsSummaryWriterImpl", 216 "DeprecatedListWriter", 217 "DocFileWriter", 218 "HelpWriter", 219 "IndexRedirectWriter", 220 "IndexWriter", 221 "ModuleIndexWriter", 222 "ModuleWriterImpl", 223 "PackageIndexWriter", 224 "PackageTreeWriter", 225 "PackageUseWriter", 226 "PackageWriterImpl", 227 "SerializedFormWriterImpl", 228 "SourceToHTMLConverter", 229 "SystemPropertiesWriter", 230 "TreeWriter" 231 ); 232 checkMetadata()233 void checkMetadata() throws IOException { 234 Path outputDirPath = outputDir.toPath(); 235 for (Path p : tb.findFiles(".html", outputDirPath)) { 236 checkMetadata(outputDirPath.relativize(p)); 237 } 238 } 239 checkMetadata(Path p)240 void checkMetadata(Path p) { 241 checking("Check generator: " + p); 242 243 List<String> generators = nl.splitAsStream(readOutputFile(p.toString())) 244 .filter(s -> s.contains("<meta name=\"generator\"")) 245 .collect(Collectors.toList()); 246 247 String generator; 248 switch (generators.size()) { 249 case 0: 250 failed(""" 251 Not found: <meta name="generator\""""); 252 return; 253 case 1: 254 generator = generators.get(0); 255 break; 256 default: 257 failed(""" 258 Multiple found: <meta name="generator\""""); 259 return; 260 } 261 262 Matcher m = generatorPattern.matcher(generator); 263 if (m.find()) { 264 String content = m.group(1); 265 if (allGenerators.contains(content)) { 266 passed("found: " + content); 267 allGeneratorsFound.add(content); 268 checkDescription(p, content); 269 } else { 270 failed("Unrecognized content: " + content); 271 } 272 } else { 273 failed("Unrecognized line:\n" + generator); 274 } 275 276 } 277 checkDescription(Path p, String generator)278 void checkDescription(Path p, String generator) { 279 checking("Check description: " + p); 280 281 List<String> descriptions = nl.splitAsStream(readOutputFile(p.toString())) 282 .filter(s -> s.contains("<meta name=\"description\"")) 283 .collect(Collectors.toList()); 284 285 String description; 286 switch (descriptions.size()) { 287 case 0: 288 if (generator.equals("DocFileWriter")) { 289 passed("Not found, as expected"); 290 } else { 291 failed(""" 292 Not found: <meta name="description\""""); 293 } 294 return; 295 case 1: 296 description = descriptions.get(0); 297 break; 298 default: 299 failed(""" 300 Multiple found: <meta name="description\""""); 301 return; 302 } 303 304 String content; 305 Matcher m = contentPattern.matcher(description); 306 if (m.find()) { 307 content = m.group(1); 308 } else { 309 failed("Unrecognized line:\n" + description); 310 return; 311 } 312 313 switch (generator) { 314 case "AllClassesIndexWriter": 315 case "AllPackagesIndexWriter": 316 case "ModuleIndexWriter": 317 case "PackageIndexWriter": 318 check(generator, content, content.contains("index")); 319 break; 320 321 322 case "AnnotationTypeWriterImpl": 323 case "ClassWriterImpl": 324 case "ModuleWriterImpl": 325 case "PackageWriterImpl": 326 check(generator, content, content.startsWith("declaration: ")); 327 break; 328 329 case "ClassUseWriter": 330 case "PackageUseWriter": 331 check(generator, content, content.startsWith("use: ")); 332 break; 333 334 case "ConstantsSummaryWriterImpl": 335 check(generator, content, content.contains("constants")); 336 break; 337 338 case "DeprecatedListWriter": 339 check(generator, content, content.contains("deprecated")); 340 break; 341 342 case "DocFileWriter": 343 passed("no constraint for user-provided doc-files"); 344 break; 345 346 case "HelpWriter": 347 check(generator, content, content.contains("help")); 348 break; 349 350 case "IndexRedirectWriter": 351 check(generator, content, content.contains("redirect")); 352 break; 353 354 case "IndexWriter": 355 check(generator, content, content.startsWith("index")); 356 break; 357 358 case "PackageTreeWriter": 359 case "TreeWriter": 360 check(generator, content, content.contains("tree")); 361 break; 362 363 case "SerializedFormWriterImpl": 364 check(generator, content, content.contains("serialized")); 365 break; 366 367 case "SourceToHTMLConverter": 368 check(generator, content, content.startsWith("source:")); 369 break; 370 371 case "SystemPropertiesWriter": 372 check(generator, content, content.contains("system properties")); 373 break; 374 375 default: 376 failed("unexpected generator: " + generator); 377 break; 378 } 379 } 380 check(String generator, String content, boolean ok)381 void check(String generator, String content, boolean ok) { 382 if (ok) { 383 passed("OK: " + generator + " " + content); 384 } else { 385 failed("unexpected value for " + generator + ": " + content); 386 } 387 } 388 genSource(Source s)389 Path genSource(Source s) throws IOException { 390 Path src = Path.of("src-" + s); 391 switch (s) { 392 case PACKAGES: 393 tb.writeJavaFiles(src, 394 "/** Package pA. {@systemProperty exampleProperty} */ package pA;", 395 "/** Class pA.CA. */ package pA; public class CA { @Deprecated public static final int ZERO = 0; }", 396 "/** Anno pA.Anno, */ package pA; public @interface Anno { }", 397 "/** Serializable pA.Ser, */ package pA; public class Ser implements java.io.Serializable { }", 398 "/** Package pB. */ package pB;", 399 "/** Class pB.CB. */ package pB; public class CB { }"); 400 tb.writeFile(src.resolve("pA").resolve("doc-files").resolve("extra.html"), 401 """ 402 <!doctype html> 403 <html><head></head><body>Extra</body></html>"""); 404 break; 405 406 case MODULES: 407 new ModuleBuilder(tb, "mA") 408 .exports("pA") 409 .classes("/** Package mA/pA. */ package pA;") 410 .classes("/** Class mA/pA.CA. */ package pA; public class CA { @Deprecated public static int ZERO = 0; }") 411 .write(src); 412 new ModuleBuilder(tb, "mB") 413 .exports("pB") 414 .classes("/** Package mB/pB. */ package pB;") 415 .classes("/** Class mB/pB.CB. */ package pB; public class CB { }") 416 .write(src); 417 break; 418 } 419 420 return src; 421 } 422 } 423