1+ /*
2+ * Copyright (c) 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+ /*
26+ * @test
27+ * @bug 8267574
28+ * @summary check stylesheet names against HtmlStyle
29+ * @modules jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.markup
30+ * jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources:open
31+ */
32+
33+ import java .io .BufferedWriter ;
34+ import java .io .IOException ;
35+ import java .io .InputStream ;
36+ import java .io .PrintWriter ;
37+ import java .net .URL ;
38+ import java .nio .file .Files ;
39+ import java .nio .file .Path ;
40+ import java .util .Arrays ;
41+ import java .util .Set ;
42+ import java .util .TreeSet ;
43+ import java .util .regex .Matcher ;
44+ import java .util .regex .Pattern ;
45+ import java .util .stream .Collectors ;
46+
47+ import jdk .javadoc .internal .doclets .formats .html .markup .HtmlStyle ;
48+
49+ /**
50+ * This test compares the set of CSS class names defined in HtmlStyle
51+ * and other files (such as search.js) against the set of CSS class names
52+ * defined in the main stylesheet.css provided by the doclet.
53+ *
54+ * The goal is to detect "unexpected" discrepancies between the two sets.
55+ * "Expected" discrepancies are taken into account, but may indicate a
56+ * need to resolve the discrepancy.
57+ *
58+ * The test does not take into direct account the recent introduction of
59+ * CSS constructs like section {@code [class$="-details"]}
60+ */
61+ public class CheckStylesheetClasses {
62+ public static void main (String ... args ) throws Exception {
63+ CheckStylesheetClasses c = new CheckStylesheetClasses ();
64+ c .run ();
65+ }
66+
67+ int errors = 0 ;
68+
69+ void run () throws Exception {
70+ Set <String > htmlStyleNames = getHtmlStyleNames ();
71+ Set <String > styleSheetNames = getStylesheetNames ();
72+
73+ System .err .println ("found " + htmlStyleNames .size () + " names in HtmlStyle" );
74+ System .err .println ("found " + styleSheetNames .size () + " names in stylesheet" );
75+
76+ // Write the lists to external files for the benefit of external diff tools:
77+ // for example, to compare against the CSS class names used in generated documentation.
78+ // To find the classes used in a directory containing HTML files, use something like
79+ // find $DIRECTORY -name \*.html | \
80+ // xargs grep -o 'class="[^"]*"' | \
81+ // sed -e 's/^[^"]*"//' -e 's/".*$//' | \
82+ // while read line ; do for w in $line ; do echo $w ; done ; done | \
83+ // sort -u
84+
85+ try (BufferedWriter out = Files .newBufferedWriter (Path .of ("htmlStyleNames.txt" ));
86+ PrintWriter pw = new PrintWriter (out )) {
87+ htmlStyleNames .forEach (pw ::println );
88+ }
89+
90+ try (BufferedWriter out = Files .newBufferedWriter (Path .of ("styleSheetNames.txt" ));
91+ PrintWriter pw = new PrintWriter (out )) {
92+ styleSheetNames .forEach (pw ::println );
93+ }
94+
95+ // Remove names from htmlStyleNames if they are valid names generated by the doclet,
96+ // even if they do not by default require a style to be defined in the stylesheet.
97+ // In general, names in these lists are worthy of attention to see if they *should*
98+ // be defined in the stylesheet, especially when the names exist in a family of
99+ // related items: related by name or by function.
100+
101+ // the page names are provided to override a style on a specific page;
102+ // only some are used in the stylesheet
103+ htmlStyleNames .removeIf (s -> s .endsWith ("-page" ) && !styleSheetNames .contains (s ));
104+
105+ // descriptions; class-description is used;
106+ // surprisingly? module-description and package-description are not
107+ htmlStyleNames .removeIf (s -> s .endsWith ("-description" ) && !styleSheetNames .contains (s ));
108+
109+ // help page
110+ htmlStyleNames .removeIf (s -> s .startsWith ("help-" ) && !styleSheetNames .contains (s ));
111+
112+ // summary and details tables; styles for these may be present in the stylesheet
113+ // using constructs like these:
114+ // .summary section[class$="-summary"], .details section[class$="-details"],
115+ htmlStyleNames .removeIf (s -> s .endsWith ("-details" ));
116+ htmlStyleNames .removeIf (s -> s .endsWith ("-summary" ) && !styleSheetNames .contains (s ));
117+
118+ // signature classes
119+ removeAll (htmlStyleNames , "annotations" , "element-name" , "extends-implements" ,
120+ "modifiers" , "permits" , "return-type" );
121+
122+ // misc: these are defined in HtmlStyle, and used by the doclet
123+ removeAll (htmlStyleNames , "col-plain" , "details-table" , "external-link" ,
124+ "hierarchy" , "index" , "package-uses" , "packages" , "permits-note" ,
125+ "serialized-package-container" , "source-container" );
126+
127+ // Remove names from styleSheetNames if they are false positives,
128+ // or used by other code (i.e. not HtmlStyle),
129+ // or if they are unused and therefore candidates to be deleted.
130+
131+ // false positives: file extensions and URL components
132+ removeAll (styleSheetNames , "css" , "png" , "w3" );
133+
134+ // for doc-comment authors; maybe worthy of inclusion in HtmlStyle, just to be documented
135+ removeAll (styleSheetNames , "borderless" , "plain" , "striped" );
136+
137+ // used in search.js; may be worth documenting in HtmlStyle
138+ removeAll (styleSheetNames , "result-highlight" , "result-item" ,
139+ "search-tag-desc-result" , "search-tag-holder-result" ,
140+ "ui-autocomplete" , "ui-autocomplete-category" ,
141+ "watermark" );
142+
143+ // very JDK specific
144+ styleSheetNames .remove ("module-graph" );
145+
146+ // apparently unused
147+ // "tab" is commented implying it is in the header/footer, but
148+ // (a) it is a poorly chosen name
149+ // (b) it does not seem to be used in make/Docs.gmk or anywhere else
150+ removeAll (styleSheetNames , "all-classes-container" , "all-packages-container" ,
151+ "bottom-nav" , "clear" , "constant-values-container" , "deprecated-content" ,
152+ "footer" , "hidden" , "override-specify-label" , "serialized-class-details" ,
153+ "tab" , "table-sub-heading-color" );
154+
155+ boolean ok = check (htmlStyleNames , "HtmlStyle" , styleSheetNames , "stylesheet" )
156+ & check (styleSheetNames , "stylesheet" , htmlStyleNames , "HtmlStyle" );
157+
158+ if (!ok ) {
159+ throw new Exception ("differences found" );
160+ }
161+
162+ if (errors > 0 ) {
163+ throw new Exception (errors + " errors found" );
164+ }
165+ }
166+
167+ boolean check (Set <String > s1 , String l1 , Set <String > s2 , String l2 ) {
168+ boolean equal = true ;
169+ for (String s : s1 ) {
170+ if (!s2 .contains (s )) {
171+ System .err .println ("In " + l1 + " but not " + l2 + ": " + s );
172+ equal = false ;
173+ }
174+ }
175+ return equal ;
176+ }
177+
178+ /**
179+ * Remove all the names from the set, giving a message for any that were not found.
180+ */
181+ void removeAll (Set <String > set , String ... names ) {
182+ for (String name : names ) {
183+ if (!set .remove (name )) {
184+ error ("name not found in set: " + name );
185+ }
186+ }
187+ }
188+
189+ void error (String message ) {
190+ System .err .println ("error: " + message );
191+ errors ++;
192+ }
193+
194+ Set <String > getHtmlStyleNames () {
195+ return Arrays .stream (HtmlStyle .values ())
196+ .map (HtmlStyle ::cssName )
197+ .collect (Collectors .toCollection (TreeSet ::new ));
198+ }
199+
200+ Set <String > getStylesheetNames () throws IOException {
201+ Set <String > names = new TreeSet <>();
202+ String stylesheet = "/jdk/javadoc/internal/doclets/toolkit/resources/stylesheet.css" ;
203+ URL url = HtmlStyle .class .getResource (stylesheet );
204+ readStylesheet (url , names );
205+ return names ;
206+ }
207+
208+ private void readStylesheet (URL resource , Set <String > names ) throws IOException {
209+ try (InputStream in = resource .openStream ()) {
210+ if (in == null ) {
211+ throw new AssertionError ("Cannot find or access resource " + resource );
212+ }
213+ String s = new String (in .readAllBytes ());
214+ Pattern p = Pattern .compile ("(?i)(\\ s|([a-z][a-z0-9-]*))\\ .(?<name>[a-z0-9-]+)\\ b" );
215+ Matcher m = p .matcher (s );
216+ while (m .find ()) {
217+ names .add (m .group ("name" ));
218+ }
219+ }
220+ }
221+ }
0 commit comments