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