1 /* 2 * Copyright (c) 1997, 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 26 package jdk.javadoc.internal.doclets.toolkit; 27 28 import java.io.*; 29 import java.util.*; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 33 import javax.lang.model.element.Element; 34 import javax.lang.model.element.ModuleElement; 35 import javax.lang.model.element.PackageElement; 36 import javax.lang.model.element.TypeElement; 37 import javax.tools.JavaFileManager; 38 import javax.tools.JavaFileManager.Location; 39 import javax.tools.JavaFileObject; 40 41 import com.sun.source.util.DocTreePath; 42 import jdk.javadoc.doclet.Doclet; 43 import jdk.javadoc.doclet.DocletEnvironment; 44 import jdk.javadoc.doclet.Reporter; 45 import jdk.javadoc.internal.doclets.toolkit.builders.BuilderFactory; 46 import jdk.javadoc.internal.doclets.toolkit.taglets.TagletManager; 47 import jdk.javadoc.internal.doclets.toolkit.util.DocFile; 48 import jdk.javadoc.internal.doclets.toolkit.util.DocFileFactory; 49 import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; 50 import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; 51 import jdk.javadoc.internal.doclets.toolkit.util.Extern; 52 import jdk.javadoc.internal.doclets.toolkit.util.Group; 53 import jdk.javadoc.internal.doclets.toolkit.util.MessageRetriever; 54 import jdk.javadoc.internal.doclets.toolkit.util.MetaKeywords; 55 import jdk.javadoc.internal.doclets.toolkit.util.TypeElementCatalog; 56 import jdk.javadoc.internal.doclets.toolkit.util.Utils; 57 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap.GetterSetter; 58 59 import static javax.tools.Diagnostic.Kind.*; 60 61 /** 62 * Configure the output based on the options. Doclets should sub-class 63 * Configuration, to configure and add their own options. This class contains 64 * all user options which are supported by the 1.1 doclet and the standard 65 * doclet. 66 * 67 * <p><b>This is NOT part of any supported API. 68 * If you write code that depends on this, you do so at your own risk. 69 * This code and its internal interfaces are subject to change or 70 * deletion without notice.</b> 71 * 72 * @author Robert Field. 73 * @author Atul Dambalkar. 74 * @author Jamie Ho 75 */ 76 public abstract class Configuration { 77 78 /** 79 * Exception used to report a problem during setOptions. 80 */ 81 public static class Fault extends Exception { 82 private static final long serialVersionUID = 0; 83 84 Fault(String msg) { 85 super(msg); 86 } 87 88 Fault(String msg, Exception cause) { 89 super(msg, cause); 90 } 91 } 92 93 /** 94 * The factory for builders. 95 */ 96 protected BuilderFactory builderFactory; 97 98 /** 99 * The taglet manager. 100 */ 101 public TagletManager tagletManager; 102 103 /** 104 * The path to the builder XML input file. 105 */ 106 public String builderXMLPath; 107 108 /** 109 * The default path to the builder XML. 110 */ 111 private static final String DEFAULT_BUILDER_XML = "resources/doclet.xml"; 112 113 /** 114 * The path to Taglets 115 */ 116 public String tagletpath = ""; 117 118 /** 119 * This is true if option "-serialwarn" is used. Defualt value is false to 120 * suppress excessive warnings about serial tag. 121 */ 122 public boolean serialwarn = false; 123 124 /** 125 * The specified amount of space between tab stops. 126 */ 127 public int sourcetab; 128 129 public String tabSpaces; 130 131 /** 132 * True if we should generate browsable sources. 133 */ 134 public boolean linksource = false; 135 136 /** 137 * True if command line option "-nosince" is used. Default value is 138 * false. 139 */ 140 public boolean nosince = false; 141 142 /** 143 * True if we should recursively copy the doc-file subdirectories 144 */ 145 public boolean copydocfilesubdirs = false; 146 147 /** 148 * Maintain backward compatibility with previous javadoc version 149 */ 150 public boolean backwardCompatibility = true; 151 152 /** 153 * The META charset tag used for cross-platform viewing. 154 */ 155 public String charset = ""; 156 157 /** 158 * True if user wants to add member names as meta keywords. 159 * Set to false because meta keywords are ignored in general 160 * by most Internet search engines. 161 */ 162 public boolean keywords = false; 163 164 /** 165 * The meta tag keywords instance. 166 */ 167 public final MetaKeywords metakeywords; 168 169 /** 170 * The list of doc-file subdirectories to exclude 171 */ 172 protected Set<String> excludedDocFileDirs; 173 174 /** 175 * The list of qualifiers to exclude 176 */ 177 protected Set<String> excludedQualifiers; 178 179 /** 180 * The Root of the generated Program Structure from the Doclet API. 181 */ 182 public DocletEnvironment root; 183 184 /** 185 * An utility class for commonly used helpers 186 */ 187 public Utils utils; 188 189 /** 190 * All the temporary accessors to javac internals. 191 */ 192 public WorkArounds workArounds; 193 194 /** 195 * Destination directory name, in which doclet will generate the entire 196 * documentation. Default is current directory. 197 */ 198 public String destDirName = ""; 199 200 /** 201 * Destination directory name, in which doclet will copy the doc-files to. 202 */ 203 public String docFileDestDirName = ""; 204 205 /** 206 * Encoding for this document. Default is default encoding for this 207 * platform. 208 */ 209 public String docencoding = null; 210 211 /** 212 * True if user wants to suppress descriptions and tags. 213 */ 214 public boolean nocomment = false; 215 216 /** 217 * Encoding for this document. Default is default encoding for this 218 * platform. 219 */ 220 public String encoding = null; 221 222 /** 223 * Generate author specific information for all the classes if @author 224 * tag is used in the doc comment and if -author option is used. 225 * <code>showauthor</code> is set to true if -author option is used. 226 * Default is don't show author information. 227 */ 228 public boolean showauthor = false; 229 230 /** 231 * Generate documentation for JavaFX getters and setters automatically 232 * by copying it from the appropriate property definition. 233 */ 234 public boolean javafx = false; 235 236 /** 237 * Generate version specific information for the all the classes 238 * if @version tag is used in the doc comment and if -version option is 239 * used. <code>showversion</code> is set to true if -version option is 240 * used.Default is don't show version information. 241 */ 242 public boolean showversion = false; 243 244 /** 245 * Sourcepath from where to read the source files. Default is classpath. 246 * 247 */ 248 public String sourcepath = ""; 249 250 /** 251 * Generate modules documentation if more than one module is present. 252 */ 253 public boolean showModules = false; 254 255 /** 256 * Don't generate deprecated API information at all, if -nodeprecated 257 * option is used. <code>nodepracted</code> is set to true if 258 * -nodeprecated option is used. Default is generate deprected API 259 * information. 260 */ 261 public boolean nodeprecated = false; 262 263 /** 264 * The catalog of classes specified on the command-line 265 */ 266 public TypeElementCatalog typeElementCatalog; 267 268 /** 269 * Message Retriever for the doclet, to retrieve message from the resource 270 * file for this Configuration, which is common for 1.1 and standard 271 * doclets. 272 * 273 * TODO: Make this private!!! 274 */ 275 public MessageRetriever message = null; 276 277 /** 278 * True if user wants to suppress time stamp in output. 279 * Default is false. 280 */ 281 public boolean notimestamp= false; 282 283 /** 284 * The package grouping instance. 285 */ 286 public final Group group = new Group(this); 287 288 /** 289 * The tracker of external package links. 290 */ 291 public final Extern extern = new Extern(this); 292 293 public Reporter reporter; 294 295 public Locale locale; 296 297 /** 298 * Suppress all messages 299 */ 300 public boolean quiet = false; 301 302 private String urlForLink; 303 304 private String pkglistUrlForLink; 305 306 private String urlForLinkOffline; 307 308 private String pkglistUrlForLinkOffline; 309 310 private List<GroupContainer> groups; 311 312 /** 313 * Return the build date for the doclet. 314 */ 315 public abstract String getDocletSpecificBuildDate(); 316 317 /** 318 * This method should be defined in all those doclets(configurations), 319 * which want to derive themselves from this Configuration. This method 320 * can be used to finish up the options setup. 321 */ 322 323 public abstract boolean finishOptionSettings(); 324 325 /** 326 * Return the doclet specific {@link MessageRetriever} 327 * @return the doclet specific MessageRetriever. 328 */ 329 public abstract MessageRetriever getDocletSpecificMsg(); 330 331 public CommentUtils cmtUtils; 332 public SortedSet<ModuleElement> modules; 333 334 /** 335 * A sorted set of packages specified on the command-line merged with a 336 * collection of packages that contain the classes specified on the 337 * command-line. 338 */ 339 public SortedSet<PackageElement> packages; 340 341 protected final List<Doclet.Option> optionsProcessed; 342 343 public final OverviewElement overviewElement; 344 345 // The following three fields provide caches for use by all instances of VisibleMemberMap. 346 public final Map<TypeElement, List<Element>> propertiesCache = new HashMap<>(); 347 public final Map<Element, Element> classPropertiesMap = new HashMap<>(); 348 public final Map<Element, GetterSetter> getterSetterMap = new HashMap<>(); 349 350 public DocFileFactory docFileFactory; 351 352 /** 353 * A sorted set of modules containing the packages. 354 */ 355 public Map<ModuleElement, Set<PackageElement>> modulePackages; 356 357 /** 358 * Constructor. Constructs the message retriever with resource file. 359 */ 360 public Configuration() { 361 message = new MessageRetriever(this, "jdk.javadoc.internal.doclets.toolkit.resources.doclets"); 362 excludedDocFileDirs = new HashSet<>(); 363 excludedQualifiers = new HashSet<>(); 364 setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH); 365 metakeywords = new MetaKeywords(this); 366 optionsProcessed = new ArrayList<>(); 367 groups = new ArrayList<>(0); 368 overviewElement = new OverviewElement(root); 369 } 370 371 /** 372 * Return the builder factory for this doclet. 373 * 374 * @return the builder factory for this doclet. 375 */ 376 public BuilderFactory getBuilderFactory() { 377 if (builderFactory == null) { 378 builderFactory = new BuilderFactory(this); 379 } 380 return builderFactory; 381 } 382 383 public Reporter getReporter() { 384 return this.reporter; 385 } 386 387 private void initModules() { 388 // Build the modules structure used by the doclet 389 modulePackages = new TreeMap<>(utils.makeModuleComparator()); 390 for (PackageElement p: packages) { 391 ModuleElement mdle = root.getElementUtils().getModuleOf(p); 392 if (mdle != null && !mdle.isUnnamed()) { 393 Set<PackageElement> s = modulePackages.get(mdle); 394 if (s == null) 395 modulePackages.put(mdle, s = new TreeSet<>(utils.makePackageComparator())); 396 s.add(p); 397 } 398 } 399 modules = new TreeSet<>(utils.makeModuleComparator()); 400 modules.addAll(modulePackages.keySet()); 401 showModules = (modulePackages.size() > 1); 402 } 403 404 private void initPackages() { 405 packages = new TreeSet<>(utils.makePackageComparator()); 406 packages.addAll(utils.getSpecifiedPackages()); 407 for (TypeElement aClass : utils.getSpecifiedClasses()) { 408 packages.add(utils.containingPackage(aClass)); 409 } 410 } 411 412 public Set<Doclet.Option> getSupportedOptions() { 413 Doclet.Option[] options = { 414 new Option(this, "author") { 415 @Override 416 public boolean process(String opt, ListIterator<String> args) { 417 optionsProcessed.add(this); 418 showauthor = true; 419 return true; 420 } 421 }, 422 new Option(this, "d", 1) { 423 @Override 424 public boolean process(String opt, ListIterator<String> args) { 425 optionsProcessed.add(this); 426 destDirName = addTrailingFileSep(args.next()); 427 return true; 428 } 429 }, 430 new Option(this, "docencoding", 1) { 431 @Override 432 public boolean process(String opt, ListIterator<String> args) { 433 optionsProcessed.add(this); 434 docencoding = args.next(); 435 return true; 436 } 437 }, 438 new Option(this, "docfilessubdirs") { 439 @Override 440 public boolean process(String opt, ListIterator<String> args) { 441 optionsProcessed.add(this); 442 copydocfilesubdirs = true; 443 return true; 444 } 445 }, 446 new Hidden(this, "encoding", 1) { 447 @Override 448 public boolean process(String opt, ListIterator<String> args) { 449 optionsProcessed.add(this); 450 encoding = args.next(); 451 return true; 452 } 453 }, 454 new Option(this, "excludedocfilessubdir", 1) { 455 @Override 456 public boolean process(String opt, ListIterator<String> args) { 457 optionsProcessed.add(this); 458 addToSet(excludedDocFileDirs, args.next()); 459 return true; 460 } 461 }, 462 new Option(this, "group", 2) { 463 @Override 464 public boolean process(String opt, ListIterator<String> args) { 465 optionsProcessed.add(this); 466 groups.add(new GroupContainer(args.next(), args.next())); 467 return true; 468 } 469 }, 470 new Hidden(this, "javafx") { 471 @Override 472 public boolean process(String opt, ListIterator<String> args) { 473 optionsProcessed.add(this); 474 javafx = true; 475 return true; 476 } 477 }, 478 new Option(this, "keywords") { 479 @Override 480 public boolean process(String opt, ListIterator<String> args) { 481 optionsProcessed.add(this); 482 keywords = true; 483 return true; 484 } 485 }, 486 new Option(this, "link", 1) { 487 @Override 488 public boolean process(String opt, ListIterator<String> args) { 489 optionsProcessed.add(this); 490 urlForLink = args.next(); 491 pkglistUrlForLink = urlForLink; 492 return true; 493 } 494 }, 495 new Option(this, "linksource") { 496 @Override 497 public boolean process(String opt, ListIterator<String> args) { 498 optionsProcessed.add(this); 499 linksource = true; 500 return true; 501 } 502 }, 503 new Option(this, "linkoffline", 2) { 504 @Override 505 public boolean process(String opt, ListIterator<String> args) { 506 optionsProcessed.add(this); 507 urlForLinkOffline = args.next(); 508 pkglistUrlForLinkOffline = args.next(); 509 return true; 510 } 511 }, 512 new Option(this, "nocomment") { 513 @Override 514 public boolean process(String opt, ListIterator<String> args) { 515 optionsProcessed.add(this); 516 nocomment = true; 517 return true; 518 } 519 }, 520 new Option(this, "nodeprecated") { 521 @Override 522 public boolean process(String opt, ListIterator<String> args) { 523 optionsProcessed.add(this); 524 nodeprecated = true; 525 return true; 526 } 527 }, 528 new Option(this, "nosince") { 529 @Override 530 public boolean process(String opt, ListIterator<String> args) { 531 optionsProcessed.add(this); 532 nosince = true; 533 return true; 534 } 535 }, 536 new Option(this, "notimestamp") { 537 @Override 538 public boolean process(String opt, ListIterator<String> args) { 539 optionsProcessed.add(this); 540 notimestamp = true; 541 return true; 542 } 543 }, 544 new Option(this, "noqualifier", 1) { 545 @Override 546 public boolean process(String opt, ListIterator<String> args) { 547 optionsProcessed.add(this); 548 addToSet(excludedQualifiers, args.next()); 549 return true; 550 } 551 }, 552 new Hidden(this, "quiet") { 553 @Override 554 public boolean process(String opt, ListIterator<String> args) { 555 optionsProcessed.add(this); 556 quiet = true; 557 return true; 558 } 559 }, 560 new Option(this, "serialwarn") { 561 @Override 562 public boolean process(String opt, ListIterator<String> args) { 563 optionsProcessed.add(this); 564 serialwarn = true; 565 return true; 566 } 567 }, 568 new Option(this, "sourcetab", 1) { 569 @Override 570 public boolean process(String opt, ListIterator<String> args) { 571 optionsProcessed.add(this); 572 linksource = true; 573 try { 574 setTabWidth(Integer.parseInt(args.next())); 575 } catch (NumberFormatException e) { 576 //Set to -1 so that warning will be printed 577 //to indicate what is valid argument. 578 sourcetab = -1; 579 } 580 if (sourcetab <= 0) { 581 message.warning("doclet.sourcetab_warning"); 582 setTabWidth(DocletConstants.DEFAULT_TAB_STOP_LENGTH); 583 } 584 return true; 585 } 586 }, 587 new Option(this, "tag", 1) { 588 @Override 589 public boolean process(String opt, ListIterator<String> args) { 590 optionsProcessed.add(this); 591 ArrayList<String> list = new ArrayList<>(); 592 list.add(opt); 593 list.add(args.next()); 594 customTagStrs.add(list); 595 return true; 596 } 597 }, 598 new Option(this, "taglet", 1) { 599 @Override 600 public boolean process(String opt, ListIterator<String> args) { 601 optionsProcessed.add(this); 602 ArrayList<String> list = new ArrayList<>(); 603 list.add(opt); 604 list.add(args.next()); 605 customTagStrs.add(list); 606 return true; 607 } 608 }, 609 new Option(this, "tagletpath", 1) { 610 @Override 611 public boolean process(String opt, ListIterator<String> args) { 612 optionsProcessed.add(this); 613 tagletpath = args.next(); 614 return true; 615 } 616 }, 617 new Option(this, "version") { 618 @Override 619 public boolean process(String opt, ListIterator<String> args) { 620 optionsProcessed.add(this); 621 showversion = true; 622 return true; 623 } 624 } 625 }; 626 Set<Doclet.Option> set = new TreeSet<>(); 627 set.addAll(Arrays.asList(options)); 628 return set; 629 } 630 631 final LinkedHashSet<List<String>> customTagStrs = new LinkedHashSet<>(); 632 633 /* 634 * when this is called all the option have been set, this method, 635 * initializes certain components before anything else is started. 636 */ 637 private void finishOptionSettings0() throws Fault { 638 ensureOutputDirExists(); 639 if (urlForLink != null && pkglistUrlForLink != null) 640 extern.link(urlForLink, pkglistUrlForLink, reporter, false); 641 if (urlForLinkOffline != null && pkglistUrlForLinkOffline != null) 642 extern.link(urlForLinkOffline, pkglistUrlForLinkOffline, reporter, true); 643 if (docencoding == null) { 644 docencoding = encoding; 645 } 646 typeElementCatalog = new TypeElementCatalog(utils.getSpecifiedClasses(), this); 647 initTagletManager(customTagStrs); 648 groups.stream().forEach((grp) -> { 649 group.checkPackageGroups(grp.value1, grp.value2); 650 }); 651 } 652 653 /** 654 * Set the command line options supported by this configuration. 655 * 656 * @return true if the options are set successfully 657 * @throws DocletAbortException 658 */ 659 public boolean setOptions() throws Fault { 660 try { 661 initPackages(); 662 initModules(); 663 finishOptionSettings0(); 664 if (!finishOptionSettings()) 665 return false; 666 667 } catch (Fault f) { 668 throw new DocletAbortException(f.getMessage()); 669 } 670 return true; 671 } 672 673 private void ensureOutputDirExists() throws Fault { 674 DocFile destDir = DocFile.createFileForDirectory(this, destDirName); 675 if (!destDir.exists()) { 676 //Create the output directory (in case it doesn't exist yet) 677 if (!destDirName.isEmpty()) 678 reporter.print(NOTE, getText("doclet.dest_dir_create", destDirName)); 679 destDir.mkdirs(); 680 } else if (!destDir.isDirectory()) { 681 throw new Fault(getText( 682 "doclet.destination_directory_not_directory_0", 683 destDir.getPath())); 684 } else if (!destDir.canWrite()) { 685 throw new Fault(getText( 686 "doclet.destination_directory_not_writable_0", 687 destDir.getPath())); 688 } 689 } 690 691 /** 692 * Initialize the taglet manager. The strings to initialize the simple custom tags should 693 * be in the following format: "[tag name]:[location str]:[heading]". 694 * @param customTagStrs the set two dimensional arrays of strings. These arrays contain 695 * either -tag or -taglet arguments. 696 */ 697 private void initTagletManager(Set<List<String>> customTagStrs) { 698 tagletManager = tagletManager == null ? 699 new TagletManager(nosince, showversion, showauthor, javafx, message) : 700 tagletManager; 701 for (List<String> args : customTagStrs) { 702 if (args.get(0).equals("-taglet")) { 703 tagletManager.addCustomTag(args.get(1), getFileManager(), tagletpath); 704 continue; 705 } 706 List<String> tokens = tokenize(args.get(1), TagletManager.SIMPLE_TAGLET_OPT_SEPARATOR, 3); 707 if (tokens.size() == 1) { 708 String tagName = args.get(1); 709 if (tagletManager.isKnownCustomTag(tagName)) { 710 //reorder a standard tag 711 tagletManager.addNewSimpleCustomTag(tagName, null, ""); 712 } else { 713 //Create a simple tag with the heading that has the same name as the tag. 714 StringBuilder heading = new StringBuilder(tagName + ":"); 715 heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0))); 716 tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a"); 717 } 718 } else if (tokens.size() == 2) { 719 //Add simple taglet without heading, probably to excluding it in the output. 720 tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(1), ""); 721 } else if (tokens.size() >= 3) { 722 tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(2), tokens.get(1)); 723 } else { 724 message.error("doclet.Error_invalid_custom_tag_argument", args.get(1)); 725 } 726 } 727 } 728 729 /** 730 * Given a string, return an array of tokens. The separator can be escaped 731 * with the '\' character. The '\' character may also be escaped by the 732 * '\' character. 733 * 734 * @param s the string to tokenize. 735 * @param separator the separator char. 736 * @param maxTokens the maximum number of tokens returned. If the 737 * max is reached, the remaining part of s is appended 738 * to the end of the last token. 739 * 740 * @return an array of tokens. 741 */ 742 private List<String> tokenize(String s, char separator, int maxTokens) { 743 List<String> tokens = new ArrayList<>(); 744 StringBuilder token = new StringBuilder (); 745 boolean prevIsEscapeChar = false; 746 for (int i = 0; i < s.length(); i += Character.charCount(i)) { 747 int currentChar = s.codePointAt(i); 748 if (prevIsEscapeChar) { 749 // Case 1: escaped character 750 token.appendCodePoint(currentChar); 751 prevIsEscapeChar = false; 752 } else if (currentChar == separator && tokens.size() < maxTokens-1) { 753 // Case 2: separator 754 tokens.add(token.toString()); 755 token = new StringBuilder(); 756 } else if (currentChar == '\\') { 757 // Case 3: escape character 758 prevIsEscapeChar = true; 759 } else { 760 // Case 4: regular character 761 token.appendCodePoint(currentChar); 762 } 763 } 764 if (token.length() > 0) { 765 tokens.add(token.toString()); 766 } 767 return tokens; 768 } 769 770 private void addToSet(Set<String> s, String str){ 771 StringTokenizer st = new StringTokenizer(str, ":"); 772 String current; 773 while(st.hasMoreTokens()){ 774 current = st.nextToken(); 775 s.add(current); 776 } 777 } 778 779 /** 780 * Add a trailing file separator, if not found. Remove superfluous 781 * file separators if any. Preserve the front double file separator for 782 * UNC paths. 783 * 784 * @param path Path under consideration. 785 * @return String Properly constructed path string. 786 */ 787 public static String addTrailingFileSep(String path) { 788 String fs = System.getProperty("file.separator"); 789 String dblfs = fs + fs; 790 int indexDblfs; 791 while ((indexDblfs = path.indexOf(dblfs, 1)) >= 0) { 792 path = path.substring(0, indexDblfs) + 793 path.substring(indexDblfs + fs.length()); 794 } 795 if (!path.endsWith(fs)) 796 path += fs; 797 return path; 798 } 799 800 /** 801 * This checks for the validity of the options used by the user. 802 * This works exactly like DocErrorReporter. This will validate the options which are shared 803 * by our doclets. For example, this method will flag an error using 804 * the DocErrorReporter if user has used "-nohelp" and "-helpfile" option 805 * together. 806 * 807 * @return true if all the options are valid. 808 */ 809 public boolean generalValidOptions() { 810 boolean docencodingfound = false; 811 for (Doclet.Option opt : optionsProcessed) { 812 if (opt.matches("-docencoding")) { 813 docencodingfound = true; 814 if (!checkOutputFileEncoding(docencoding)) { 815 return false; 816 } 817 }; 818 } 819 if (!docencodingfound && (encoding != null && !encoding.isEmpty())) { 820 if (!checkOutputFileEncoding(encoding)) { 821 return false; 822 } 823 } 824 return true; 825 } 826 827 /** 828 * Check the validity of the given Source or Output File encoding on this 829 * platform. 830 * 831 * @param docencoding output file encoding. 832 * @param reporter used to report errors. 833 */ 834 private boolean checkOutputFileEncoding(String docencoding) { 835 OutputStream ost= new ByteArrayOutputStream(); 836 OutputStreamWriter osw = null; 837 try { 838 osw = new OutputStreamWriter(ost, docencoding); 839 } catch (UnsupportedEncodingException exc) { 840 reporter.print(ERROR, getText("doclet.Encoding_not_supported", docencoding)); 841 return false; 842 } finally { 843 try { 844 if (osw != null) { 845 osw.close(); 846 } 847 } catch (IOException exc) { 848 } 849 } 850 return true; 851 } 852 853 /** 854 * Return true if the given doc-file subdirectory should be excluded and 855 * false otherwise. 856 * @param docfilesubdir the doc-files subdirectory to check. 857 * @return true if the directory is excluded. 858 */ 859 public boolean shouldExcludeDocFileDir(String docfilesubdir){ 860 return excludedDocFileDirs.contains(docfilesubdir); 861 } 862 863 /** 864 * Return true if the given qualifier should be excluded and false otherwise. 865 * @param qualifier the qualifier to check. 866 */ 867 public boolean shouldExcludeQualifier(String qualifier){ 868 if (excludedQualifiers.contains("all") || 869 excludedQualifiers.contains(qualifier) || 870 excludedQualifiers.contains(qualifier + ".*")) { 871 return true; 872 } else { 873 int index = -1; 874 while ((index = qualifier.indexOf(".", index + 1)) != -1) { 875 if (excludedQualifiers.contains(qualifier.substring(0, index + 1) + "*")) { 876 return true; 877 } 878 } 879 return false; 880 } 881 } 882 883 /** 884 * Return the qualified name of the Element if its qualifier is not excluded. 885 * Otherwise return the unqualified Element name. 886 * @param te the TypeElement to check. 887 * @return the class name 888 */ 889 public String getClassName(TypeElement te) { 890 PackageElement pkg = utils.containingPackage(te); 891 return shouldExcludeQualifier(utils.getPackageName(pkg)) 892 ? utils.getSimpleName(te) 893 : utils.getFullyQualifiedName(te); 894 } 895 896 public String getText(String key) { 897 //Check the doclet specific properties file. 898 if (getDocletSpecificMsg().containsKey(key)) { 899 return getDocletSpecificMsg().getText(key); 900 } 901 //Check the shared properties file. 902 return message.getText(key); 903 } 904 905 public String getText(String key, String a1) { 906 //Check the doclet specific properties file. 907 if (getDocletSpecificMsg().containsKey(key)) { 908 return getDocletSpecificMsg().getText(key, a1); 909 } 910 //Check the shared properties file. 911 return message.getText(key, a1); 912 } 913 914 public String getText(String key, String a1, String a2) { 915 //Check the doclet specific properties file. 916 if (getDocletSpecificMsg().containsKey(key)) { 917 return getDocletSpecificMsg().getText(key, a1, a2); 918 } 919 //Check the shared properties file. 920 return message.getText(key, a1, a2); 921 } 922 923 public String getText(String key, String a1, String a2, String a3) { 924 //Check the doclet specific properties file. 925 if (getDocletSpecificMsg().containsKey(key)) { 926 return getDocletSpecificMsg().getText(key, a1, a2, a3); 927 } 928 //Check the shared properties file. 929 return message.getText(key, a1, a2, a3); 930 } 931 932 public abstract Content newContent(); 933 934 /** 935 * Get the configuration string as a content. 936 * 937 * @param key the key to look for in the configuration file 938 * @return a content tree for the text 939 */ 940 public Content getResource(String key) { 941 Content c = newContent(); 942 c.addContent(getText(key)); 943 return c; 944 } 945 946 /** 947 * Get the configuration string as a content. 948 * 949 * @param key the key to look for in the configuration file 950 * @param o string or content argument added to configuration text 951 * @return a content tree for the text 952 */ 953 public Content getResource(String key, Object o) { 954 return getResource(key, o, null, null); 955 } 956 957 /** 958 * Get the configuration string as a content. 959 * 960 * @param key the key to look for in the configuration file 961 * @param o1 resource argument 962 * @param o2 resource argument 963 * @return a content tree for the text 964 */ 965 public Content getResource(String key, Object o1, Object o2) { 966 return getResource(key, o1, o2, null); 967 } 968 969 /** 970 * Get the configuration string as a content. 971 * 972 * @param key the key to look for in the configuration file 973 * @param o0 string or content argument added to configuration text 974 * @param o1 string or content argument added to configuration text 975 * @param o2 string or content argument added to configuration text 976 * @return a content tree for the text 977 */ 978 public Content getResource(String key, Object o0, Object o1, Object o2) { 979 Content c = newContent(); 980 Pattern p = Pattern.compile("\\{([012])\\}"); 981 String text = getText(key); 982 Matcher m = p.matcher(text); 983 int start = 0; 984 while (m.find(start)) { 985 c.addContent(text.substring(start, m.start())); 986 987 Object o = null; 988 switch (m.group(1).charAt(0)) { 989 case '0': o = o0; break; 990 case '1': o = o1; break; 991 case '2': o = o2; break; 992 } 993 994 if (o == null) { 995 c.addContent("{" + m.group(1) + "}"); 996 } else if (o instanceof String) { 997 c.addContent((String) o); 998 } else if (o instanceof Content) { 999 c.addContent((Content) o); 1000 } 1001 1002 start = m.end(); 1003 } 1004 1005 c.addContent(text.substring(start)); 1006 return c; 1007 } 1008 1009 1010 /** 1011 * Return true if the TypeElement element is getting documented, depending upon 1012 * -nodeprecated option and the deprecation information. Return true if 1013 * -nodeprecated is not used. Return false if -nodeprecated is used and if 1014 * either TypeElement element is deprecated or the containing package is deprecated. 1015 * 1016 * @param te the TypeElement for which the page generation is checked 1017 * @return true if it is a generated doc. 1018 */ 1019 public boolean isGeneratedDoc(TypeElement te) { 1020 if (!nodeprecated) { 1021 return true; 1022 } 1023 return !(utils.isDeprecated(te) || utils.isDeprecated(utils.containingPackage(te))); 1024 } 1025 1026 /** 1027 * Return the doclet specific instance of a writer factory. 1028 * @return the {@link WriterFactory} for the doclet. 1029 */ 1030 public abstract WriterFactory getWriterFactory(); 1031 1032 /** 1033 * Return the input stream to the builder XML. 1034 * 1035 * @return the input steam to the builder XML. 1036 * @throws FileNotFoundException when the given XML file cannot be found. 1037 */ 1038 public InputStream getBuilderXML() throws IOException { 1039 return builderXMLPath == null ? 1040 Configuration.class.getResourceAsStream(DEFAULT_BUILDER_XML) : 1041 DocFile.createFileForInput(this, builderXMLPath).openInputStream(); 1042 } 1043 1044 /** 1045 * Return the Locale for this document. 1046 * @return the current locale 1047 */ 1048 public abstract Locale getLocale(); 1049 1050 /** 1051 * Return the path of the overview file and null if it does not exist. 1052 * 1053 * @return the path of the overview file. 1054 */ 1055 public abstract JavaFileObject getOverviewPath(); 1056 1057 /** 1058 * Return the current file manager. 1059 * @return JavaFileManager 1060 */ 1061 public abstract JavaFileManager getFileManager(); 1062 1063 private void setTabWidth(int n) { 1064 sourcetab = n; 1065 tabSpaces = String.format("%" + n + "s", ""); 1066 } 1067 1068 public abstract boolean showMessage(DocTreePath path, String key); 1069 1070 public abstract boolean showMessage(Element e, String key); 1071 1072 public static abstract class Option implements Doclet.Option, Comparable<Option> { 1073 private final String name; 1074 private final String parameters; 1075 private final String description; 1076 private final int argCount; 1077 1078 protected final Configuration c; 1079 1080 protected Option(Configuration config, String keyName, String name, int argCount) { 1081 c = config; 1082 String key = keyName + "name"; 1083 String oname = getOptionsMessage(key); 1084 if (oname.isEmpty()) { 1085 this.name = name; 1086 this.parameters = "<MISSING KEY>"; 1087 this.description = "<MISSING KEY>"; 1088 } else { 1089 this.name = oname; 1090 this.parameters = getOptionsMessage(keyName + "parameters"); 1091 this.description = getOptionsMessage(keyName + "description"); 1092 } 1093 this.argCount = argCount; 1094 } 1095 1096 protected Option(String prefix, Configuration config, String name, int argCount) { 1097 this(config, prefix + name.toLowerCase() + ".", name, argCount); 1098 } 1099 1100 protected Option(Configuration config, String name, int argCount) { 1101 this("doclet.usage.", config, name, argCount); 1102 } 1103 1104 protected Option(Configuration config, String name) { 1105 this(config, name, 0); 1106 } 1107 1108 private String getOptionsMessage(String key) { 1109 try { 1110 return c.getDocletSpecificMsg().getText(key, (Object[]) null); 1111 } catch (MissingResourceException ignore) { 1112 return ""; 1113 } 1114 } 1115 1116 @Override 1117 public String getDescription() { 1118 return description; 1119 } 1120 1121 @Override 1122 public Option.Kind getKind() { 1123 return Doclet.Option.Kind.STANDARD; 1124 } 1125 1126 @Override 1127 public String getName() { 1128 return name; 1129 } 1130 1131 @Override 1132 public String getParameters() { 1133 return parameters; 1134 } 1135 1136 /** 1137 * Maintains the formatting for javadoc -help. Note the space 1138 * alignment. 1139 */ 1140 @Override 1141 public String toString() { 1142 String opt = name + (name.endsWith(":") ? "" : " ") + parameters; 1143 int optlen = opt.length(); 1144 int spaces = 32 - optlen; 1145 StringBuffer sb = new StringBuffer(" -").append(opt); 1146 for (int i = 0; i < spaces; i++) { 1147 sb.append(" "); 1148 } 1149 sb.append(description); 1150 return sb.toString(); 1151 } 1152 1153 @Override 1154 public int getArgumentCount() { 1155 return argCount; 1156 } 1157 1158 @Override 1159 public boolean matches(String option) { 1160 String arg = option.startsWith("-") ? option.substring(1) : option; 1161 return name.toLowerCase().equals(arg.toLowerCase()); 1162 } 1163 1164 @Override 1165 public int compareTo(Option that) { 1166 return this.getName().compareTo(that.getName()); 1167 } 1168 } 1169 1170 public abstract class XOption extends Option { 1171 1172 public XOption(Configuration config, String keyname, String name, int argCount) { 1173 super(config, keyname, name, argCount); 1174 } 1175 1176 public XOption(Configuration config, String name, int argCount) { 1177 super("doclet.xusage.", config, name, argCount); 1178 } 1179 1180 public XOption(Configuration config, String name) { 1181 this(config, name, 0); 1182 } 1183 1184 @Override 1185 public Option.Kind getKind() { 1186 return Doclet.Option.Kind.EXTENDED; 1187 } 1188 } 1189 1190 public abstract class Hidden extends Option { 1191 1192 public Hidden(Configuration config, String name, int argCount) { 1193 super("doclet.xusage.", config, name, argCount); 1194 } 1195 1196 public Hidden(Configuration config, String name) { 1197 this(config, name, 0); 1198 } 1199 1200 @Override 1201 public Option.Kind getKind() { 1202 return Doclet.Option.Kind.OTHER; 1203 } 1204 } 1205 1206 /* 1207 * Stores a pair of Strings. 1208 */ 1209 protected static class GroupContainer { 1210 final String value1; 1211 final String value2; 1212 public GroupContainer(String value1, String value2) { 1213 this.value1 = value1; 1214 this.value2 = value2; 1215 } 1216 } 1217 1218 public abstract Location getLocationForPackage(PackageElement pd); 1219 }