001/* 002 * Copyright (c) 1997, 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.ArrayList; 029import java.util.Arrays; 030import java.util.List; 031import java.util.SortedSet; 032import java.util.function.Predicate; 033import java.util.regex.Pattern; 034import java.util.stream.Collectors; 035 036import javax.lang.model.element.Element; 037import javax.lang.model.element.ModuleElement; 038import javax.lang.model.element.PackageElement; 039import javax.lang.model.element.TypeElement; 040 041import org.jdrupes.mdoclet.internal.doclets.formats.html.Navigation.PageMode; 042import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.BodyContents; 043import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder; 044import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Entity; 045import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlStyle; 046import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree; 047import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.TagName; 048import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Text; 049import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 050import org.jdrupes.mdoclet.internal.doclets.toolkit.PackageSummaryWriter; 051import org.jdrupes.mdoclet.internal.doclets.toolkit.util.CommentHelper; 052import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException; 053import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath; 054import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths; 055 056import com.sun.source.doctree.DeprecatedTree; 057import com.sun.source.doctree.DocTree; 058 059/** 060 * Class to generate file for each package contents in the right-hand 061 * frame. This will list all the Class Kinds in the package. A click on any 062 * class-kind will update the frame with the clicked class-kind page. 063 */ 064public class PackageWriterImpl extends HtmlDocletWriter 065 implements PackageSummaryWriter { 066 067 /** 068 * The package being documented. 069 */ 070 protected PackageElement packageElement; 071 072 private List<PackageElement> relatedPackages; 073 private SortedSet<TypeElement> allClasses; 074 075 /** 076 * The HTML element for the section tag being written. 077 */ 078 private final HtmlTree section 079 = HtmlTree.SECTION(HtmlStyle.packageDescription, new ContentBuilder()); 080 081 private final BodyContents bodyContents = new BodyContents(); 082 083 // Maximum number of subpackages and sibling packages to list in related 084 // packages table 085 private static final int MAX_SUBPACKAGES = 20; 086 private static final int MAX_SIBLING_PACKAGES = 5; 087 088 /** 089 * Constructor to construct PackageWriter object and to generate 090 * "package-summary.html" file in the respective package directory. 091 * For example for package "java.lang" this will generate file 092 * "package-summary.html" file in the "java/lang" directory. It will also 093 * create "java/lang" directory in the current or the destination directory 094 * if it doesn't exist. 095 * 096 * @param configuration the configuration of the doclet. 097 * @param packageElement PackageElement under consideration. 098 */ 099 public PackageWriterImpl(HtmlConfiguration configuration, 100 PackageElement packageElement) { 101 super(configuration, 102 configuration.docPaths.forPackage(packageElement) 103 .resolve(DocPaths.PACKAGE_SUMMARY)); 104 this.packageElement = packageElement; 105 computePackageData(); 106 } 107 108 @Override 109 public Content getPackageHeader() { 110 String packageName = getLocalizedPackageName(packageElement).toString(); 111 HtmlTree body = getBody(getWindowTitle(packageName)); 112 var div = HtmlTree.DIV(HtmlStyle.header); 113 if (configuration.showModules) { 114 ModuleElement mdle = configuration.docEnv.getElementUtils() 115 .getModuleOf(packageElement); 116 var classModuleLabel = HtmlTree.SPAN(HtmlStyle.moduleLabelInPackage, 117 contents.moduleLabel); 118 var moduleNameDiv 119 = HtmlTree.DIV(HtmlStyle.subTitle, classModuleLabel); 120 moduleNameDiv.add(Entity.NO_BREAK_SPACE); 121 moduleNameDiv.add(getModuleLink(mdle, 122 Text.of(mdle.getQualifiedName().toString()))); 123 div.add(moduleNameDiv); 124 } 125 Content packageHead = new ContentBuilder(); 126 if (!packageElement.isUnnamed()) { 127 packageHead.add(contents.packageLabel).add(" "); 128 } 129 packageHead.add(packageName); 130 var tHeading = HtmlTree.HEADING_TITLE(Headings.PAGE_TITLE_HEADING, 131 HtmlStyle.title, packageHead); 132 div.add(tHeading); 133 bodyContents.setHeader(getHeader(PageMode.PACKAGE, packageElement)) 134 .addMainContent(div); 135 return body; 136 } 137 138 @Override 139 public Content getContentHeader() { 140 return new ContentBuilder(); 141 } 142 143 private void computePackageData() { 144 relatedPackages = findRelatedPackages(); 145 boolean isSpecified = utils.isSpecified(packageElement); 146 allClasses = filterClasses(isSpecified 147 ? utils.getAllClasses(packageElement) 148 : configuration.typeElementCatalog.allClasses(packageElement)); 149 } 150 151 private SortedSet<TypeElement> filterClasses(SortedSet<TypeElement> types) { 152 List<TypeElement> typeList = types 153 .stream() 154 .filter( 155 te -> utils.isCoreClass(te) && configuration.isGeneratedDoc(te)) 156 .collect(Collectors.toList()); 157 return utils.filterOutPrivateClasses(typeList, options.javafx()); 158 } 159 160 private List<PackageElement> findRelatedPackages() { 161 String pkgName = packageElement.getQualifiedName().toString(); 162 163 // always add superpackage 164 int lastdot = pkgName.lastIndexOf('.'); 165 String pkgPrefix = lastdot > 0 ? pkgName.substring(0, lastdot) : null; 166 List<PackageElement> packages = new ArrayList<>( 167 filterPackages( 168 p -> p.getQualifiedName().toString().equals(pkgPrefix))); 169 boolean hasSuperPackage = !packages.isEmpty(); 170 171 // add subpackages unless there are very many of them 172 Pattern subPattern 173 = Pattern.compile(pkgName.replace(".", "\\.") + "\\.\\w+"); 174 List<PackageElement> subpackages = filterPackages( 175 p -> subPattern.matcher(p.getQualifiedName().toString()).matches()); 176 if (subpackages.size() <= MAX_SUBPACKAGES) { 177 packages.addAll(subpackages); 178 } 179 180 // only add sibling packages if there is a non-empty superpackage, we 181 // are beneath threshold, 182 // and number of siblings is beneath threshold as well 183 if (hasSuperPackage && pkgPrefix != null 184 && packages.size() <= MAX_SIBLING_PACKAGES) { 185 Pattern siblingPattern 186 = Pattern.compile(pkgPrefix.replace(".", "\\.") + "\\.\\w+"); 187 188 List<PackageElement> siblings = filterPackages( 189 p -> siblingPattern.matcher(p.getQualifiedName().toString()) 190 .matches()); 191 if (siblings.size() <= MAX_SIBLING_PACKAGES) { 192 packages.addAll(siblings); 193 } 194 } 195 return packages; 196 } 197 198 @Override 199 protected Navigation getNavBar(PageMode pageMode, Element element) { 200 Content linkContent 201 = getModuleLink(utils.elementUtils.getModuleOf(packageElement), 202 contents.moduleLabel); 203 return super.getNavBar(pageMode, element) 204 .setNavLinkModule(linkContent) 205 .setSubNavLinks(() -> List.of( 206 links.createLink(HtmlIds.PACKAGE_DESCRIPTION, 207 contents.navDescription, 208 !utils.getFullBody(packageElement).isEmpty() 209 && !options.noComment()), 210 links.createLink(HtmlIds.RELATED_PACKAGE_SUMMARY, 211 contents.relatedPackages, 212 relatedPackages != null && !relatedPackages.isEmpty()), 213 links.createLink(HtmlIds.CLASS_SUMMARY, 214 contents.navClassesAndInterfaces, 215 allClasses != null && !allClasses.isEmpty()))); 216 } 217 218 /** 219 * Add the package deprecation information to the documentation tree. 220 * 221 * @param div the content to which the deprecation information will be added 222 */ 223 public void addDeprecationInfo(Content div) { 224 List<? extends DeprecatedTree> deprs 225 = utils.getDeprecatedTrees(packageElement); 226 if (utils.isDeprecated(packageElement)) { 227 CommentHelper ch = utils.getCommentHelper(packageElement); 228 var deprDiv = HtmlTree.DIV(HtmlStyle.deprecationBlock); 229 var deprPhrase = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, 230 getDeprecatedPhrase(packageElement)); 231 deprDiv.add(deprPhrase); 232 if (!deprs.isEmpty()) { 233 List<? extends DocTree> commentTags 234 = ch.getDescription(deprs.get(0)); 235 if (!commentTags.isEmpty()) { 236 addInlineDeprecatedComment(packageElement, deprs.get(0), 237 deprDiv); 238 } 239 } 240 div.add(deprDiv); 241 } 242 } 243 244 @Override 245 public Content getSummariesList() { 246 return HtmlTree.UL(HtmlStyle.summaryList); 247 } 248 249 @Override 250 public void addRelatedPackagesSummary(Content summaryContent) { 251 boolean showModules = configuration.showModules 252 && hasRelatedPackagesInOtherModules(relatedPackages); 253 TableHeader tableHeader = showModules 254 ? new TableHeader(contents.moduleLabel, contents.packageLabel, 255 contents.descriptionLabel) 256 : new TableHeader(contents.packageLabel, contents.descriptionLabel); 257 addPackageSummary(relatedPackages, contents.relatedPackages, 258 tableHeader, 259 summaryContent, showModules); 260 } 261 262 /** 263 * Add all types to the content. 264 * 265 * @param target the content to which the links will be added 266 */ 267 public void addAllClassesAndInterfacesSummary(Content target) { 268 var table = new Table<TypeElement>(HtmlStyle.summaryTable) 269 .setHeader( 270 new TableHeader(contents.classLabel, contents.descriptionLabel)) 271 .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colLast) 272 .setId(HtmlIds.CLASS_SUMMARY) 273 .setDefaultTab(contents.allClassesAndInterfacesLabel) 274 .addTab(contents.interfaces, utils::isPlainInterface) 275 .addTab(contents.classes, e -> utils.isNonThrowableClass(e)) 276 .addTab(contents.enums, utils::isEnum) 277 .addTab(contents.records, e -> utils.isRecord(e)) 278 .addTab(contents.exceptionClasses, e -> utils.isThrowable(e)) 279 .addTab(contents.annotationTypes, utils::isAnnotationInterface); 280 for (TypeElement typeElement : allClasses) { 281 if (typeElement != null && utils.isCoreClass(typeElement)) { 282 Content classLink = getLink(new HtmlLinkInfo( 283 configuration, 284 HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS_AND_BOUNDS, 285 typeElement)); 286 ContentBuilder description = new ContentBuilder(); 287 addPreviewSummary(typeElement, description); 288 if (utils.isDeprecated(typeElement)) { 289 description.add(getDeprecatedPhrase(typeElement)); 290 List<? extends DeprecatedTree> tags 291 = utils.getDeprecatedTrees(typeElement); 292 if (!tags.isEmpty()) { 293 addSummaryDeprecatedComment(typeElement, tags.get(0), 294 description); 295 } 296 } else { 297 addSummaryComment(typeElement, description); 298 } 299 table.addRow(typeElement, 300 Arrays.asList(classLink, description)); 301 } 302 } 303 if (!table.isEmpty()) { 304 target.add(HtmlTree.LI(table)); 305 } 306 } 307 308 public void addPackageSummary(List<PackageElement> packages, Content label, 309 TableHeader tableHeader, Content summaryContent, 310 boolean showModules) { 311 if (!packages.isEmpty()) { 312 var table = new Table<Void>(HtmlStyle.summaryTable) 313 .setId(HtmlIds.RELATED_PACKAGE_SUMMARY) 314 .setCaption(label) 315 .setHeader(tableHeader); 316 if (showModules) { 317 table.setColumnStyles(HtmlStyle.colPlain, HtmlStyle.colFirst, 318 HtmlStyle.colLast); 319 } else { 320 table.setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colLast); 321 } 322 323 for (PackageElement pkg : packages) { 324 Content packageLink 325 = getPackageLink(pkg, Text.of(pkg.getQualifiedName())); 326 Content moduleLink = Text.EMPTY; 327 if (showModules) { 328 ModuleElement module 329 = (ModuleElement) pkg.getEnclosingElement(); 330 if (module != null && !module.isUnnamed()) { 331 moduleLink = getModuleLink(module, 332 Text.of(module.getQualifiedName())); 333 } 334 } 335 ContentBuilder description = new ContentBuilder(); 336 addPreviewSummary(pkg, description); 337 if (utils.isDeprecated(pkg)) { 338 description.add(getDeprecatedPhrase(pkg)); 339 List<? extends DeprecatedTree> tags 340 = utils.getDeprecatedTrees(pkg); 341 if (!tags.isEmpty()) { 342 addSummaryDeprecatedComment(pkg, tags.get(0), 343 description); 344 } 345 } else { 346 addSummaryComment(pkg, description); 347 } 348 if (showModules) { 349 table.addRow(moduleLink, packageLink, description); 350 } else { 351 table.addRow(packageLink, description); 352 } 353 } 354 summaryContent.add(HtmlTree.LI(table)); 355 } 356 } 357 358 @Override 359 public void addPackageDescription(Content packageContent) { 360 addPreviewInfo(packageElement, packageContent); 361 if (!utils.getBody(packageElement).isEmpty()) { 362 section.setId(HtmlIds.PACKAGE_DESCRIPTION); 363 addDeprecationInfo(section); 364 addInlineComment(packageElement, section); 365 } 366 } 367 368 @Override 369 public void addPackageTags(Content packageContent) { 370 addTagsInfo(packageElement, section); 371 packageContent.add(section); 372 } 373 374 @Override 375 public void addPackageSignature(Content packageContent) { 376 packageContent.add(new HtmlTree(TagName.HR)); 377 packageContent 378 .add(Signatures.getPackageSignature(packageElement, this)); 379 } 380 381 @Override 382 public void addPackageContent(Content packageContent) { 383 bodyContents.addMainContent(packageContent); 384 } 385 386 @Override 387 public void addPackageFooter() { 388 bodyContents.setFooter(getFooter()); 389 } 390 391 @Override 392 public void printDocument(Content content) throws DocFileIOException { 393 String description = getDescription("declaration", packageElement); 394 List<DocPath> localStylesheets = getLocalStylesheets(packageElement); 395 content.add(bodyContents); 396 printHtmlDocument( 397 configuration.metakeywords.getMetaKeywords(packageElement), 398 description, localStylesheets, content); 399 } 400 401 @Override 402 public Content getPackageSummary(Content summaryContent) { 403 return HtmlTree.SECTION(HtmlStyle.summary, summaryContent); 404 } 405 406 private boolean hasRelatedPackagesInOtherModules( 407 List<PackageElement> relatedPackages) { 408 final ModuleElement module 409 = (ModuleElement) packageElement.getEnclosingElement(); 410 return relatedPackages.stream() 411 .anyMatch(pkg -> module != pkg.getEnclosingElement()); 412 } 413 414 private List<PackageElement> 415 filterPackages(Predicate<? super PackageElement> filter) { 416 return configuration.packages.stream() 417 .filter(p -> p != packageElement && filter.test(p)) 418 .collect(Collectors.toList()); 419 } 420}