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