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 8219998 8221991
  27  * @summary Eliminate inherently singleton lists
  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 TestSingletonLists
  36  */
  37 
  38 import java.io.IOException;
  39 import java.io.PrintStream;
  40 import java.nio.file.Path;
  41 import java.util.ArrayList;
  42 import java.util.List;
  43 import java.util.Map;
  44 import java.util.Stack;
  45 import java.util.TreeMap;
  46 import java.util.function.Function;
  47 
  48 import javadoc.tester.HtmlChecker;
  49 import javadoc.tester.JavadocTester;
  50 import toolbox.ModuleBuilder;
  51 import toolbox.ToolBox;
  52 
  53 
  54 public class TestSingletonLists extends JavadocTester {
  55     public static void main(String... args) throws Exception {
  56         TestSingletonLists tester = new TestSingletonLists();
  57         tester.runTests();
  58     }
  59 
  60     enum Index  { SINGLE, SPLIT };
  61     enum Source { PACKAGES, MODULES };
  62 
  63     final ToolBox tb = new ToolBox();
  64 
  65     public void runTests() throws Exception {
  66         for (Source s : Source.values()) {
  67             Path src = genSource(s);
  68                 for (Index i : Index.values()) {
  69                     List<String> args = new ArrayList<>();
  70                     args.add("-d");
  71                     args.add(String.format("out-%s-%s", s, i));
  72                     args.add("-use");
  73                     if (s != Source.MODULES) {
  74                         args.add("-linksource"); // broken, with modules: JDK-8219060
  75                     }
  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("p1");
  83                         args.add("p2");
  84                         args.add("p3");
  85                     } else {
  86                         args.add("--module-source-path");
  87                         args.add(src.toString());
  88                         args.add("--module");
  89                         args.add("mA,mB,mC");
  90                     }
  91                     javadoc(args.toArray(new String[args.size()]));
  92                     checkExit(Exit.OK);
  93                     checkLists();
  94                 }
  95         }
  96 
  97         printSummary();
  98     }
  99 
 100     Path genSource(Source s) throws IOException {
 101         Path src = Path.of("src-" + s);
 102         switch (s) {
 103             case PACKAGES:
 104                 for (String p : new String[] { "1", "2", "3" }) {
 105                     tb.writeJavaFiles(src, genClasses("p" + p));
 106                 }
 107                 break;
 108 
 109             case MODULES:
 110                 for (String m : new String[] { "A", "B", "C"}) {
 111                     ModuleBuilder mb = new ModuleBuilder(tb, "m" + m);
 112                     for (String p : new String[] { "1", "2", "3" } ) {
 113                         mb.exports("p" + m + p);
 114                         mb.classes(genClasses("p" + m + p));
 115                     }
 116                     mb.write(src);
 117                 }
 118                 break;
 119         }
 120 
 121         return src;
 122     }
 123 
 124 
 125     String[] genClasses(String pkg) {
 126         List<String> list = new ArrayList<>();
 127         list.add("package " + pkg + ";");
 128         for (int i = 0; i < 3; i++) {
 129             list.add(genClass(pkg, i));
 130             list.add(genAnno(pkg, i));
 131             list.add(genEnum(pkg, i));
 132         }
 133         return list.toArray(new String[list.size()]);
 134     }
 135 
 136     String genClass(String pkg, int index) {
 137         String cn = (pkg + "c" + index).toUpperCase();
 138         StringBuilder sb = new StringBuilder();
 139         int pkgIndex = Character.getNumericValue(pkg.charAt(pkg.length()-1));
 140         String pkgdependency = pkg.substring(0, pkg.length()-1) + (pkgIndex == 3 ? 1 : pkgIndex + 1);
 141         String enumClassName = pkgdependency.toUpperCase() + "E" + index;
 142         sb.append("package ").append(pkg).append(";\n")
 143                 .append("import " + pkgdependency + ".*;\n")
 144                 .append("public class ").append(cn).append(" {\n");
 145         // fields
 146         for (int f = 0; f < 3; f++) {
 147             sb.append("public int f").append(f).append(";\n");
 148         }
 149         // constructors
 150         for (int c = 0; c < 3; c++) {
 151             sb.append("public ").append(cn).append("(");
 152             for (int i = 0; i < c; i++) {
 153                 sb.append(i == 0 ? "" : ", ").append("int i").append(i);
 154             }
 155             sb.append(") { }\n");
 156         }
 157         // methods
 158         for (int m = 0; m < 3; m++) {
 159             sb.append("public void m").append(m).append("() { }\n");
 160         }
 161         sb.append("public void n(").append(enumClassName).append(" e){}");
 162         sb.append("}\n");
 163         return sb.toString();
 164     }
 165 
 166     String genAnno(String pkg, int index) {
 167         String an = (pkg + "a" + index).toUpperCase();
 168         StringBuilder sb = new StringBuilder();
 169         sb.append("package ").append(pkg).append(";\n")
 170                 .append("public @interface ").append(an).append(" {\n");
 171         // fields
 172         for (int f = 0; f < 3; f++) {
 173             sb.append("public static final int f").append(f).append(" = 0;\n");
 174         }
 175             // values
 176         for (int v = 0; v < 6; v++) {
 177             sb.append("public int v").append(v).append("()").append(v< 3 ? "" :  " default " + v).append(";\n");
 178         }
 179         sb.append("}\n");
 180         return sb.toString();
 181     }
 182 
 183     String genEnum(String pkg, int index) {
 184         String en = (pkg + "e" + index).toUpperCase();
 185         StringBuilder sb = new StringBuilder();
 186         sb.append("package ").append(pkg).append(";\n")
 187                 .append("public enum ").append(en).append(" {\n");
 188              // enum members
 189         for (int e = 0; e < 3; e++) {
 190             sb.append(e == 0 ? "" : ", ").append("E").append(e);
 191         }
 192         sb.append(";\n");
 193         // fields
 194         for (int f = 0; f < 3; f++) {
 195             sb.append("public int f").append(f).append(";\n");
 196         }
 197         // methods
 198         for (int m = 0; m < 3; m++) {
 199             sb.append("public void m").append(m).append("() { }\n");
 200         }
 201         sb.append("}\n");
 202         return sb.toString();
 203     }
 204 
 205     void checkLists() {
 206         checking("Check lists");
 207         ListChecker c = new ListChecker(out, this::readFile);
 208         try {
 209             c.checkDirectory(outputDir.toPath());
 210             c.report();
 211             int errors = c.getErrorCount();
 212             if (errors == 0) {
 213                 passed("No list errors found");
 214             } else {
 215                 failed(errors + " errors found when checking lists");
 216             }
 217         } catch (IOException e) {
 218             failed("exception thrown when reading files: " + e);
 219         }
 220     }
 221 
 222     /**
 223      * A class to check the presence of singleton lists.
 224      */
 225     public class ListChecker extends HtmlChecker {
 226         private int listErrors;
 227 
 228         private boolean inBody;
 229         private boolean inNoScript;
 230         private Stack<Map<String,Integer>> counts = new Stack<>();
 231         private int regionErrors;
 232         private String fileName;
 233         private boolean inheritanceClass;
 234         private List<String> excludeFiles = List.of("overview-tree.html","package-tree.html","module-summary.html");
 235 
 236         ListChecker(PrintStream out, Function<Path,String> fileReader) {
 237             super(out, fileReader);
 238         }
 239 
 240         protected int getErrorCount() {
 241             return errors;
 242         }
 243 
 244         @Override
 245         public void report() {
 246             if (listErrors == 0) {
 247                 out.println("All lists OK");
 248             } else {
 249                 out.println(listErrors + " list errors");
 250             }
 251 
 252             if (regionErrors == 0) {
 253                 out.println("All regions OK");
 254             } else {
 255                 out.println(regionErrors + " errors in regions");
 256             }
 257         }
 258 
 259         @Override
 260         public void startFile(Path path) {
 261             fileName = path.getFileName().toString();
 262         }
 263 
 264         @Override
 265         public void endFile() {
 266         }
 267 
 268         @Override
 269         public void docType(String doctype) {
 270         }
 271 
 272         @Override
 273         public void startElement(String name, Map<String,String> attrs, boolean selfClosing) {
 274             switch (name) {
 275 
 276                 case "ul": case "ol": case "dl":
 277                     counts.push(new TreeMap<>());
 278                     break;
 279 
 280                 case "li": case "dd": case "dt": {
 281                     Map<String, Integer> c = counts.peek();
 282                     c.put(name, 1 + c.computeIfAbsent(name, n -> 0));
 283                     break;
 284                 }
 285             }
 286         }
 287 
 288         @Override
 289         public void endElement(String name) {
 290             switch (name) {
 291                 case "ul": case "ol": {
 292                     Map<String,Integer> c = counts.pop();
 293                     if (c.get("li") == 0) {
 294                         error(currFile, getLineNumber(), "empty list");
 295                     } else if (c.get("li") == 1 && fileName != null && !excludeFiles.contains(fileName)) {
 296                         error(currFile, getLineNumber(), "singleton list");
 297                     }
 298                     break;
 299                 }
 300 
 301                 case "dl": {
 302                     Map<String, Integer> c = counts.pop();
 303                     if (c.get("dd") == 0 || c.get("dt") == 0) {
 304                         error(currFile, getLineNumber(), "empty list");
 305                     }
 306                     /*if (c.get("dd") == 1 || c.get("dt") == 1) {
 307                         error(currFile, getLineNumber(), "singleton list");
 308                     }*/
 309                     break;
 310                 }
 311             }
 312         }
 313     }
 314 }