11/*
2- * Copyright (c) 1998, 2021 , Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 1998, 2023 , Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * This code is free software; you can redistribute it and/or modify it
@@ -108,6 +108,11 @@ private static class Item {
108108 */
109109 final boolean relative ;
110110
111+ /**
112+ * Indicates that docs use old-form of anchors.
113+ */
114+ final boolean useOldFormId ;
115+
111116 /**
112117 * Constructor to build a Extern Item object and map it with the element name.
113118 * If the same element name is found in the map, then the first mapped
@@ -118,10 +123,11 @@ private static class Item {
118123 * file is picked.
119124 * @param relative True if path is URL, false if directory path.
120125 */
121- Item (String elementName , DocPath path , boolean relative ) {
126+ Item (String elementName , DocPath path , boolean relative , boolean useOldFormId ) {
122127 this .elementName = elementName ;
123128 this .path = path ;
124129 this .relative = relative ;
130+ this .useOldFormId = useOldFormId ;
125131 }
126132
127133 /**
@@ -191,7 +197,7 @@ public DocLink getExternalLink(Element element, DocPath relativepath, String fil
191197 DocPath p = fnd .relative ?
192198 relativepath .resolve (fnd .path ).resolve (filename ) :
193199 fnd .path .resolve (filename );
194- return new DocLink (p , memberName );
200+ return new DocLink (p , fnd . useOldFormId ? getOldFormHtmlName ( memberName ) : memberName );
195201 }
196202
197203 /**
@@ -261,7 +267,7 @@ public void checkPlatformLinks(String linkPlatformProperties, Reporter reporter)
261267 reporter .print (Kind .WARNING , resources .getText ("doclet.Resource_error" , elementListPath .getPath ()));
262268 } else {
263269 try (InputStream in = open (elementListUrl )) {
264- readElementList (in , docUrl , false , versionNumber );
270+ readElementList (in , docUrl , false , versionNumber , isOldFormPlatformDocs ( versionNumber ) );
265271 } catch (IOException exc ) {
266272 throw new Fault (resources .getText (
267273 "doclet.Resource_error" , elementListPath .getPath ()), exc );
@@ -272,6 +278,18 @@ public void checkPlatformLinks(String linkPlatformProperties, Reporter reporter)
272278 }
273279 }
274280
281+ /**
282+ * Checks if platform docs for the specified version use old-form anchors.
283+ * Old-form anchors are used by Oracle docs for JDKs 8 and 9.
284+ * It can be checked on https://docs.oracle.com/javase/<version>/docs/api
285+ *
286+ * @param version
287+ * @return True if docs use old-form anchors
288+ */
289+ private boolean isOldFormPlatformDocs (int version ) {
290+ return 8 == version || 9 == version ;
291+ }
292+
275293 /**
276294 * Return the resource path for the package or element list for the given {@code version}.
277295 * @param version the platform version number
@@ -424,7 +442,7 @@ private void readElementListFromURL(String urlpath, URL elemlisturlpath) throws
424442 try {
425443 URL link = elemlisturlpath .toURI ().resolve (DocPaths .ELEMENT_LIST .getPath ()).toURL ();
426444 try (InputStream in = open (link )) {
427- readElementList (in , urlpath , false , 0 );
445+ readElementList (in , urlpath , false , 0 , false );
428446 }
429447 } catch (URISyntaxException | MalformedURLException exc ) {
430448 throw new Fault (resources .getText ("doclet.MalformedURL" , elemlisturlpath .toString ()), exc );
@@ -443,7 +461,7 @@ private void readPackageListFromURL(String urlpath, URL elemlisturlpath) throws
443461 try {
444462 URL link = elemlisturlpath .toURI ().resolve (DocPaths .PACKAGE_LIST .getPath ()).toURL ();
445463 try (InputStream in = open (link )) {
446- readElementList (in , urlpath , false , 0 );
464+ readElementList (in , urlpath , false , 0 , true );
447465 }
448466 } catch (URISyntaxException | MalformedURLException exc ) {
449467 throw new Fault (resources .getText ("doclet.MalformedURL" , elemlisturlpath .toString ()), exc );
@@ -467,27 +485,27 @@ private void readElementListFromFile(String path, DocFile elemListPath)
467485 file = file .resolveAgainst (DocumentationTool .Location .DOCUMENTATION_OUTPUT );
468486 }
469487 if (file .exists ()) {
470- readElementList (file , path );
488+ readElementList (file , path , false );
471489 } else {
472490 DocFile file1 = elemListPath .resolve (DocPaths .PACKAGE_LIST );
473491 if (!(file1 .isAbsolute () || linkoffline )) {
474492 file1 = file1 .resolveAgainst (DocumentationTool .Location .DOCUMENTATION_OUTPUT );
475493 }
476494 if (file1 .exists ()) {
477- readElementList (file1 , path );
495+ readElementList (file1 , path , true );
478496 } else {
479497 throw new Fault (resources .getText ("doclet.File_error" , file .getPath ()), null );
480498 }
481499 }
482500 }
483501
484- private void readElementList (DocFile file , String path ) throws Fault , DocFileIOException {
502+ private void readElementList (DocFile file , String path , boolean isOldFormDoc ) throws Fault , DocFileIOException {
485503 try {
486504 if (file .canRead ()) {
487505 boolean pathIsRelative
488506 = !isUrl (path )
489507 && !DocFile .createFileForInput (configuration , path ).isAbsolute ();
490- readElementList (file .openInputStream (), path , pathIsRelative , 0 );
508+ readElementList (file .openInputStream (), path , pathIsRelative , 0 , isOldFormDoc );
491509 } else {
492510 throw new Fault (resources .getText ("doclet.File_error" , file .getPath ()), null );
493511 }
@@ -507,7 +525,8 @@ private void readElementList(DocFile file, String path) throws Fault, DocFileIOE
507525 * or {@code 0} if it does not belong to a platform libraries doc bundle.
508526 * @throws IOException if there is a problem reading or closing the stream
509527 */
510- private void readElementList (InputStream input , String path , boolean relative , int platformVersion )
528+ private void readElementList (InputStream input , String path , boolean relative , int platformVersion ,
529+ boolean isOldFormDoc )
511530 throws IOException {
512531 try (BufferedReader in = new BufferedReader (new InputStreamReader (input ))) {
513532 String elemname ;
@@ -520,7 +539,7 @@ private void readElementList(InputStream input, String path, boolean relative, i
520539 elempath = basePath ;
521540 if (elemname .startsWith (DocletConstants .MODULE_PREFIX )) {
522541 moduleName = elemname .replace (DocletConstants .MODULE_PREFIX , "" );
523- Item item = new Item (moduleName , elempath , relative );
542+ Item item = new Item (moduleName , elempath , relative , isOldFormDoc );
524543 moduleItems .put (moduleName , item );
525544 } else {
526545 DocPath pkgPath = DocPath .create (elemname .replace ('.' , '/' ));
@@ -538,7 +557,7 @@ private void readElementList(InputStream input, String path, boolean relative, i
538557 } else {
539558 actualModuleName = moduleName == null ? DocletConstants .DEFAULT_ELEMENT_NAME : moduleName ;
540559 }
541- Item item = new Item (elemname , elempath , relative );
560+ Item item = new Item (elemname , elempath , relative , isOldFormDoc );
542561 packageItems .computeIfAbsent (actualModuleName , k -> new TreeMap <>())
543562 .putIfAbsent (elemname , item ); // first-one-wins semantics
544563 issueWarning = false ;
@@ -659,4 +678,65 @@ private InputStream open(URL url) throws IOException {
659678
660679 return in ;
661680 }
681+
682+ /**
683+ * Converts a name to an old-form HTML name (old-form id).
684+ *
685+ * @param name the string that needs to be converted to a valid HTML name
686+ * @return old-form HTML name
687+ */
688+ private String getOldFormHtmlName (String name ) {
689+ /* The HTML 4 spec at http://www.w3.org/TR/html4/types.html#h-6.2 mentions
690+ * that the name/id should begin with a letter followed by other valid characters.
691+ * The HTML 5 spec (draft) is more permissive on names/ids where the only restriction
692+ * is that it should be at least one character long and should not contain spaces.
693+ * The spec draft is @ http://www.w3.org/html/wg/drafts/html/master/dom.html#the-id-attribute.
694+ *
695+ * For HTML 4, we need to check for non-characters at the beginning of the name and
696+ * substitute it accordingly, "_" and "$" can appear at the beginning of a member name.
697+ * The method substitutes "$" with "Z:Z:D" and will prefix "_" with "Z:Z".
698+ */
699+
700+ if (null == name )
701+ return name ;
702+
703+ StringBuilder sb = new StringBuilder ();
704+ for (int i = 0 ; i < name .length (); i ++) {
705+ char ch = name .charAt (i );
706+ switch (ch ) {
707+ case '(' :
708+ case ')' :
709+ case '<' :
710+ case '>' :
711+ case ',' :
712+ sb .append ('-' );
713+ break ;
714+ case ' ' :
715+ case '[' :
716+ break ;
717+ case ']' :
718+ sb .append (":A" );
719+ break ;
720+ // Any appearance of $ needs to be substituted with ":D" and not with hyphen
721+ // since a field name "P$$ and a method P(), both valid member names, can end
722+ // up as "P--". A member name beginning with $ needs to be substituted with
723+ // "Z:Z:D".
724+ case '$' :
725+ if (i == 0 )
726+ sb .append ("Z:Z" );
727+ sb .append (":D" );
728+ break ;
729+ // A member name beginning with _ needs to be prefixed with "Z:Z" since valid anchor
730+ // names can only begin with a letter.
731+ case '_' :
732+ if (i == 0 )
733+ sb .append ("Z:Z" );
734+ sb .append (ch );
735+ break ;
736+ default :
737+ sb .append (ch );
738+ }
739+ }
740+ return sb .toString ();
741+ }
662742}
0 commit comments