1 /*
   2  * Copyright (c) 2016, 2017, 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 8162353 8164747 8173707
  27  * @summary javadoc should provide a way to disable use of frames
  28  * @library /tools/lib ../lib
  29  * @modules
  30  *      jdk.compiler/com.sun.tools.javac.api
  31  *      jdk.compiler/com.sun.tools.javac.main
  32  *      jdk.javadoc/jdk.javadoc.internal.tool
  33  * @build toolbox.ModuleBuilder toolbox.ToolBox
  34  * @build JavadocTester
  35  * @run main TestFramesNoFrames
  36  */
  37 
  38 import java.io.*;
  39 import java.lang.annotation.Annotation;
  40 import java.lang.reflect.InvocationTargetException;
  41 import java.lang.reflect.Method;
  42 import java.nio.file.*;
  43 import java.util.*;
  44 import java.util.stream.Collectors;
  45 
  46 import toolbox.ModuleBuilder;
  47 import toolbox.ToolBox;
  48 
  49 public class TestFramesNoFrames extends JavadocTester {
  50 
  51     public static void main(String... args) throws Exception {
  52         TestFramesNoFrames tester = new TestFramesNoFrames();
  53         tester.generateSource();
  54         tester.runTests();
  55     }
  56 
  57     ToolBox tb = new ToolBox();
  58     Path gensrcModules = Paths.get("gensrc/modules");
  59     Path gensrcPackages = Paths.get("gensrc/packages");
  60 
  61     void generateSource() throws IOException {
  62         String[] modules = { "", "m1", "m2", "m3" };
  63         String[] packages = { "p1", "p2", "p3" };
  64         String[] classes = { "C1", "C2", "C3" };
  65         for (String m: modules) {
  66             ModuleBuilder mb = m.equals("") ? null : new ModuleBuilder(tb, m);
  67             for (String p: packages) {
  68                 Path pkgRoot;
  69                 if (m.equals("")) {
  70                     pkgRoot = gensrcPackages;
  71                 } else {
  72                     pkgRoot = gensrcModules.resolve(m);
  73                     mb.exports(m + p);
  74                 }
  75                 for (String c: classes) {
  76                     tb.writeJavaFiles(pkgRoot,
  77                         "package " + (m + p) + ";\n"
  78                         + "/** class " + (m + p + c).toUpperCase() + ". */\n"
  79                         + "public class " + (m + p + c).toUpperCase() + " { }"
  80                     );
  81                 }
  82             }
  83             if (!m.equals("")) {
  84                 mb.write(gensrcModules);
  85             }
  86         }
  87         tb.writeFile("overview.html",
  88                 "<html><body>This is the overview file</body></html>");
  89     }
  90 
  91     enum FrameKind {
  92         DEFAULT(),
  93         FRAMES("--frames"),
  94         NO_FRAMES("--no-frames");
  95         FrameKind(String... opts) {
  96             this.opts = Arrays.asList(opts);
  97         }
  98         final List<String> opts;
  99     }
 100 
 101     enum OverviewKind {
 102         DEFAULT(),
 103         OVERVIEW("-overview", "overview.html"),
 104         NO_OVERVIEW("-nooverview");
 105         OverviewKind(String... opts) {
 106             this.opts = Arrays.asList(opts);
 107         }
 108         final List<String> opts;
 109     }
 110 
 111     enum HtmlKind {
 112         HTML4("-html4"),
 113         HTML5("-html5");
 114         HtmlKind(String... opts) {
 115             this.opts = Arrays.asList(opts);
 116         }
 117         final List<String> opts;
 118     }
 119 
 120     @Override
 121     public void runTests() throws Exception {
 122         for (Method m : getClass().getDeclaredMethods()) {
 123             Annotation a = m.getAnnotation(Test.class);
 124             if (a != null) {
 125                 for (FrameKind fk : FrameKind.values()) {
 126                     for (OverviewKind ok : OverviewKind.values()) {
 127                         for (HtmlKind hk : HtmlKind.values()) {
 128                             try {
 129                                 out.println("Running test " + m.getName() + " " + fk + " " + ok + " " + hk);
 130                                 Path base = Paths.get(m.getName() + "_" + fk + "_" + ok + "_" + hk);
 131                                 Files.createDirectories(base);
 132                                 m.invoke(this, new Object[]{base, fk, ok, hk});
 133                             } catch (InvocationTargetException e) {
 134                                 Throwable cause = e.getCause();
 135                                 throw (cause instanceof Exception) ? ((Exception) cause) : e;
 136                             }
 137                             out.println();
 138                         }
 139                     }
 140                 }
 141             }
 142         }
 143         printSummary();
 144     }
 145 
 146     void javadoc(Path outDir, FrameKind fKind, OverviewKind oKind, HtmlKind hKind, String... rest) {
 147         List<String> args = new ArrayList<>();
 148         args.add("-d");
 149         args.add(outDir.toString());
 150         args.addAll(fKind.opts);
 151         args.addAll(oKind.opts);
 152         args.addAll(hKind.opts);
 153         args.addAll(Arrays.asList(rest));
 154         javadoc(args.toArray(new String[0]));
 155         checkExit(Exit.OK);
 156     }
 157 
 158     @Test
 159     void testClass(Path base, FrameKind fKind, OverviewKind oKind, HtmlKind hKind) throws Exception {
 160         javadoc(base, fKind, oKind, hKind,
 161             gensrcPackages.resolve("p1/P1C1.java").toString());
 162 
 163         new Checker(fKind, oKind, hKind)
 164             .classes("p1.P1C1")
 165             .check();
 166     }
 167 
 168     @Test
 169     void testClasses(Path base, FrameKind fKind, OverviewKind oKind, HtmlKind hKind) throws IOException {
 170         javadoc(base, fKind, oKind, hKind,
 171             gensrcPackages.resolve("p1/P1C1.java").toString(),
 172             gensrcPackages.resolve("p1/P1C2.java").toString(),
 173             gensrcPackages.resolve("p1/P1C3.java").toString());
 174 
 175         new Checker(fKind, oKind, hKind)
 176             .classes("p1.P1C1", "p1.P1C2", "p1.P1C3")
 177             .check();
 178     }
 179 
 180     @Test
 181     void testPackage(Path base, FrameKind fKind, OverviewKind oKind, HtmlKind hKind) throws IOException {
 182         javadoc(base, fKind, oKind, hKind,
 183             "-sourcepath", gensrcPackages.toString(),
 184             "p1");
 185 
 186         new Checker(fKind, oKind, hKind)
 187             .classes("p1.P1C1", "p1.P1C2", "p1.P1C3")
 188             .check();
 189     }
 190 
 191     @Test
 192     void testPackages(Path base, FrameKind fKind, OverviewKind oKind, HtmlKind hKind) throws IOException {
 193         javadoc(base, fKind, oKind, hKind,
 194             "-sourcepath", gensrcPackages.toString(),
 195             "p1", "p2", "p3");
 196 
 197         new Checker(fKind, oKind, hKind)
 198             .classes("p1.P1C1", "p1.P1C2", "p1.P1C3",
 199                     "p2.P2C1", "p2.P2C2", "p2.P2C3",
 200                     "p3.P3C1", "p3.P3C2", "p3.P3C3")
 201             .check();
 202     }
 203 
 204     @Test
 205     void testModules(Path base, FrameKind fKind, OverviewKind oKind, HtmlKind hKind) throws IOException {
 206         javadoc(base, fKind, oKind, hKind,
 207             "--module-source-path", gensrcModules.toString(),
 208             "--module", "m1,m2,m3");
 209 
 210         new Checker(fKind, oKind, hKind)
 211             .classes("m1/m1p1.M1P1C1", "m1/m1p1.M1P1C2", "m1/m1p1.M1P1C3",
 212                     "m2/m2p1.M2P1C1", "m2/m2p1.M2P1C2", "m2/m2p1.M2P1C3",
 213                     "m3/m3p1.M3P1C1", "m3/m3p1.M3P1C2", "m3/m3p1.M3P1C3")
 214             .check();
 215     }
 216 
 217     /**
 218      * Check the contents of the generated output, according to the
 219      * specified options.
 220      */
 221     class Checker {
 222         private final FrameKind fKind;
 223         private final OverviewKind oKind;
 224         private final HtmlKind hKind;
 225         List<String> classes;
 226 
 227         private boolean frames;
 228         private boolean overview;
 229 
 230         Checker(FrameKind fKind, OverviewKind oKind, HtmlKind hKind) {
 231             this.fKind = fKind;
 232             this.oKind = oKind;
 233             this.hKind = hKind;
 234         }
 235 
 236         Checker classes(String... classes) {
 237             this.classes = Arrays.asList(classes);
 238             return this;
 239         }
 240 
 241         void check() throws IOException {
 242             switch (fKind) {
 243                 case DEFAULT:
 244                 case FRAMES:
 245                     frames = true;
 246                     break;
 247 
 248                 case NO_FRAMES:
 249                     frames = false;
 250                     break;
 251             }
 252 
 253             switch (oKind) {
 254                 case DEFAULT:
 255                     overview = (getPackageCount() > 1);
 256                     break;
 257 
 258                 case OVERVIEW:
 259                     overview = true;
 260                     break;
 261 
 262                 case NO_OVERVIEW:
 263                     overview = false;
 264                     break;
 265             }
 266 
 267             checkAllClassesFiles();
 268             checkFrameFiles();
 269             checkOverviewSummary();
 270 
 271             checkIndex();
 272             checkNavBar();
 273             checkHelpDoc();
 274 
 275         }
 276 
 277         private void checkAllClassesFiles() {
 278             // these files are only generated in frames mode
 279             checkFiles(frames,
 280                     "allclasses-frame.html",
 281                     "allclasses-noframe.html");
 282 
 283             // this file is only generated when not in frames mode
 284             checkFiles(!frames,
 285                     "allclasses.html");
 286 
 287             if (frames) {
 288                 checkOutput("allclasses-frame.html", true,
 289                         classes.stream()
 290                             .map(c -> "title=\"class in " + packagePart(c) + "\" target=\"classFrame\">" + classPart(c) + "</a>")
 291                             .toArray(String[]::new));
 292                 checkOutput("allclasses-noframe.html", false,
 293                             "target=\"classFrame\">");
 294             } else {
 295                 checkOutput("allclasses.html", false,
 296                             "target=\"classFrame\">");
 297 
 298             }
 299         }
 300 
 301         private void checkFrameFiles() {
 302             // these files are all only generated in frames mode
 303 
 304             // <module>/module-frame.html and <module>/module-type-frame.html files
 305             checkFiles(frames, classes.stream()
 306                 .filter(c -> isInModule(c))
 307                 .map(c -> modulePart(c))
 308                 .flatMap(m -> Arrays.asList(
 309                         m + "/module-frame.html",
 310                         m + "/module-type-frame.html").stream())
 311                 .collect(Collectors.toSet()));
 312 
 313             // <package>/package-frame.html files
 314             checkFiles(frames, classes.stream()
 315                     .map(c -> (isInModule(c) ? (modulePart(c) + "/") : "") + packagePart(c) + "/package-frame.html")
 316                     .collect(Collectors.toSet()));
 317         }
 318 
 319         private void checkHelpDoc() {
 320             // the Help page only describes Frame/NoFrames in frames mode
 321             checkOutput("help-doc.html", frames,
 322                         "<h2>Frames/No Frames</h2>");
 323         }
 324 
 325         private void checkIndex() {
 326             // the index.html page only contains frames and Javascript to default to no-frames view,
 327             // in frames mode
 328             checkOutput("index.html", frames,
 329                     "<iframe ",
 330                     "</iframe>",
 331                     "<body onload=\"loadFrames()\">\n"
 332                     + "<script type=\"text/javascript\">\n"
 333                     + "if (targetPage == \"\" || targetPage == \"undefined\")");
 334 
 335             // the index.html contains the overview if one
 336             // has been given, and not in frames mode
 337             checkOutput("index.html", !frames && oKind == OverviewKind.OVERVIEW,
 338                     "This is the overview file");
 339 
 340             // the index.html file contains a summary table
 341             // if an overview was generated and not in frames mode
 342             checkOutput("index.html", !frames && overview,
 343                     "<table class=\"overviewSummary\"");
 344 
 345             // the index.html file contains a redirect if
 346             // no frames and no overview
 347             checkOutput("index.html", !frames && !overview,
 348                     "<meta http-equiv=\"Refresh\" content=\"0;",
 349                     "<script type=\"text/javascript\">window.location.replace(");
 350 
 351             // the index.html file <meta> refresh should only use <noscript> in HTML 5
 352             if (!frames && !overview) {
 353                 checkOutput("index.html", hKind == HtmlKind.HTML5,
 354                         "<noscript>\n<meta http-equiv=\"Refresh\" content=\"0;");
 355             }
 356         }
 357 
 358         private void checkNavBar() {
 359             // the files containing a navigation bar should only
 360             // contain FRAMES/NO-FRAMES links in frames mode
 361             List<String> navbarFiles = new ArrayList<>();
 362             navbarFiles.addAll(classes.stream()
 363                     .map(c -> (isInModule(c) ? (modulePart(c) + "/") : "") + toHtml(packageClassPart(c)))
 364                     .collect(Collectors.toSet()));
 365             for (String f : navbarFiles) {
 366                 checkOutput(f, frames,
 367                         "target=\"_top\">Frames</a>",
 368                         "target=\"_top\">No&nbsp;Frames</a>");
 369             }
 370         }
 371 
 372         private void checkOverviewSummary() {
 373             // the overview-summary.html file only appears if
 374             // in frames mode and (overview requested or multiple packages)
 375             checkFiles(frames && overview,
 376                     "overview-summary.html");
 377         }
 378 
 379         private long getPackageCount() {
 380             return this.classes.stream()
 381                 .filter(name -> name.contains("."))
 382                 .map(name -> name.substring(0, name.lastIndexOf(".")))
 383                 .distinct()
 384                 .count();
 385         }
 386 
 387         private String classPart(String className) {
 388             int lastDot = className.lastIndexOf(".");
 389             return className.substring(lastDot + 1);
 390         }
 391 
 392         private String packagePart(String className) {
 393             int slash = className.indexOf("/");
 394             int lastDot = className.lastIndexOf(".");
 395             return className.substring(slash + 1, lastDot);
 396         }
 397 
 398         private String packageClassPart(String className) {
 399             int slash = className.indexOf("/");
 400             return className.substring(slash + 1);
 401         }
 402 
 403         private boolean isInModule(String className) {
 404             return className.contains("/");
 405         }
 406 
 407         private String modulePart(String className) {
 408             int slash = className.indexOf("/");
 409             return className.substring(0, slash);
 410         }
 411 
 412         private String toHtml(String className) {
 413             return className.replace(".", "/") + ".html";
 414         }
 415     }
 416 }