001/* 002 * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.toolkit.util; 027 028import java.io.BufferedReader; 029import java.io.IOException; 030import java.io.InputStream; 031import java.io.InputStreamReader; 032import java.net.HttpURLConnection; 033import java.net.MalformedURLException; 034import java.net.URISyntaxException; 035import java.net.URL; 036import java.net.URLConnection; 037import java.util.HashMap; 038import java.util.Map; 039import java.util.Properties; 040import java.util.TreeMap; 041 042import javax.lang.model.SourceVersion; 043import javax.lang.model.element.Element; 044import javax.lang.model.element.ModuleElement; 045import javax.lang.model.element.PackageElement; 046import javax.tools.Diagnostic; 047import javax.tools.Diagnostic.Kind; 048 049import org.jdrupes.mdoclet.internal.doclets.toolkit.AbstractDoclet; 050import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseConfiguration; 051import org.jdrupes.mdoclet.internal.doclets.toolkit.Resources; 052 053import javax.tools.DocumentationTool; 054 055import jdk.javadoc.doclet.Reporter; 056 057/** 058 * Process and manage "-link" and "-linkoffline" to external packages. The 059 * options "-link" and "-linkoffline" both depend on the fact that Javadoc now 060 * generates "package-list"(lists all the packages which are getting 061 * documented) file in the current or the destination directory, while 062 * generating the documentation. 063 */ 064public class Extern { 065 066 /** 067 * Map element names onto Extern Item objects. 068 * Lazily initialized. 069 */ 070 private Map<String, Item> moduleItems = new HashMap<>(); 071 private Map<String, Map<String, Item>> packageItems = new HashMap<>(); 072 073 /** 074 * The global configuration information for this run. 075 */ 076 private final BaseConfiguration configuration; 077 078 private final Resources resources; 079 080 private final Utils utils; 081 082 /** 083 * True if we are using -linkoffline and false if -link is used instead. 084 */ 085 private boolean linkoffline = false; 086 087 /** 088 * Stores the info for one external doc set 089 */ 090 private static class Item { 091 092 /** 093 * Element name, found in the "element-list" file in the {@link #path}. 094 */ 095 final String elementName; 096 097 /** 098 * The URL or the directory path at which the element documentation will be 099 * available. 100 */ 101 final DocPath path; 102 103 /** 104 * If given path is directory path then true else if it is a URL then false. 105 */ 106 final boolean relative; 107 108 /** 109 * Indicates that docs use old-form of anchors. 110 */ 111 final boolean useOldFormId; 112 113 /** 114 * Constructor to build a Extern Item object and map it with the element name. 115 * If the same element name is found in the map, then the first mapped 116 * Item object or offline location will be retained. 117 * 118 * @param elementName Element name found in the "element-list" file. 119 * @param path URL or Directory path from where the "element-list" 120 * file is picked. 121 * @param relative True if path is URL, false if directory path. 122 */ 123 Item(String elementName, DocPath path, boolean relative, 124 boolean useOldFormId) { 125 this.elementName = elementName; 126 this.path = path; 127 this.relative = relative; 128 this.useOldFormId = useOldFormId; 129 } 130 131 /** 132 * String representation of "this" with elementname and the path. 133 */ 134 @Override 135 public String toString() { 136 return elementName + (relative ? " -> " : " => ") + path.getPath(); 137 } 138 } 139 140 public Extern(BaseConfiguration configuration) { 141 this.configuration = configuration; 142 this.resources = configuration.getDocResources(); 143 this.utils = configuration.utils; 144 } 145 146 /** 147 * Determine if a element item is externally documented. 148 * 149 * @param element an Element. 150 * @return true if the element is externally documented 151 */ 152 public boolean isExternal(Element element) { 153 if (packageItems.isEmpty()) { 154 return false; 155 } 156 PackageElement pe = utils.containingPackage(element); 157 if (pe.isUnnamed()) { 158 return false; 159 } 160 161 return findElementItem(pe) != null; 162 } 163 164 /** 165 * Determine if a element item is a module or not. 166 * 167 * @param elementName name of the element. 168 * @return true if the element is a module 169 */ 170 public boolean isModule(String elementName) { 171 Item elem = moduleItems.get(elementName); 172 return elem != null; 173 } 174 175 /** 176 * Convert a link to be an external link if appropriate. 177 * 178 * @param element The element . 179 * @param relativepath The relative path. 180 * @param filename The link to convert. 181 * @return if external return converted link else return null 182 */ 183 public DocLink getExternalLink(Element element, DocPath relativepath, 184 String filename) { 185 return getExternalLink(element, relativepath, filename, null); 186 } 187 188 public DocLink getExternalLink(Element element, DocPath relativepath, 189 String filename, 190 String memberName) { 191 Item fnd = findElementItem(element); 192 if (fnd == null) 193 return null; 194 195 // The following is somewhat questionable since we are using DocPath 196 // to contain external URLs! 197 DocPath p 198 = fnd.relative ? relativepath.resolve(fnd.path).resolve(filename) 199 : fnd.path.resolve(filename); 200 return new DocLink(p, 201 fnd.useOldFormId ? getOldFormHtmlName(memberName) : memberName); 202 } 203 204 /** 205 * Build the extern element list from given URL or the directory path, 206 * as specified with the "-link" flag. 207 * Flag error if the "-link" or "-linkoffline" option is already used. 208 * 209 * @param url URL or Directory path. 210 * @param reporter The <code>DocErrorReporter</code> used to report errors. 211 * @return true if successful, false otherwise 212 * @throws DocFileIOException if there is a problem reading a element list file 213 */ 214 public boolean link(String url, Reporter reporter) 215 throws DocFileIOException { 216 return link(url, url, reporter, false); 217 } 218 219 /** 220 * Build the extern element list from given URL or the directory path, 221 * as specified with the "-linkoffline" flag. 222 * Flag error if the "-link" or "-linkoffline" option is already used. 223 * 224 * @param url URL or Directory path. 225 * @param elemlisturl This can be another URL for "element-list" or ordinary 226 * file. 227 * @param reporter The <code>DocErrorReporter</code> used to report errors. 228 * @return true if successful, false otherwise 229 * @throws DocFileIOException if there is a problem reading the element list file 230 */ 231 public boolean link(String url, String elemlisturl, Reporter reporter) 232 throws DocFileIOException { 233 return link(url, elemlisturl, reporter, true); 234 } 235 236 /** 237 * Check whether links to platform documentation are configured. If not then configure 238 * links using the documentation URL defined in {@code linkPlatformProperties} or the 239 * default documentation URL if that parameter is {@code null}. 240 * 241 * @param linkPlatformProperties path or URL to properties file containing 242 * platform documentation URLs, or null 243 * @param reporter the {@code DocErrorReporter} used to report errors 244 */ 245 public void checkPlatformLinks(String linkPlatformProperties, 246 Reporter reporter) { 247 PackageElement javaLang 248 = utils.elementUtils.getPackageElement("java.lang"); 249 if (utils.isIncluded(javaLang)) { 250 return; 251 } 252 DocLink link = getExternalLink(javaLang, DocPath.empty, 253 DocPaths.PACKAGE_SUMMARY.getPath()); 254 if (link != null) { 255 // Links to platform docs are already configure, nothing to do here. 256 return; 257 } 258 try { 259 int versionNumber = getSourceVersionNumber(); 260 String docUrl; 261 262 if (linkPlatformProperties != null) { 263 docUrl = getCustomPlatformDocs(versionNumber, 264 linkPlatformProperties); 265 } else { 266 docUrl = getDefaultPlatformDocs(versionNumber); 267 } 268 if (docUrl == null) { 269 return; 270 } 271 DocPath elementListPath = getPlatformElementList(versionNumber); 272 URL elementListUrl 273 = jdk.javadoc.internal.doclets.toolkit.AbstractDoclet.class 274 .getResource(elementListPath.getPath()); 275 if (elementListUrl == null) { 276 reporter.print(Kind.WARNING, resources.getText( 277 "doclet.Resource_error", elementListPath.getPath())); 278 } else { 279 try (InputStream in = open(elementListUrl)) { 280 readElementList(in, docUrl, false, versionNumber, 281 isOldFormPlatformDocs(versionNumber)); 282 } catch (IOException exc) { 283 throw new Fault(resources.getText( 284 "doclet.Resource_error", elementListPath.getPath()), 285 exc); 286 } 287 } 288 } catch (Fault f) { 289 reporter.print(Kind.ERROR, f.getMessage()); 290 } 291 } 292 293 /** 294 * Checks if platform docs for the specified version use old-form anchors. 295 * Old-form anchors are used by Oracle docs for JDKs 8 and 9. 296 * It can be checked on https://docs.oracle.com/javase/<version>/docs/api 297 * 298 * @param version 299 * @return True if docs use old-form anchors 300 */ 301 private boolean isOldFormPlatformDocs(int version) { 302 return 8 == version || 9 == version; 303 } 304 305 /** 306 * Return the resource path for the package or element list for the given {@code version}. 307 * @param version the platform version number 308 * @return the resource path 309 */ 310 private DocPath getPlatformElementList(int version) { 311 String filename = version <= 8 312 ? "package-list-" + version + ".txt" 313 : "element-list-" + version + ".txt"; 314 return DocPaths.RESOURCES.resolve("releases").resolve(filename); 315 } 316 317 /** 318 * Return the default URL for the platform API documentation for the given {@code version}. 319 * @param version the platform version number 320 * @return the URL as String 321 */ 322 private String getDefaultPlatformDocs(int version) { 323 Resources resources = configuration.getDocResources(); 324 return version <= 10 325 ? resources.getText("doclet.platform.docs.old", version) 326 : isPrerelease(version) 327 ? resources.getText("doclet.platform.docs.ea", version) 328 : resources.getText("doclet.platform.docs.new", version); 329 } 330 331 /** 332 * Retrieve and return the custom URL for the platform API documentation for the given 333 * {@code version} from the properties file at {@code linkPlatformProperties}. 334 * @param version the platform version number 335 * @param linkPlatformProperties path pointing to a properties file 336 * @return the custom URL as String 337 */ 338 private String getCustomPlatformDocs(int version, 339 String linkPlatformProperties) throws Fault { 340 String url; 341 try { 342 Properties props = new Properties(); 343 InputStream inputStream; 344 if (isUrl(linkPlatformProperties)) { 345 inputStream = toURL(linkPlatformProperties).openStream(); 346 } else { 347 inputStream = DocFile 348 .createFileForInput(configuration, linkPlatformProperties) 349 .openInputStream(); 350 } 351 try (inputStream) { 352 props.load(inputStream); 353 } 354 url = props.getProperty("doclet.platform.docs." + version); 355 } catch (MalformedURLException exc) { 356 throw new Fault(resources.getText("doclet.MalformedURL", 357 linkPlatformProperties), exc); 358 } catch (IOException exc) { 359 throw new Fault( 360 resources.getText("doclet.URL_error", linkPlatformProperties), 361 exc); 362 } catch (DocFileIOException exc) { 363 throw new Fault( 364 resources.getText("doclet.File_error", linkPlatformProperties), 365 exc); 366 } 367 return url; 368 } 369 370 /** 371 * Return the source version number used in the current execution of javadoc. 372 * @return the source version number 373 */ 374 private int getSourceVersionNumber() { 375 SourceVersion sourceVersion = configuration.docEnv.getSourceVersion(); 376 // TODO it would be nice if this was provided by SourceVersion 377 String versionNumber = sourceVersion.name().substring(8); 378 assert SourceVersion 379 .valueOf("RELEASE_" + versionNumber) == sourceVersion; 380 return Integer.parseInt(versionNumber); 381 } 382 383 /** 384 * Return true if the given {@code sourceVersion} is the same as the current doclet version 385 * and is a pre-release version. 386 * @param sourceVersion the source version number 387 * @return true if it is a pre-release version 388 */ 389 private boolean isPrerelease(int sourceVersion) { 390 Runtime.Version docletVersion = configuration.getDocletVersion(); 391 return docletVersion.feature() == sourceVersion 392 && docletVersion.pre().isPresent(); 393 } 394 395 /* 396 * Build the extern element list from given URL or the directory path. 397 * Flag error if the "-link" or "-linkoffline" option is already used. 398 * 399 * @param url URL or Directory path. 400 * 401 * @param elemlisturl This can be another URL for "element-list" or ordinary 402 * file. 403 * 404 * @param reporter The <code>DocErrorReporter</code> used to report errors. 405 * 406 * @param linkoffline True if -linkoffline is used and false if -link is 407 * used. 408 * 409 * @return true if successful, false otherwise 410 * 411 * @throws DocFileIOException if there is a problem reading the element list 412 * file 413 */ 414 private boolean link(String url, String elemlisturl, Reporter reporter, 415 boolean linkoffline) 416 throws DocFileIOException { 417 this.linkoffline = linkoffline; 418 try { 419 url = adjustEndFileSeparator(url); 420 if (isUrl(elemlisturl)) { 421 readElementListFromURL(url, 422 toURL(adjustEndFileSeparator(elemlisturl))); 423 } else { 424 readElementListFromFile(url, 425 DocFile.createFileForInput(configuration, elemlisturl)); 426 } 427 return true; 428 } catch (Fault f) { 429 reporter.print(Diagnostic.Kind.ERROR, f.getMessage()); 430 return false; 431 } 432 } 433 434 private static class Fault extends Exception { 435 private static final long serialVersionUID = 0; 436 437 Fault(String msg, Exception cause) { 438 super(msg + (cause == null ? "" : " (" + cause + ")"), cause); 439 } 440 } 441 442 /** 443 * Get the Extern Item object associated with this element name. 444 * 445 * @param element Element 446 */ 447 private Item findElementItem(Element element) { 448 Item item = null; 449 if (element instanceof ModuleElement me) { 450 item = moduleItems.get(utils.getModuleName(me)); 451 } else if (element instanceof PackageElement pkg) { 452 ModuleElement moduleElement = utils.containingModule(pkg); 453 Map<String, Item> pkgMap 454 = packageItems.get(utils.getModuleName(moduleElement)); 455 item = (pkgMap != null) ? pkgMap.get(utils.getPackageName(pkg)) 456 : null; 457 } 458 return item; 459 } 460 461 /** 462 * If the URL or Directory path is missing end file separator, add that. 463 */ 464 private String adjustEndFileSeparator(String url) { 465 return url.endsWith("/") ? url : url + '/'; 466 } 467 468 /** 469 * Fetch the URL and read the "element-list" file. 470 * 471 * @param urlpath Path to the elements. 472 * @param elemlisturlpath URL or the path to the "element-list" file. 473 */ 474 private void readElementListFromURL(String urlpath, URL elemlisturlpath) 475 throws Fault { 476 try { 477 URL link = elemlisturlpath.toURI() 478 .resolve(DocPaths.ELEMENT_LIST.getPath()).toURL(); 479 try (InputStream in = open(link)) { 480 readElementList(in, urlpath, false, 0, false); 481 } 482 } catch (URISyntaxException | MalformedURLException exc) { 483 throw new Fault(resources.getText("doclet.MalformedURL", 484 elemlisturlpath.toString()), exc); 485 } catch (IOException exc) { 486 readPackageListFromURL(urlpath, elemlisturlpath); 487 } 488 } 489 490 /** 491 * Fetch the URL and read the "package-list" file. 492 * 493 * @param urlpath Path to the packages. 494 * @param elemlisturlpath URL or the path to the "package-list" file. 495 */ 496 private void readPackageListFromURL(String urlpath, URL elemlisturlpath) 497 throws Fault { 498 try { 499 URL link = elemlisturlpath.toURI() 500 .resolve(DocPaths.PACKAGE_LIST.getPath()).toURL(); 501 try (InputStream in = open(link)) { 502 readElementList(in, urlpath, false, 0, true); 503 } 504 } catch (URISyntaxException | MalformedURLException exc) { 505 throw new Fault(resources.getText("doclet.MalformedURL", 506 elemlisturlpath.toString()), exc); 507 } catch (IOException exc) { 508 throw new Fault(resources.getText("doclet.URL_error", 509 elemlisturlpath.toString()), exc); 510 } 511 } 512 513 /** 514 * Read the "element-list" file which is available locally. 515 * 516 * @param path URL or directory path to the elements. 517 * @param elemListPath Path to the local "element-list" file. 518 * @throws Fault if an error occurs that can be treated as a warning 519 * @throws DocFileIOException if there is a problem opening the element list file 520 */ 521 private void readElementListFromFile(String path, DocFile elemListPath) 522 throws Fault, DocFileIOException { 523 DocFile file = elemListPath.resolve(DocPaths.ELEMENT_LIST); 524 if (!(file.isAbsolute() || linkoffline)) { 525 file = file.resolveAgainst( 526 DocumentationTool.Location.DOCUMENTATION_OUTPUT); 527 } 528 if (file.exists()) { 529 readElementList(file, path, false); 530 } else { 531 DocFile file1 = elemListPath.resolve(DocPaths.PACKAGE_LIST); 532 if (!(file1.isAbsolute() || linkoffline)) { 533 file1 = file1.resolveAgainst( 534 DocumentationTool.Location.DOCUMENTATION_OUTPUT); 535 } 536 if (file1.exists()) { 537 readElementList(file1, path, true); 538 } else { 539 throw new Fault( 540 resources.getText("doclet.File_error", file.getPath()), 541 null); 542 } 543 } 544 } 545 546 private void readElementList(DocFile file, String path, 547 boolean isOldFormDoc) throws Fault, DocFileIOException { 548 try { 549 if (file.canRead()) { 550 boolean pathIsRelative 551 = !isUrl(path) 552 && !DocFile.createFileForInput(configuration, path) 553 .isAbsolute(); 554 readElementList(file.openInputStream(), path, pathIsRelative, 0, 555 isOldFormDoc); 556 } else { 557 throw new Fault( 558 resources.getText("doclet.File_error", file.getPath()), 559 null); 560 } 561 } catch (IOException exc) { 562 throw new Fault( 563 resources.getText("doclet.File_error", file.getPath()), exc); 564 } 565 } 566 567 /** 568 * Read the file "element-list" and for each element name found, create 569 * Extern object and associate it with the element name in the map. 570 * 571 * @param input InputStream from the "element-list" file. 572 * @param path URL or the directory path to the elements. 573 * @param relative Is path relative? 574 * @param platformVersion The version of platform libraries the element list belongs to, 575 * or {@code 0} if it does not belong to a platform libraries doc bundle. 576 * @throws IOException if there is a problem reading or closing the stream 577 */ 578 private void readElementList(InputStream input, String path, 579 boolean relative, int platformVersion, 580 boolean isOldFormDoc) 581 throws IOException { 582 try (BufferedReader in 583 = new BufferedReader(new InputStreamReader(input))) { 584 String elemname; 585 DocPath elempath; 586 String moduleName = null; 587 DocPath basePath = DocPath.create(path); 588 boolean showDiagnostic = true; 589 while ((elemname = in.readLine()) != null) { 590 if (elemname.length() > 0) { 591 elempath = basePath; 592 if (elemname.startsWith(DocletConstants.MODULE_PREFIX)) { 593 moduleName = elemname 594 .replace(DocletConstants.MODULE_PREFIX, ""); 595 Item item = new Item(moduleName, elempath, relative, 596 isOldFormDoc); 597 moduleItems.put(moduleName, item); 598 } else { 599 DocPath pkgPath 600 = DocPath.create(elemname.replace('.', '/')); 601 // Although being modular, JDKs 9 and 10 do not use 602 // module names in javadoc URL paths. 603 if (moduleName != null && platformVersion != 9 604 && platformVersion != 10) { 605 elempath = elempath.resolve( 606 DocPath.create(moduleName).resolve(pkgPath)); 607 } else { 608 elempath = elempath.resolve(pkgPath); 609 } 610 String actualModuleName; 611 // For user provided libraries we check whether 612 // modularity matches the actual library. 613 // We trust modularity to be correct for platform 614 // library element lists. 615 if (platformVersion == 0) { 616 actualModuleName = checkLinkCompatibility(elemname, 617 moduleName, path, showDiagnostic); 618 } else { 619 actualModuleName = moduleName == null 620 ? DocletConstants.DEFAULT_ELEMENT_NAME 621 : moduleName; 622 } 623 Item item = new Item(elemname, elempath, relative, 624 isOldFormDoc); 625 packageItems 626 .computeIfAbsent(actualModuleName, 627 k -> new TreeMap<>()) 628 .putIfAbsent(elemname, item); // first-one-wins 629 // semantics 630 showDiagnostic = false; 631 } 632 } 633 } 634 } 635 } 636 637 /** 638 * Check if the external documentation format matches our internal model of the code. 639 * Returns the module name to use for external reference lookup according to the actual 640 * modularity of the external package (and regardless of modularity of documentation). 641 * 642 * @param packageName the package name 643 * @param moduleName the module name or null 644 * @param path the documentation path 645 * @param showDiagnostic whether to print a diagnostic message in case of modularity mismatch 646 * @return the module name to use according to actual modularity of the package 647 */ 648 private String checkLinkCompatibility(String packageName, String moduleName, 649 String path, boolean showDiagnostic) { 650 PackageElement pe = utils.elementUtils.getPackageElement(packageName); 651 if (pe != null) { 652 ModuleElement me = (ModuleElement) pe.getEnclosingElement(); 653 if (me == null || me.isUnnamed()) { 654 if (moduleName != null && showDiagnostic) { 655 printModularityMismatchDiagnostic( 656 "doclet.linkMismatch_PackagedLinkedtoModule", path); 657 } 658 // library is not modular, ignore module name even if 659 // documentation is modular 660 return DocletConstants.DEFAULT_ELEMENT_NAME; 661 } else if (moduleName == null) { 662 // suppress the diagnostic message in the case of automatic 663 // modules 664 if (!utils.elementUtils.isAutomaticModule(me) 665 && showDiagnostic) { 666 printModularityMismatchDiagnostic( 667 "doclet.linkMismatch_ModuleLinkedtoPackage", path); 668 } 669 // library is modular, use module name for lookup even though 670 // documentation is not 671 return utils.getModuleName(me); 672 } 673 } 674 return moduleName == null ? DocletConstants.DEFAULT_ELEMENT_NAME 675 : moduleName; 676 } 677 678 public boolean isUrl(String urlCandidate) { 679 try { 680 @SuppressWarnings("deprecation") 681 var _unused = new URL(urlCandidate); 682 // No exception was thrown, so this must really be a URL. 683 return true; 684 } catch (MalformedURLException e) { 685 // Since exception is thrown, this must be a directory path. 686 return false; 687 } 688 } 689 690 @SuppressWarnings("deprecation") 691 private URL toURL(String url) throws Fault { 692 try { 693 return new URL(url); 694 } catch (MalformedURLException e) { 695 throw new Fault(resources.getText("doclet.MalformedURL", url), e); 696 } 697 } 698 699 /** 700 * Open a stream to a URL, following a limited number of redirects 701 * if necessary. 702 * 703 * @param url the URL 704 * @return the stream 705 * @throws IOException if an error occurred accessing the URL 706 */ 707 private InputStream open(URL url) throws IOException { 708 URLConnection conn = url.openConnection(); 709 710 boolean redir; 711 int redirects = 0; 712 InputStream in; 713 714 do { 715 // Open the input stream before getting headers, 716 // because getHeaderField() et al swallow IOExceptions. 717 in = conn.getInputStream(); 718 redir = false; 719 720 if (conn instanceof HttpURLConnection http) { 721 int stat = http.getResponseCode(); 722 // See: 723 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 724 // https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection 725 switch (stat) { 726 case 300: // Multiple Choices 727 case 301: // Moved Permanently 728 case 302: // Found (previously Moved Temporarily) 729 case 303: // See Other 730 case 307: // Temporary Redirect 731 case 308: // Permanent Redirect 732 URL base = http.getURL(); 733 String loc = http.getHeaderField("Location"); 734 URL target = null; 735 if (loc != null) { 736 @SuppressWarnings("deprecation") 737 var _unused = target = new URL(base, loc); 738 } 739 http.disconnect(); 740 if (target == null || redirects >= 5) { 741 throw new IOException("illegal URL redirect"); 742 } 743 redir = true; 744 conn = target.openConnection(); 745 redirects++; 746 } 747 } 748 } while (redir); 749 750 if (!url.equals(conn.getURL())) { 751 configuration.getReporter().print(Kind.WARNING, 752 resources.getText("doclet.urlRedirected", url, conn.getURL())); 753 } 754 755 return in; 756 } 757 758 private void printModularityMismatchDiagnostic(String key, Object arg) { 759 switch (configuration.getOptions().linkModularityMismatch()) { 760 case INFO -> configuration.getMessages().notice(key, arg); 761 case WARN -> configuration.getMessages().warning(key, arg); 762 } 763 } 764 765 /** 766 * Converts a name to an old-form HTML name (old-form id). 767 * 768 * @param name the string that needs to be converted to a valid HTML name 769 * @return old-form HTML name 770 */ 771 private String getOldFormHtmlName(String name) { 772 /* 773 * The HTML 4 spec at http://www.w3.org/TR/html4/types.html#h-6.2 774 * mentions 775 * that the name/id should begin with a letter followed by other valid 776 * characters. 777 * The HTML 5 spec (draft) is more permissive on names/ids where the 778 * only restriction 779 * is that it should be at least one character long and should not 780 * contain spaces. 781 * The spec draft is @ 782 * http://www.w3.org/html/wg/drafts/html/master/dom.html#the-id- 783 * attribute. 784 * 785 * For HTML 4, we need to check for non-characters at the beginning of 786 * the name and 787 * substitute it accordingly, "_" and "$" can appear at the beginning of 788 * a member name. 789 * The method substitutes "$" with "Z:Z:D" and will prefix "_" with 790 * "Z:Z". 791 */ 792 793 if (null == name) 794 return name; 795 796 StringBuilder sb = new StringBuilder(); 797 for (int i = 0; i < name.length(); i++) { 798 char ch = name.charAt(i); 799 switch (ch) { 800 case '(': 801 case ')': 802 case '<': 803 case '>': 804 case ',': 805 sb.append('-'); 806 break; 807 case ' ': 808 case '[': 809 break; 810 case ']': 811 sb.append(":A"); 812 break; 813 // Any appearance of $ needs to be substituted with ":D" and not 814 // with hyphen 815 // since a field name "P$$ and a method P(), both valid member 816 // names, can end 817 // up as "P--". A member name beginning with $ needs to be 818 // substituted with 819 // "Z:Z:D". 820 case '$': 821 if (i == 0) 822 sb.append("Z:Z"); 823 sb.append(":D"); 824 break; 825 // A member name beginning with _ needs to be prefixed with "Z:Z" 826 // since valid anchor 827 // names can only begin with a letter. 828 case '_': 829 if (i == 0) 830 sb.append("Z:Z"); 831 sb.append(ch); 832 break; 833 default: 834 sb.append(ch); 835 } 836 } 837 return sb.toString(); 838 } 839}