ModuleSummary.java revision 16177:89ef4b822745
1/*
2 * Copyright (c) 2014, 2016, 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package build.tools.jigsaw;
27
28import java.io.IOException;
29import java.io.PrintStream;
30import java.lang.module.Configuration;
31import java.lang.module.ModuleDescriptor;
32import java.lang.module.ModuleFinder;
33import java.lang.module.ModuleReference;
34import java.lang.module.ResolvedModule;
35import java.nio.file.Files;
36import java.nio.file.Path;
37import java.nio.file.Paths;
38import java.util.Arrays;
39import java.util.Comparator;
40import java.util.Date;
41import java.util.Enumeration;
42import java.util.HashMap;
43import java.util.HashSet;
44import java.util.Map;
45import java.util.Set;
46import java.util.stream.Collectors;
47import java.util.zip.ZipEntry;
48import java.util.zip.ZipFile;
49import static java.lang.module.ModuleDescriptor.*;
50import static build.tools.jigsaw.ModuleSummary.HtmlDocument.Selector.*;
51import static build.tools.jigsaw.ModuleSummary.HtmlDocument.Division.*;
52
53public class ModuleSummary {
54    private static final String USAGE = "Usage: ModuleSummary --module-path <dir> -o <outfile> [--root mn]*";
55
56    public static void main(String[] args) throws Exception {
57        int i=0;
58        Path modpath = null;
59        Path outfile = null;
60        Set<String> roots = new HashSet<>();
61        while (i < args.length && args[i].startsWith("-")) {
62            String arg = args[i++];
63            switch (arg) {
64                case "--module-path":
65                    modpath = Paths.get(args[i++]);
66                    break;
67                case "-o":
68                    outfile = Paths.get(args[i++]);
69                    break;
70                case "--root":
71                    roots.add(args[i++]);
72                default:
73                    System.err.println(USAGE);
74                    System.exit(-1);
75            }
76        }
77        if (outfile == null || modpath == null) {
78            System.err.println(USAGE);
79            System.exit(1);
80        }
81        Path dir = outfile.getParent() != null ? outfile.getParent() : Paths.get(".");
82        Files.createDirectories(dir);
83
84        Map<String, ModuleSummary> modules = new HashMap<>();
85        Set<ModuleReference> mrefs = ModuleFinder.ofSystem().findAll();
86        for (ModuleReference mref : mrefs) {
87            String mn = mref.descriptor().name();
88            Path jmod = modpath.resolve(mn + ".jmod");
89            modules.put(mn, new ModuleSummary(mref, jmod));
90        }
91
92        if (roots.isEmpty()) {
93            roots.addAll(modules.keySet());
94        }
95        genReport(outfile, modules, roots, "JDK Module Summary");
96    }
97
98    static void genReport(Path outfile, Map<String, ModuleSummary> modules, Set<String> roots, String title)
99        throws IOException
100    {
101        Configuration cf = resolve(roots);
102        try (PrintStream out = new PrintStream(Files.newOutputStream(outfile))) {
103            HtmlDocument doc = new HtmlDocument(title, modules);
104            Set<ModuleDescriptor> descriptors = cf.modules().stream()
105                    .map(ResolvedModule::reference)
106                    .map(ModuleReference::descriptor)
107                    .collect(Collectors.toSet());
108            doc.writeTo(out, descriptors);
109        }
110    }
111
112    private final String name;
113    private final ModuleDescriptor descriptor;
114    private final JmodInfo jmodInfo;
115    ModuleSummary(ModuleReference mref, Path jmod) throws IOException {
116        this.name = mref.descriptor().name();
117        this.descriptor = mref.descriptor();
118        this.jmodInfo = new JmodInfo(jmod);
119    }
120
121    String name() {
122        return name;
123    }
124
125    long uncompressedSize() {
126        return jmodInfo.size;
127    }
128
129    long jmodFileSize() {
130        return jmodInfo.filesize; // estimated compressed size
131    }
132
133    ModuleDescriptor descriptor() {
134        return descriptor;
135    }
136
137    int numClasses() {
138        return jmodInfo.classCount;
139    }
140
141    long classBytes() {
142        return jmodInfo.classBytes;
143    }
144
145    int numResources() {
146        return jmodInfo.resourceCount;
147    }
148
149    long resourceBytes() {
150        return jmodInfo.resourceBytes;
151    }
152
153    int numConfigs() {
154        return jmodInfo.configCount;
155    }
156    long configBytes() {
157        return jmodInfo.configBytes;
158    }
159    int numCommands() {
160        return jmodInfo.nativeCmds.size();
161    }
162
163    long commandBytes() {
164        return jmodInfo.nativeCmds.values().stream()
165                .mapToLong(l -> l.longValue()).sum() - jmodInfo.debugInfoCmdBytes;
166    }
167    int numCommandsDebug() {
168        return jmodInfo.debugInfoCmdCount;
169    }
170    long commandDebugBytes() {
171        return jmodInfo.debugInfoCmdBytes;
172    }
173    int numNativeLibraries() {
174        return jmodInfo.nativeLibs.size();
175    }
176
177    long nativeLibrariesBytes() {
178        return jmodInfo.nativeLibs.values().stream()
179                .mapToLong(l -> l.longValue()).sum() - jmodInfo.debugInfoLibBytes;
180    }
181    int numNativeLibrariesDebug() {
182        return jmodInfo.debugInfoLibCount;
183    }
184
185    long nativeLibrariesDebugBytes() {
186        return jmodInfo.debugInfoLibBytes;
187    }
188
189    Map<String,Long> commands() {
190        return jmodInfo.nativeCmds;
191    }
192
193    Map<String,Long> nativeLibs() {
194        return jmodInfo.nativeLibs;
195    }
196
197    Map<String,Long> configFiles() {
198        return jmodInfo.configFiles;
199    }
200
201
202    static class JmodInfo {
203        final long size;
204        final long filesize;
205        final int  classCount;
206        final long classBytes;
207        final int  resourceCount;
208        final long resourceBytes;
209        final int  configCount;
210        final long configBytes;
211        final int  debugInfoLibCount;
212        final long debugInfoLibBytes;
213        final int  debugInfoCmdCount;
214        final long debugInfoCmdBytes;
215        final Map<String,Long> configFiles = new HashMap<>();
216        final Map<String,Long> nativeCmds = new HashMap<>();
217        final Map<String,Long> nativeLibs = new HashMap<>();
218
219        JmodInfo(Path jmod) throws IOException {
220            long total = 0;
221            long cBytes = 0, rBytes = 0, cfBytes = 0, dizLibBytes = 0, dizCmdBytes = 0;
222            int  cCount = 0, rCount = 0, cfCount = 0, dizLibCount = 0, dizCmdCount = 0;
223            try (ZipFile zf = new ZipFile(jmod.toFile())) {
224                for (Enumeration<? extends ZipEntry> e = zf.entries(); e.hasMoreElements(); ) {
225                    ZipEntry ze = e.nextElement();
226                    String fn = ze.getName();
227                    int pos = fn.indexOf('/');
228                    String dir = fn.substring(0, pos);
229                    String filename = fn.substring(fn.lastIndexOf('/') + 1);
230                    // name shown in the column
231                    String name = filename;
232
233                    long len = ze.getSize();
234                    total += len;
235                    switch (dir) {
236                        case NATIVE_LIBS:
237                            nativeLibs.put(name, len);
238                            if (filename.endsWith(".diz")) {
239                                dizLibCount++;
240                                dizLibBytes += len;
241                            }
242                            break;
243                        case NATIVE_CMDS:
244                            nativeCmds.put(name, len);
245                            if (filename.endsWith(".diz")) {
246                                dizCmdCount++;
247                                dizCmdBytes += len;
248                            }
249                            break;
250                        case CLASSES:
251                            if (filename.endsWith(".class")) {
252                                cCount++;
253                                cBytes += len;
254                            } else {
255                                rCount++;
256                                rBytes += len;
257                            }
258                            break;
259                        case CONFIG:
260                            configFiles.put(name, len);
261                            cfCount++;
262                            cfBytes += len;
263                            break;
264                        default:
265                            break;
266                    }
267                }
268                this.filesize = jmod.toFile().length();
269                this.classCount = cCount;
270                this.classBytes = cBytes;
271                this.resourceCount = rCount;
272                this.resourceBytes = rBytes;
273                this.configCount = cfCount;
274                this.configBytes = cfBytes;
275                this.size = total;
276                this.debugInfoLibCount = dizLibCount;
277                this.debugInfoLibBytes = dizLibBytes;
278                this.debugInfoCmdCount = dizCmdCount;
279                this.debugInfoCmdBytes = dizCmdBytes;
280            }
281        }
282
283        static final String NATIVE_LIBS = "native";
284        static final String NATIVE_CMDS = "bin";
285        static final String CLASSES     = "classes";
286        static final String CONFIG      = "conf";
287
288        static final String MODULE_ID = "module/id";
289        static final String MODULE_MAIN_CLASS = "module/main-class";
290    }
291
292    static Configuration resolve(Set<String> roots) {
293        return Configuration.empty()
294            .resolveRequires(ModuleFinder.ofSystem(),
295                             ModuleFinder.of(),
296                             roots);
297    }
298
299    static class HtmlDocument {
300        final String title;
301        final Map<String, ModuleSummary> modules;
302        boolean requiresTransitiveNote = false;
303        boolean aggregatorNote = false;
304        boolean totalBytesNote = false;
305        HtmlDocument(String title, Map<String, ModuleSummary> modules) {
306            this.title = title;
307            this.modules = modules;
308        }
309
310        void writeTo(PrintStream out, Set<ModuleDescriptor> selectedModules) {
311            out.format("<html><head>%n");
312            out.format("<title>%s</title>%n", title);
313            // stylesheet
314            Arrays.stream(HtmlDocument.STYLES).forEach(out::println);
315            out.format("</head>%n");
316
317            // body begins
318            out.format("<body>%n");
319
320            // title and date
321            out.println(DOCTITLE.toString(title));
322            out.println(VERSION.toString(String.format("%tc", new Date())));
323
324            // total modules and sizes
325            long totalBytes = selectedModules.stream()
326                    .map(ModuleDescriptor::name)
327                    .map(modules::get)
328                    .mapToLong(ModuleSummary::uncompressedSize)
329                    .sum();
330            String[] sections = new String[] {
331                    String.format("%s: %d", "Total modules", selectedModules.size()),
332                    String.format("%s: %,d bytes (%s %s)", "Total size",
333                                  totalBytes,
334                                  System.getProperty("os.name"),
335                                  System.getProperty("os.arch"))
336            };
337            out.println(SECTION.toString(sections));
338
339            // write table and header
340            out.println(String.format("<table class=\"%s\">", MODULES));
341            out.println(header("Module", "Requires", "Exports",
342                    "Services", "Commands/Native Libraries/Configs"));
343
344            // write contents - one row per module
345            selectedModules.stream()
346                    .sorted(Comparator.comparing(ModuleDescriptor::name))
347                    .map(m -> modules.get(m.name()))
348                    .map(ModuleTableRow::new)
349                    .forEach(table -> table.writeTo(out));
350
351            out.format("</table>");  // end table
352            out.format("</body>");
353            out.println("</html>");
354        }
355
356        String header(String... columns) {
357            StringBuilder sb = new StringBuilder();
358            sb.append("<tr>");
359            Arrays.stream(columns)
360                    .forEach(cn -> sb.append("  <th>").append(cn).append("</th>").append("\n"));
361            sb.append("</tr>");
362            return sb.toString();
363        }
364
365        static enum Selector {
366            MODULES("modules"),
367            MODULE("module"),
368            MODULE_DEF("code name def"),
369            AGGREGATOR("code name def agg"),
370            REQUIRES("code"),
371            REQUIRES_PUBLIC("code reexp"),
372            BR("br"),
373            CODE("code"),
374            NUMBER("number"),;
375            final String name;
376            Selector(String name) {
377                this.name = name;
378            }
379            @Override
380            public String toString() {
381                return name;
382            }
383        }
384
385        static enum Division {
386            DOCTITLE("doctitle"),
387            VERSION("versions"),
388            SECTION("section");
389            final String name;
390
391            Division(String name) {
392                this.name = name;
393            }
394
395            public String toString(String... lines) {
396                String value = Arrays.stream(lines).collect(Collectors.joining("<br>\n"));
397                return "<div class=\"" + name + "\">" + value + "</div>";
398            }
399        }
400
401        class ModuleTableRow {
402            private final ModuleSummary ms;
403            private final Set<ModuleDescriptor> deps;
404            private final int maxRows;
405            private final boolean aggregator;
406            ModuleTableRow(ModuleSummary ms) {
407                this.ms = ms;
408                Configuration cf = resolve(Set.of(ms.name()));
409                this.deps = cf.modules().stream()
410                        .map(ResolvedModule::reference)
411                        .map(ModuleReference::descriptor)
412                        .collect(Collectors.toSet());
413                int count = (ms.numClasses() > 0 ? 1 : 0) +
414                            (ms.numResources() > 0 ? 1 : 0) +
415                            (ms.numConfigs() > 0 ? 1 : 0) +
416                            (ms.numNativeLibraries() > 0 ? 1 : 0) +
417                            (ms.numNativeLibrariesDebug() > 0 ? 1 : 0) +
418                            (ms.numCommands() > 0 ? 1 : 0) +
419                            (ms.numCommandsDebug() > 0 ? 1 : 0);
420                this.aggregator = ms.numClasses() == 1 && count == 1; // only module-info.class
421
422                // 5 fixed rows (name + 2 transitive count/size + 2 blank rows)
423                this.maxRows = 5 + count + (aggregator && !aggregatorNote ? 2 : 0);
424            }
425
426            public void writeTo(PrintStream out) {
427                out.println(String.format("<tr id=\"%s\" class=\"%s\">", ms.name(), MODULE));
428                out.println(moduleColumn());
429                out.println(requiresColumn());
430                out.println(exportsColumn());
431                out.println(servicesColumn());
432                out.println(otherSectionColumn());
433                out.println("</td>");
434                out.println("</tr>");
435            }
436
437            public String moduleColumn() {
438                // module name
439                StringBuilder sb = new StringBuilder("  ");
440                sb.append("<td>");
441                sb.append(String.format("<table class=\"%s\">", MODULE)).append("\n");
442                sb.append(moduleName(ms.name()));
443                sb.append(blankRow());
444                // metadata
445                sb.append(toTableRow("class", "classes", ms.numClasses(), ms.classBytes()));
446                sb.append(toTableRow("resource", "resources", ms.numResources(), ms.resourceBytes()));
447                sb.append(toTableRow("config", "configs", ms.numConfigs(), ms.configBytes()));
448                sb.append(toTableRow("native library", "native libraries",
449                                     ms.numNativeLibraries(), ms.nativeLibrariesBytes()));
450                sb.append(toTableRow("native library debug", "native libraries debug",
451                                     ms.numNativeLibrariesDebug(), ms.nativeLibrariesDebugBytes()));
452                sb.append(toTableRow("command", "commands", ms.numCommands(), ms.commandBytes()));
453                sb.append(toTableRow("command debug", "commands debug",
454                                     ms.numCommandsDebug(), ms.commandDebugBytes()));
455                sb.append(blankRow());
456
457                // transitive dependencies
458                long reqBytes = deps.stream()
459                                    .filter(d -> !d.name().equals(ms.name()))
460                                    .mapToLong(d -> modules.get(d.name()).uncompressedSize())
461                                    .sum();
462                long reqJmodFileSize = deps.stream()
463                                            .mapToLong(d -> modules.get(d.name()).jmodFileSize())
464                                            .sum();
465                // size
466                if (totalBytesNote) {
467                    sb.append(toTableRow("Total bytes", ms.uncompressedSize()));
468                    sb.append(toTableRow("Total bytes of dependencies", reqBytes));
469                } else {
470                    // print footnote
471                    sb.append(toTableRow("Total bytes<sup>1</sup>", ms.uncompressedSize()));
472                    sb.append(toTableRow("Total bytes of dependencies<sup>2</sup>", reqBytes));
473                }
474                String files = deps.size() == 1 ? "file" : "files";
475                sb.append(toTableRow(String.format("Total jmod bytes (%d %s)", deps.size(), files), reqJmodFileSize));
476
477                if (aggregator && !aggregatorNote) {
478                    aggregatorNote = true;
479                    sb.append(blankRow());
480                    sb.append(toTableRow("<i>* aggregator is a module with module-info.class only</i>", BR));
481                }
482                if (!totalBytesNote) {
483                    totalBytesNote = true;
484                    sb.append(blankRow());
485                    sb.append(toTableRow("<i><sup>1</sup>sum of all files including debug files</i>", BR));
486                    sb.append(toTableRow("<i><sup>2</sup>sum of direct and indirect dependencies</i>", BR));
487                }
488                sb.append("</table>").append("</td>");
489                return sb.toString();
490            }
491
492            private String moduleName(String mn) {
493                if (aggregator) {
494                    StringBuilder sb = new StringBuilder();
495                    sb.append(String.format("<tr><td colspan=\"2\"><span class=\"%s\">", AGGREGATOR))
496                      .append(mn)
497                      .append("</span>").append("&nbsp;&nbsp;");
498                    if (!aggregatorNote) {
499                        sb.append("(aggregator<sup>*</sup>)");
500                    } else {
501                        sb.append("(aggregator)");
502                    }
503                    sb.append("</td></tr>");
504                    return sb.toString();
505                } else {
506                    return toTableRow(mn, MODULE_DEF);
507                }
508            }
509
510            public String requiresColumn() {
511                StringBuilder sb = new StringBuilder();
512                sb.append(String.format("<td>"));
513                boolean footnote = requiresTransitiveNote;
514                ms.descriptor().requires().stream()
515                        .sorted(Comparator.comparing(Requires::name))
516                        .forEach(r -> {
517                            boolean requiresTransitive = r.modifiers().contains(Requires.Modifier.TRANSITIVE);
518                            Selector sel = requiresTransitive ? REQUIRES_PUBLIC : REQUIRES;
519                            String req = String.format("<a class=\"%s\" href=\"#%s\">%s</a>",
520                                                       sel, r.name(), r.name());
521                            if (!requiresTransitiveNote && requiresTransitive) {
522                                requiresTransitiveNote = true;
523                                req += "<sup>*</sup>";
524                            }
525                            sb.append(req).append("\n").append("<br>");
526                        });
527
528                if (!ms.name().equals("java.base")) {
529                    int directDeps = ms.descriptor().requires().size();
530                    int indirectDeps = deps.size()-directDeps-1;
531                    for (int i=directDeps; i< (maxRows-1); i++) {
532                        sb.append("<br>");
533                    }
534                    sb.append("<br>");
535                    sb.append("<i>+").append(indirectDeps).append(" transitive dependencies</i>");
536                }
537                if (footnote != requiresTransitiveNote) {
538                    sb.append("<br><br>").append("<i>* bold denotes requires transitive</i>");
539                }
540                sb.append("</td>");
541                return sb.toString();
542            }
543
544            public String exportsColumn() {
545                StringBuilder sb = new StringBuilder();
546                sb.append(String.format("  <td class=\"%s\">", CODE));
547                ms.descriptor().exports().stream()
548                        .sorted(Comparator.comparing(Exports::source))
549                        .filter(e -> !e.isQualified())
550                        .forEach(e -> sb.append(e.source()).append("<br>").append("\n"));
551                sb.append("</td>");
552                return sb.toString();
553            }
554
555            public String servicesColumn() {
556                StringBuilder sb = new StringBuilder();
557                sb.append(String.format("  <td class=\"%s\">", CODE));
558                ms.descriptor().uses().stream()
559                        .sorted()
560                        .forEach(s -> sb.append("uses ").append(s).append("<br>").append("\n"));
561                ms.descriptor().provides().stream()
562                        .sorted(Comparator.comparing(Provides::service))
563                        .map(p -> String.format("provides %s<br>&nbsp;&nbsp;&nbsp;&nbsp;with %s",
564                                                p.service(), p.providers()))
565                        .forEach(p -> sb.append(p).append("<br>").append("\n"));
566                sb.append("</td>");
567                return sb.toString();
568            }
569
570            public String otherSectionColumn() {
571                StringBuilder sb = new StringBuilder();
572                sb.append("<td>");
573                sb.append(String.format("<table class=\"%s\">", MODULE)).append("\n");
574                // commands
575                if (ms.numCommands() > 0) {
576                    sb.append(toTableRow("bin/", CODE));
577                    ms.commands().entrySet().stream()
578                            .sorted(Map.Entry.comparingByKey())
579                            .forEach(e -> sb.append(toTableRow(e.getKey(), e.getValue(), CODE)));
580                    sb.append(blankRow());
581                }
582
583                // native libraries
584                if (ms.numNativeLibraries() > 0) {
585                    sb.append(toTableRow("lib/", CODE));
586                    ms.nativeLibs().entrySet().stream()
587                            .sorted(Map.Entry.comparingByKey())
588                            .forEach(e -> sb.append(toTableRow(e.getKey(), e.getValue(), CODE)));
589                    sb.append(blankRow());
590                }
591
592                // config files
593                if (ms.numConfigs() > 0) {
594                    sb.append(toTableRow("conf/", CODE));
595                    ms.configFiles().entrySet().stream()
596                            .sorted(Map.Entry.comparingByKey())
597                            .forEach(e -> sb.append(toTableRow(e.getKey(), e.getValue(), CODE)));
598                }
599                // totals
600                sb.append("</table>").append("</td>");
601                return sb.toString();
602            }
603
604            private String blankRow() {
605                return toTableRow("&nbsp;", BR);
606            }
607
608            private String toTableRow(String col, Selector selector) {
609                TableDataBuilder builder = new TableDataBuilder();
610                builder.colspan(selector, 2, col);
611                return builder.build();
612            }
613
614            private String toTableRow(String col1, long col2) {
615                return toTableRow(col1, col2, BR);
616            }
617
618            private String toTableRow(String col1, long col2, Selector selector) {
619                TableDataBuilder builder = new TableDataBuilder();
620                builder.data(selector, col1);
621                builder.data(col2);
622                return builder.build();
623
624            }
625
626            private String toTableRow(String singular, String plural, int count, long bytes) {
627                if (count == 0) {
628                    return "";
629                }
630                TableDataBuilder builder = new TableDataBuilder();
631                if (count == 1) {
632                    builder.data(count + " " + singular);
633                } else {
634                    builder.data(count + " " + plural);
635                }
636                builder.data(bytes);
637                return builder.build();
638            }
639
640            class TableDataBuilder {
641                private final StringBuilder sb;
642                TableDataBuilder() {
643                    this.sb = new StringBuilder("<tr>");
644                }
645                TableDataBuilder data(String s) {
646                    data(BR, s);
647                    return this;
648                }
649                TableDataBuilder data(long num) {
650                    data(NUMBER, String.format("%,d", num));
651                    return this;
652                }
653                TableDataBuilder colspan(Selector selector, int columns, String data) {
654                    sb.append("<td colspan=\"").append(columns).append("\">");
655                    sb.append("<span class=\"").append(selector).append("\">");
656                    sb.append(data).append("</span></td>");
657                    return this;
658                }
659
660                TableDataBuilder data(Selector selector, String data) {
661                    sb.append("<td class=\"").append(selector).append("\">");
662                    sb.append(data).append("</td>");
663                    return this;
664                }
665                String build() {
666                    sb.append("</tr>");
667                    return sb.toString();
668                }
669            }
670        }
671
672        private static final String[] STYLES = new String[]{
673                "<link rel=\"stylesheet\" type=\"text/css\" href=\"/.fonts/dejavu.css\"/>",
674                "<style type=\"text/css\">",
675                "        HTML, BODY, DIV, SPAN, APPLET, OBJECT, IFRAME, H1, H2, H3, H4, H5, H6, P,",
676                "        BLOCKQUOTE, PRE, A, ABBR, ACRONYM, ADDRESS, BIG, CITE, CODE, DEL, DFN, EM,",
677                "        IMG, INS, KBD, Q, S, SAMP, SMALL, STRIKE, STRONG, SUB, SUP, TT, VAR, B, U,",
678                "        I, CENTER, DL, DT, DD, OL, UL, LI, FIELDSET, FORM, LABEL, LEGEND, TABLE,",
679                "        CAPTION, TBODY, TFOOT, THEAD, TR, TH, TD, ARTICLE, ASIDE, CANVAS, DETAILS,",
680                "        EMBED, FIGURE, FIGCAPTION, FOOTER, HEADER, HGROUP, MENU, NAV, OUTPUT, RUBY,",
681                "        SECTION, SUMMARY, TIME, MARK, AUDIO, VIDEO {",
682                "          margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit;",
683                "          vertical-align: baseline; }",
684                "        ARTICLE, ASIDE, DETAILS, FIGCAPTION, FIGURE, ",
685                "        FOOTER, HEADER, HGROUP, MENU, NAV, SECTION { display: block; }",
686                "        BLOCKQUOTE, Q { quotes: none; }",
687                "        BLOCKQUOTE:before, BLOCKQUOTE:after, Q:before, Q:after {",
688                "                content: ''; content: none; }",
689                "        TABLE { border-collapse: collapse; border-spacing: 0; }",
690                "        A { text-decoration: none; }",
691                "        A:link { color: #437291; }",
692                "        A:visited { color: #666666; }",
693                "        A.anchor:link, A.anchor:visited { color: black; }",
694                "        A[href]:hover { color: #e76f00; }",
695                "        A IMG { border-width: 0px; }",
696                "        HTML { font-size: 20px; } /* baseline grid */",
697                "        HTML > BODY { font-size: 14px; }",
698                "        BODY {",
699                "          background: white;",
700                "          margin: 40px;",
701                "          margin-bottom: 150%;",
702                "          line-height: 20px;",
703                "          -webkit-text-size-adjust: 100%; /* iOS */",
704                "          color: #222;",
705                "        }",
706                "        BODY { font-family: \"DejaVu Serif\", \"Lucida Bright\", \"Bookman Old Style\",",
707                "                            Georgia, serif; }",
708                "        CODE, TT, .jref, DIV.spec .open, TABLE.profiles {",
709                "          font-family: \"DejaVu Sans\", \"Lucida Sans\", Helvetica, sans-serif; }",
710                "        PRE, .code { font-family: \"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\",",
711                "                            Monaco, \"Courier New\", monospace; }",
712                "        H1, H2, H3, H4 { color: green; font-weight: bold; }",
713                "        I { font-style: italic; }",
714                "        TH { font-weight: bold; }",
715                "        P { text-indent: 40px; }",
716                "        P:first-child, UL + P, OL + P, BLOCKQUOTE + P, TABLE + P, P.subsection,",
717                "          P.break, DIV.profiles-table + P { text-indent: 0; }",
718                "        P.break { margin-top: 10px; }",
719                "        P.subsection { margin-top: 20px; }",
720                "        P.subsection SPAN.title { font-weight: bold; padding-right: 20px; }",
721                "        UL, OL { margin: 10px 0; padding-left: 40px; }",
722                "        LI { margin-bottom: 10px; }",
723                "        UL.compact LI { margin-bottom: 0; }",
724                "        PRE { padding: 0; margin: 10px 0 10px 20px; background: #eee; width: 45em; }",
725                "        BLOCKQUOTE { margin: 10px 0; margin-left: 20px; }",
726                "        LI BLOCKQUOTE { margin-left: 0; }",
727                "        UL LI { list-style-type: square; }",
728                "        .todo { color: darkred; text-align: right; }",
729                "        .error { color: red; font-weight: bold; }",
730                "        .warn { color: #ee0000; font-weight: bold; }",
731                "        DIV.doctitle { margin-top: -13px;",
732                "          font-size: 22px; line-height: 40px; font-weight: bold; }",
733                "        DIV.twarn { color: #cc0000; font-weight: bold; margin-bottom: 9px; }",
734                "        DIV.subtitle { margin-top: 2px; font-size: 18px; font-weight: bold; }",
735                "        DIV.authors { margin-top: 10px; margin-bottom: 10px; font-size: 16px; }",
736                "        DIV.author A { font-style: italic; }",
737                "        DIV.version { margin-top: 10px; font-size: 12px; }",
738                "        DIV.version, DIV.legal-notice { font-size: 12px; line-height: 15px; }",
739                "        SPAN.hash { font-size: 9px; }",
740                "        DIV.version SPAN.modified { color: green; font-weight: bold; }",
741                "        DIV.head { margin-bottom: 20px; }",
742                "        DIV.section > DIV.title, DIV.section DIV.number SPAN {",
743                "          font-size: 15px; font-weight: bold; }",
744                "        TABLE { border-collapse: collapse; border: none; }",
745                "        TD.number { text-align: right; }",
746                "        TD, TH { text-align: left; white-space: nowrap; }",
747                "        TD.name, SPAN.name { font-weight: bold; }",
748                "        ",
749                "        TABLE.module { width: 100%; }",
750                "        TABLE.module TD:first-child { padding-right: 10px; }",
751                "        TR.module > TD { padding: 10px 0; border-top: 1px solid black; }",
752                "        TR > TH { padding-bottom: 10px; }",
753                "        TR.br TD { padding-top: 20px; }",
754                "        TABLE.modules { margin-top: 20px; }",
755                "        TABLE.modules > TBODY > TR > TD:nth-child(even) { background: #eee; }",
756                "        TABLE.modules > TBODY > TR > TD, TABLE.modules > TBODY > TR > TH {",
757                "          padding-left: 10px; padding-right: 10px; }",
758                "        .reexp, .def { font-weight: bold; }",
759                "        .agg { font-style: italic; }",
760                "        SUP { height: 0; line-height: 1; position: relative;",
761                "              vertical-align: baseline; bottom: 1ex; font-size: 11px; }",
762                "</style>",
763        };
764    }
765}
766