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.formats.html; 027 028import java.util.List; 029import java.util.ListIterator; 030import java.util.SortedSet; 031import java.util.stream.Stream; 032 033import javax.lang.model.element.Element; 034import javax.lang.model.element.ElementKind; 035import javax.lang.model.element.ModuleElement; 036import javax.lang.model.element.PackageElement; 037import javax.lang.model.element.TypeElement; 038 039import org.jdrupes.mdoclet.internal.doclets.formats.html.Navigation.PageMode; 040import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.BodyContents; 041import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder; 042import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Entity; 043import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlStyle; 044import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree; 045import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.TagName; 046import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Text; 047import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 048import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException; 049import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath; 050import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths; 051import org.jdrupes.mdoclet.internal.doclets.toolkit.util.IndexBuilder; 052import org.jdrupes.mdoclet.internal.doclets.toolkit.util.IndexItem; 053 054import com.sun.source.doctree.DeprecatedTree; 055 056/** 057 * Generator for either a single index or split index for all 058 * documented elements, terms defined in some documentation comments, 059 * and summary pages. 060 * 061 * @see IndexBuilder 062 */ 063public class IndexWriter extends HtmlDocletWriter { 064 065 protected final IndexBuilder mainIndex; 066 protected final boolean splitIndex; 067 068 /** 069 * Generates the main index of all documented elements, terms defined in some documentation 070 * comments, and summary pages. 071 * 072 * If {@link HtmlOptions#splitIndex()} is true, a separate page is generated for each 073 * initial letter; otherwise, a single page is generated for all items in the index. 074 * 075 * @param configuration the configuration 076 * @throws DocFileIOException if an error occurs while writing the files 077 */ 078 public static void generate(HtmlConfiguration configuration) 079 throws DocFileIOException { 080 IndexBuilder mainIndex = configuration.mainIndex; 081 List<Character> firstCharacters = mainIndex.getFirstCharacters(); 082 if (configuration.getOptions().splitIndex()) { 083 ListIterator<Character> iter = firstCharacters.listIterator(); 084 while (iter.hasNext()) { 085 Character ch = iter.next(); 086 DocPath file = DocPaths.INDEX_FILES 087 .resolve(DocPaths.indexN(iter.nextIndex())); 088 IndexWriter writer = new IndexWriter(configuration, file); 089 writer.generateIndexFile(firstCharacters, List.of(ch)); 090 } 091 } else { 092 IndexWriter writer 093 = new IndexWriter(configuration, DocPaths.INDEX_ALL); 094 writer.generateIndexFile(firstCharacters, firstCharacters); 095 } 096 } 097 098 /** 099 * Creates a writer that can write a page containing some or all of the overall index. 100 * 101 * @param configuration the current configuration 102 * @param path the file to be generated 103 */ 104 protected IndexWriter(HtmlConfiguration configuration, DocPath path) { 105 super(configuration, path); 106 this.mainIndex = configuration.mainIndex; 107 this.splitIndex = configuration.getOptions().splitIndex(); 108 } 109 110 /** 111 * Generates a page containing some or all of the overall index. 112 * 113 * @param allFirstCharacters the initial characters of all index items 114 * @param displayFirstCharacters the initial characters of the index items to appear on this page 115 * @throws DocFileIOException if an error occurs while writing the page 116 */ 117 protected void generateIndexFile(List<Character> allFirstCharacters, 118 List<Character> displayFirstCharacters) throws DocFileIOException { 119 String title = splitIndex 120 ? resources.getText("doclet.Window_Split_Index", 121 displayFirstCharacters.get(0)) 122 : resources.getText("doclet.Window_Single_Index"); 123 HtmlTree body = getBody(getWindowTitle(title)); 124 Content mainContent = new ContentBuilder(); 125 addLinksForIndexes(allFirstCharacters, mainContent); 126 for (Character ch : displayFirstCharacters) { 127 addContents(ch, mainIndex.getItems(ch), mainContent); 128 } 129 addLinksForIndexes(allFirstCharacters, mainContent); 130 body.add(new BodyContents() 131 .setHeader(getHeader(PageMode.INDEX)) 132 .addMainContent(HtmlTree.DIV(HtmlStyle.header, 133 HtmlTree.HEADING(Headings.PAGE_TITLE_HEADING, 134 contents.getContent("doclet.Index")))) 135 .addMainContent(mainContent) 136 .setFooter(getFooter())); 137 138 String description 139 = splitIndex ? "index: " + displayFirstCharacters.get(0) : "index"; 140 printHtmlDocument(null, description, body); 141 } 142 143 /** 144 * Adds a set of items to the page. 145 * 146 * @param ch the first character of the names of the items 147 * @param items the items 148 * @param content the content to which to add the items 149 */ 150 protected void addContents(char ch, SortedSet<IndexItem> items, 151 Content content) { 152 addHeading(ch, content); 153 154 var dl = HtmlTree.DL(HtmlStyle.index); 155 for (IndexItem item : items) { 156 addDescription(item, dl); 157 } 158 content.add(dl); 159 } 160 161 /** 162 * Adds a heading containing the first character for a set of items. 163 * 164 * @param ch the first character of the names of the items 165 * @param content the content to which to add the items 166 */ 167 protected void addHeading(char ch, Content content) { 168 Content headContent = Text.of(String.valueOf(ch)); 169 var heading = HtmlTree 170 .HEADING(Headings.CONTENT_HEADING, HtmlStyle.title, headContent) 171 .setId(HtmlIds.forIndexChar(ch)); 172 content.add(heading); 173 } 174 175 /** 176 * Adds the description for an index item into a list. 177 * 178 * @param indexItem the item 179 * @param dl the list 180 */ 181 protected void addDescription(IndexItem indexItem, Content dl) { 182 if (indexItem.isTagItem()) { 183 addTagDescription(indexItem, dl); 184 } else if (indexItem.isElementItem()) { 185 addElementDescription(indexItem, dl); 186 } 187 } 188 189 /** 190 * Add one line summary comment for the item. 191 * 192 * @param item the item to be documented 193 * @param target the content to which the description will be added 194 */ 195 protected void addElementDescription(IndexItem item, Content target) { 196 Content dt; 197 Element element = item.getElement(); 198 String label = item.getLabel(); 199 switch (element.getKind()) { 200 case MODULE: 201 dt = HtmlTree 202 .DT(getModuleLink((ModuleElement) element, Text.of(label))); 203 dt.add(" - ").add(contents.module_).add(" " + label); 204 break; 205 206 case PACKAGE: 207 dt = HtmlTree 208 .DT(getPackageLink((PackageElement) element, Text.of(label))); 209 if (configuration.showModules) { 210 item.setContainingModule(utils 211 .getFullyQualifiedName(utils.containingModule(element))); 212 } 213 dt.add(" - ").add(contents.package_).add(" " + label); 214 break; 215 216 case CLASS: 217 case ENUM: 218 case RECORD: 219 case ANNOTATION_TYPE: 220 case INTERFACE: 221 dt = HtmlTree.DT(getLink(new HtmlLinkInfo(configuration, 222 HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS_IN_LABEL, 223 (TypeElement) element).style(HtmlStyle.typeNameLink))); 224 dt.add(" - "); 225 addClassInfo((TypeElement) element, dt); 226 break; 227 228 case CONSTRUCTOR: 229 case METHOD: 230 case FIELD: 231 case ENUM_CONSTANT: 232 TypeElement containingType = item.getContainingTypeElement(); 233 dt = HtmlTree 234 .DT(getDocLink(HtmlLinkInfo.Kind.PLAIN, containingType, element, 235 label, HtmlStyle.memberNameLink)); 236 dt.add(" - "); 237 addMemberDesc(element, containingType, dt); 238 break; 239 240 default: 241 throw new Error(); 242 } 243 target.add(dt); 244 var dd = new HtmlTree(TagName.DD); 245 if (element.getKind() == ElementKind.MODULE 246 || element.getKind() == ElementKind.PACKAGE) { 247 addSummaryComment(element, dd); 248 } else { 249 addComment(element, dd); 250 } 251 target.add(dd); 252 } 253 254 /** 255 * Adds information for the given type element. 256 * 257 * @param te the element 258 * @param content the content to which the class info will be added 259 */ 260 protected void addClassInfo(TypeElement te, Content content) { 261 content.add(contents.getContent("doclet.in", 262 utils.getTypeElementKindName(te, false), 263 getPackageLink(utils.containingPackage(te), 264 getLocalizedPackageName(utils.containingPackage(te))))); 265 } 266 267 /** 268 * Adds a description for an item found in a documentation comment. 269 * 270 * @param item the item 271 * @param target the list to which to add the description 272 */ 273 protected void addTagDescription(IndexItem item, Content target) { 274 String itemPath 275 = pathToRoot.isEmpty() ? "" : pathToRoot.getPath() + "/"; 276 itemPath += item.getUrl(); 277 var labelLink = HtmlTree.A(itemPath, Text.of(item.getLabel())); 278 var dt = HtmlTree.DT(labelLink.setStyle(HtmlStyle.searchTagLink)); 279 dt.add(" - "); 280 dt.add(contents.getContent("doclet.Search_tag_in", item.getHolder())); 281 target.add(dt); 282 var dd = new HtmlTree(TagName.DD); 283 if (item.getDescription().isEmpty()) { 284 dd.add(Entity.NO_BREAK_SPACE); 285 } else { 286 dd.add(item.getDescription()); 287 } 288 target.add(dd); 289 } 290 291 /** 292 * Adds a comment for an element in the index. If the element is deprecated 293 * and it has a @deprecated tag, use that comment; otherwise, if the containing 294 * class for this element is deprecated, then add the word "Deprecated." at 295 * the start and then print the normal comment. 296 * 297 * @param element the element 298 * @param content the content to which the comment will be added 299 */ 300 protected void addComment(Element element, Content content) { 301 var span = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, 302 getDeprecatedPhrase(element)); 303 var div = HtmlTree.DIV(HtmlStyle.deprecationBlock); 304 if (utils.isDeprecated(element)) { 305 div.add(span); 306 List<? extends DeprecatedTree> tags 307 = utils.getDeprecatedTrees(element); 308 if (!tags.isEmpty()) 309 addInlineDeprecatedComment(element, tags.get(0), div); 310 content.add(div); 311 } else { 312 TypeElement encl = utils.getEnclosingTypeElement(element); 313 while (encl != null) { 314 if (utils.isDeprecated(encl)) { 315 div.add(span); 316 content.add(div); 317 break; 318 } 319 encl = utils.getEnclosingTypeElement(encl); 320 } 321 addSummaryComment(element, content); 322 } 323 } 324 325 /** 326 * Adds a description for a member element. 327 * 328 * @param member the element 329 * @param enclosing the enclosing type element 330 * @param content the content to which the member description will be added 331 */ 332 protected void addMemberDesc(Element member, TypeElement enclosing, 333 Content content) { 334 String kindName = utils.getTypeElementKindName(enclosing, true); 335 String resource = switch (member.getKind()) { 336 case ENUM_CONSTANT -> "doclet.Enum_constant_in"; 337 case FIELD -> utils.isStatic(member) ? "doclet.Static_variable_in" 338 : "doclet.Variable_in"; 339 case CONSTRUCTOR -> "doclet.Constructor_for"; 340 case METHOD -> utils.isAnnotationInterface(enclosing) 341 ? "doclet.Element_in" 342 : utils.isStatic(member) ? "doclet.Static_method_in" 343 : "doclet.Method_in"; 344 case RECORD_COMPONENT -> "doclet.Record_component_in"; 345 default -> throw new IllegalArgumentException( 346 member.getKind().toString()); 347 }; 348 content.add(contents.getContent(resource, kindName)).add(" "); 349 addPreQualifiedClassLink(HtmlLinkInfo.Kind.PLAIN, enclosing, 350 null, content); 351 } 352 353 /** 354 * Add links for all the index files, based on the first character of the names of the items. 355 * 356 * @param allFirstCharacters the list of all first characters to be linked 357 * @param content the content to which the links for indexes will be added 358 */ 359 protected void addLinksForIndexes(List<Character> allFirstCharacters, 360 Content content) { 361 ListIterator<Character> iter = allFirstCharacters.listIterator(); 362 while (iter.hasNext()) { 363 char ch = iter.next(); 364 Content label = Text.of(Character.toString(ch)); 365 Content link = splitIndex 366 ? links.createLink(DocPaths.indexN(iter.nextIndex()), label) 367 : links.createLink(HtmlIds.forIndexChar(ch), label); 368 content.add(link); 369 content.add(Entity.NO_BREAK_SPACE); 370 } 371 372 content.add(new HtmlTree(TagName.BR)); 373 List<Content> pageLinks = Stream.of(IndexItem.Category.values()) 374 .flatMap(c -> mainIndex.getItems(c).stream()) 375 .filter(i -> !(i.isElementItem() || i.isTagItem())) 376 .sorted( 377 (i1, i2) -> utils.compareStrings(i1.getLabel(), i2.getLabel())) 378 .map(i -> links.createLink(pathToRoot.resolve(i.getUrl()), 379 contents.getNonBreakString(i.getLabel()))) 380 .toList(); 381 content.add(contents.join(getVerticalSeparator(), pageLinks)); 382 } 383 384 private Content getVerticalSeparator() { 385 return HtmlTree.SPAN(HtmlStyle.verticalSeparator, Text.of("|")); 386 } 387}