1 /*
2  * Copyright (c) 2005, 2021, 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      4494033 7028815 7052425 8007338 8023608 8008164 8016549 8072461 8154261 8162363 8160196 8151743 8177417
27  *           8175218 8176452 8181215 8182263 8183511 8169819 8183037 8185369 8182765 8196201 8184205 8223378 8241544
28  *           8253117 8263528
29  * @summary  Run tests on doclet stylesheet.
30  * @library  /tools/lib ../../lib
31  * @modules jdk.javadoc/jdk.javadoc.internal.tool
32  * @build    toolbox.ToolBox javadoc.tester.*
33  * @run main TestStylesheet
34  */
35 
36 import java.io.IOException;
37 import java.io.PrintStream;
38 import java.nio.file.Path;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.TreeSet;
43 import java.util.function.Function;
44 
45 import javadoc.tester.HtmlChecker;
46 import javadoc.tester.JavadocTester;
47 import toolbox.ToolBox;
48 
49 public class TestStylesheet extends JavadocTester {
50 
main(String... args)51     public static void main(String... args) throws Exception {
52         TestStylesheet tester = new TestStylesheet();
53         tester.runTests(m -> new Object[] { Path.of(m.getName())});
54     }
55 
56     @Test
test(Path base)57     public void test(Path base) {
58         javadoc("-d", base.resolve("out").toString(),
59                 "-sourcepath", testSrc,
60                 "pkg");
61         checkExit(Exit.ERROR);
62 
63         checkOutput(Output.OUT, true,
64                 "attribute not supported in HTML5: name");
65 
66         // TODO: most of this test seems a bit silly, since javadoc is simply
67         // copying in the stylesheet from the source directory
68         checkOutput("stylesheet.css", true,
69                 """
70                     body {
71                         background-color:#ffffff;
72                         color:#353833;
73                         font-family:'DejaVu Sans', Arial, Helvetica, sans-serif;
74                         font-size:14px;
75                         margin:0;
76                         padding:0;
77                         height:100%;
78                         width:100%;
79                     }""",
80                 """
81                     iframe {
82                         margin:0;
83                         padding:0;
84                         height:100%;
85                         width:100%;
86                         overflow-y:scroll;
87                         border:none;
88                     }""",
89                 "ul {\n"
90                 + "    list-style-type:disc;\n"
91                 + "}",
92                 """
93                     .caption {
94                         position:relative;
95                         text-align:left;
96                         background-repeat:no-repeat;
97                         color:#253441;
98                         font-weight:bold;
99                         clear:none;
100                         overflow:hidden;
101                         padding:0;
102                         padding-top:10px;
103                         padding-left:1px;
104                         margin:0;
105                         white-space:pre;
106                     }""",
107                 """
108                     .caption span {
109                         white-space:nowrap;
110                         padding-top:5px;
111                         padding-left:12px;
112                         padding-right:12px;
113                         padding-bottom:7px;
114                         display:inline-block;
115                         float:left;
116                         background-color:#F8981D;
117                         border: none;
118                         height:16px;
119                     }""",
120                 """
121                     div.table-tabs > button {
122                        border: none;
123                        cursor: pointer;
124                        padding: 5px 12px 7px 12px;
125                        font-weight: bold;
126                        margin-right: 3px;
127                     }
128                     div.table-tabs > button.active-table-tab {
129                        background: #F8981D;
130                        color: #253441;
131                     }
132                     div.table-tabs > button.table-tab {
133                        background: #4D7A97;
134                        color: #FFFFFF;
135                     }""",
136                 // Test the formatting styles for proper content display in use and constant values pages.
137                 """
138                     .col-first, .col-second, .col-constructor-name {
139                         vertical-align:top;
140                         overflow: auto;
141                     }""",
142                 """
143                     .summary-table > div, .details-table > div {
144                         text-align:left;
145                         padding: 8px 3px 3px 7px;
146                     }""",
147                 "@import url('resources/fonts/dejavu.css');",
148                 """
149                     .search-tag-result:target {
150                         background-color:yellow;
151                     }""",
152                 """
153                     a[href]:hover, a[href]:focus {
154                         text-decoration:none;
155                         color:#bb7a2a;
156                     }""",
157                 """
158                     .col-first a:link, .col-first a:visited,
159                     .col-second a:link, .col-second a:visited,
160                     .col-first a:link, .col-first a:visited,
161                     .col-second a:link, .col-second a:visited,
162                     .col-constructor-name a:link, .col-constructor-name a:visited,
163                     .col-summary-item-name a:link, .col-summary-item-name a:visited,
164                     .constant-values-container a:link, .constant-values-container a:visited,
165                     .all-classes-container a:link, .all-classes-container a:visited,
166                     .all-packages-container a:link, .all-packages-container a:visited {
167                         font-weight:bold;
168                     }""",
169                 """
170                     .deprecation-block {
171                         font-size:14px;
172                         font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
173                         border-style:solid;
174                         border-width:thin;
175                         border-radius:10px;
176                         padding:10px;
177                         margin-bottom:10px;
178                         margin-right:10px;
179                         display:inline-block;
180                     }""",
181                 """
182                     #reset-button {
183                         background-color: rgb(255,255,255);
184                         background-image:url('resources/x.png');
185                         background-position:center;
186                         background-repeat:no-repeat;
187                         background-size:12px;
188                         border:0 none;
189                         width:16px;
190                         height:16px;
191                         position:relative;
192                         left:-4px;
193                         top:-4px;
194                         font-size:0px;
195                     }""",
196                 """
197                     .watermark {
198                         color:#545454;
199                     }""");
200 
201         checkOutput("pkg/A.html", true,
202                 // Test whether a link to the stylesheet file is inserted properly
203                 // in the class documentation.
204                 """
205                     <link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">""",
206                 """
207                     <div class="block">Test comment for a class which has an <a name="named_anchor">anchor_with_name</a> and
208                      an <a id="named_anchor1">anchor_with_id</a>.</div>""");
209 
210         checkOutput("pkg/package-summary.html", true,
211                 """
212                     <div class="col-last even-row-color class-summary class-summary-tab2">
213                     <div class="block">Test comment for a class which has an <a name="named_anchor">anchor_with_name</a> and
214                      an <a id="named_anchor1">anchor_with_id</a>.</div>
215                     </div>""");
216 
217         checkOutput("index.html", true,
218                 """
219                     <link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">""");
220 
221         checkOutput("stylesheet.css", false,
222                 """
223                     * {
224                         margin:0;
225                         padding:0;
226                     }""",
227                 """
228                     a:active {
229                         text-decoration:none;
230                         color:#4A6782;
231                     }""",
232                 """
233                     a[name]:hover {
234                         text-decoration:none;
235                         color:#353833;
236                     }""",
237                 """
238                     td.col-first a:link, td.col-first a:visited,
239                     td.col-second a:link, td.col-second a:visited,
240                     th.col-first a:link, th.col-first a:visited,
241                     th.col-second a:link, th.col-second a:visited,
242                     th.col-constructor-name a:link, th.col-constructor-name a:visited,
243                     td.col-last a:link, td.col-last a:visited,
244                     .constant-values-container td a:link, .constant-values-container td a:visited {
245                         font-weight:bold;
246                     }""");
247     }
248 
249     ToolBox tb = new ToolBox();
250 
251     @Test
testStyles(Path base)252     public void testStyles(Path base) throws Exception {
253         Path src = base.resolve("src");
254         tb.writeJavaFiles(src,
255                 "module mA { exports p; }",
256                 """
257                     package p; public class C {
258                     public C() { }
259                     public C(int i) { }
260                     public int f1;
261                     public int f2;
262                     public int m1() { }
263                     public int m2(int i) { }
264                     }
265                     """,
266                 """
267                     package p; public @interface Anno {
268                     public int value();
269                     }
270                     """
271         );
272 
273         javadoc("-d", base.resolve("out").toString(),
274                 "-sourcepath", src.toString(),
275                 "--module", "mA");
276         checkExit(Exit.OK);
277         checkStyles(addExtraCSSClassNamesTo(readStylesheet()));
278     }
279 
readStylesheet()280     Set<String> readStylesheet() {
281         // scan for class selectors, skipping '{' ... '}'
282         Set<String> styles = new TreeSet<>();
283         String stylesheet = readFile("stylesheet.css");
284         for (int i = 0; i < stylesheet.length(); i++) {
285             char ch = stylesheet.charAt(i);
286             switch (ch) {
287                 case '.':
288                     i++;
289                     int start = i;
290                     while (i < stylesheet.length()) {
291                         ch = stylesheet.charAt(i);
292                         if (!(Character.isLetterOrDigit(ch) || ch == '-')) {
293                             break;
294                         }
295                         i++;
296                     }
297                     styles.add(stylesheet.substring(start, i));
298                     break;
299 
300                 case '{':
301                     i++;
302                     while (i < stylesheet.length()) {
303                         ch = stylesheet.charAt(i);
304                         if (ch == '}') {
305                             break;
306                         }
307                         i++;
308                     }
309                     break;
310 
311                 case '@':
312                     i++;
313                     while (i < stylesheet.length()) {
314                         ch = stylesheet.charAt(i);
315                         if (ch == '{') {
316                             break;
317                         }
318                         i++;
319                     }
320                     break;
321             }
322         }
323         out.println("found styles: " + styles);
324         return styles;
325     }
326 
addExtraCSSClassNamesTo(Set<String> styles)327     Set<String> addExtraCSSClassNamesTo(Set<String> styles) throws Exception {
328         // The following names are used in the generated HTML,
329         // but have no corresponding definitions in the stylesheet file.
330         // They are mostly optional, in the "use if you want to" category.
331         // They are included here so that we do not get errors when these
332         // names are used in the generated HTML.
333         List<String> extra = List.of(
334                 // entries for <body> elements
335                 "all-classes-index-page",
336                 "all-packages-index-page",
337                 "constants-summary-page",
338                 "deprecated-list-page",
339                 "help-page",
340                 "index-redirect-page",
341                 "package-declaration-page",
342                 "package-tree-page",
343                 "single-index-page",
344                 "tree-page",
345                 // the following names are matched by [class$='...'] in the stylesheet
346                 "constructor-details",
347                 "constructor-summary",
348                 "field-details",
349                 "field-summary",
350                 "member-details",
351                 "method-details",
352                 "method-summary",
353                 // the following provide the ability to optionally override components of the
354                 // memberSignature structure
355                 "name",
356                 "modifiers",
357                 "packages",
358                 "return-type",
359                 // and others...
360                 "help-section",     // part of the help page
361                 "hierarchy",        // for the hierarchy on a tree page
362                 "index"             // on the index page
363         );
364         Set<String> all = new TreeSet<>(styles);
365         for (String e : extra) {
366             if (styles.contains(e)) {
367                 throw new Exception("extra CSS class name found in style sheet: " + e);
368             }
369             all.add(e);
370         }
371         return all;
372     }
373 
374     /**
375      * Checks that all the CSS names found in {@code class} attributes in HTML files in the
376      * output directory are present in a given set of styles.
377      *
378      * @param styles the styles
379      */
checkStyles(Set<String> styles)380     void checkStyles(Set<String> styles) {
381         checking("Check CSS class names");
382         CSSClassChecker c = new CSSClassChecker(out, this::readFile, styles);
383         try {
384             c.checkDirectory(outputDir.toPath());
385             c.report();
386             int errors = c.getErrorCount();
387             if (errors == 0) {
388                 passed("No CSS class name errors found");
389             } else {
390                 failed(errors + " errors found when checking CSS class names");
391             }
392         } catch (IOException e) {
393             failed("exception thrown when reading files: " + e);
394         }
395 
396     }
397 
398     class CSSClassChecker extends HtmlChecker {
399         Set<String> styles;
400         int errors;
401 
CSSClassChecker(PrintStream out, Function<Path, String> fileReader, Set<String> styles)402         protected CSSClassChecker(PrintStream out,
403                                   Function<Path, String> fileReader,
404                                   Set<String> styles) {
405             super(out, fileReader);
406             this.styles = styles;
407         }
408 
getErrorCount()409         protected int getErrorCount() {
410             return errors;
411         }
412 
413         @Override
report()414         protected void report() {
415             if (getErrorCount() == 0) {
416                 out.println("All CSS class names found");
417             } else {
418                 out.println(getErrorCount() + " CSS class names not found");
419             }
420 
421         }
422 
423         @Override
startElement(String name, Map<String,String> attrs, boolean selfClosing)424         public void startElement(String name, Map<String,String> attrs, boolean selfClosing) {
425             String style = attrs.get("class");
426             if (style != null && !styles.contains(style)) {
427                 error(currFile, getLineNumber(), "CSS class name not found: " + style);
428             }
429         }
430     }
431 }
432