package jdk.javadoc.internal.doclets.formats.html;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleElementVisitor8;
import com.sun.source.doctree.DocTree;
import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
import jdk.javadoc.internal.doclets.formats.html.markup.Entity;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
import jdk.javadoc.internal.doclets.formats.html.markup.TagName;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
import jdk.javadoc.internal.doclets.formats.html.Navigation.PageMode;
import jdk.javadoc.internal.doclets.formats.html.markup.StringContent;
import jdk.javadoc.internal.doclets.toolkit.ClassWriter;
import jdk.javadoc.internal.doclets.toolkit.Content;
import jdk.javadoc.internal.doclets.toolkit.taglets.ParamTaglet;
import jdk.javadoc.internal.doclets.toolkit.util.ClassTree;
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants;
public class ClassWriterImpl extends SubWriterHolderWriter implements ClassWriter {
private static final Set<String> suppressSubtypesSet
= Set.of("java.lang.Object",
"org.omg.CORBA.Object");
private static final Set<String> suppressImplementingSet
= Set.of("java.lang.Cloneable",
"java.lang.constant.Constable",
"java.lang.constant.ConstantDesc",
"java.io.Serializable");
protected final TypeElement typeElement;
protected final ClassTree classtree;
private final Navigation navBar;
public ClassWriterImpl(HtmlConfiguration configuration, TypeElement typeElement,
ClassTree classTree) {
super(configuration, configuration.docPaths.forClass(typeElement));
this.typeElement = typeElement;
configuration.currentTypeElement = typeElement;
this.classtree = classTree;
this.navBar = new Navigation(typeElement, configuration, PageMode.CLASS, path);
}
@Override
public Content (String header) {
HtmlTree bodyTree = getBody(getWindowTitle(utils.getSimpleName(typeElement)));
Content headerContent = new ContentBuilder();
addTop(headerContent);
Content linkContent = getModuleLink(utils.elementUtils.getModuleOf(typeElement),
contents.moduleLabel);
navBar.setNavLinkModule(linkContent);
navBar.setMemberSummaryBuilder(configuration.getBuilderFactory().getMemberSummaryBuilder(this));
navBar.setUserHeader(getUserHeaderFooter(true));
headerContent.add(navBar.getContent(Navigation.Position.TOP));
HtmlTree div = new HtmlTree(TagName.DIV);
div.setStyle(HtmlStyle.header);
if (configuration.showModules) {
ModuleElement mdle = configuration.docEnv.getElementUtils().getModuleOf(typeElement);
Content classModuleLabel = HtmlTree.SPAN(HtmlStyle.moduleLabelInType, contents.moduleLabel);
Content moduleNameDiv = HtmlTree.DIV(HtmlStyle.subTitle, classModuleLabel);
moduleNameDiv.add(Entity.NO_BREAK_SPACE);
moduleNameDiv.add(getModuleLink(mdle,
new StringContent(mdle.getQualifiedName())));
div.add(moduleNameDiv);
}
PackageElement pkg = utils.containingPackage(typeElement);
if (!pkg.isUnnamed()) {
Content classPackageLabel = HtmlTree.SPAN(HtmlStyle.packageLabelInType, contents.packageLabel);
Content pkgNameDiv = HtmlTree.DIV(HtmlStyle.subTitle, classPackageLabel);
pkgNameDiv.add(Entity.NO_BREAK_SPACE);
Content pkgNameContent = getPackageLink(pkg,
new StringContent(utils.getPackageName(pkg)));
pkgNameDiv.add(pkgNameContent);
div.add(pkgNameDiv);
}
LinkInfoImpl linkInfo = new LinkInfoImpl(configuration,
LinkInfoImpl.Kind.CLASS_HEADER, typeElement);
linkInfo.linkToSelf = false;
Content heading = HtmlTree.HEADING_TITLE(Headings.PAGE_TITLE_HEADING,
HtmlStyle.title, new StringContent(header));
heading.add(getTypeParameterLinks(linkInfo));
div.add(heading);
bodyContents.setHeader(headerContent)
.addMainContent(MarkerComments.START_OF_CLASS_DATA)
.addMainContent(div);
return bodyTree;
}
@Override
public Content () {
return getContentHeader();
}
@Override
public void () {
bodyContents.addMainContent(MarkerComments.END_OF_CLASS_DATA);
Content htmlTree = HtmlTree.FOOTER();
navBar.setUserFooter(getUserHeaderFooter(false));
htmlTree.add(navBar.getContent(Navigation.Position.BOTTOM));
addBottom(htmlTree);
bodyContents.setFooter(htmlTree);
}
@Override
public void printDocument(Content contentTree) throws DocFileIOException {
String description = getDescription("declaration", typeElement);
PackageElement pkg = utils.containingPackage(typeElement);
List<DocPath> localStylesheets = getLocalStylesheets(pkg);
contentTree.add(bodyContents);
printHtmlDocument(configuration.metakeywords.getMetaKeywords(typeElement),
description, localStylesheets, contentTree);
}
@Override
public Content () {
return getMemberTreeHeader();
}
@Override
public Content getClassInfo(Content classInfoTree) {
return getMemberTree(HtmlStyle.description, classInfoTree);
}
@Override
protected TypeElement getCurrentPageElement() {
return typeElement;
}
@Override @SuppressWarnings("preview")
public void addClassSignature(String modifiers, Content classInfoTree) {
Content hr = new HtmlTree(TagName.HR);
classInfoTree.add(hr);
Content pre = new HtmlTree(TagName.PRE);
addAnnotationInfo(typeElement, pre);
pre.add(modifiers);
LinkInfoImpl linkInfo = new LinkInfoImpl(configuration,
LinkInfoImpl.Kind.CLASS_SIGNATURE, typeElement);
linkInfo.linkToSelf = false;
Content className = new StringContent(utils.getSimpleName(typeElement));
Content parameterLinks = getTypeParameterLinks(linkInfo);
if (options.linkSource()) {
addSrcLink(typeElement, className, pre);
pre.add(parameterLinks);
} else {
Content span = HtmlTree.SPAN(HtmlStyle.typeNameLabel, className);
span.add(parameterLinks);
pre.add(span);
}
if (utils.isRecord(typeElement)) {
pre.add(getRecordComponents(typeElement));
}
if (!utils.isAnnotationType(typeElement)) {
if (!utils.isInterface(typeElement)) {
TypeMirror superclass = utils.getFirstVisibleSuperClass(typeElement);
if (superclass != null) {
pre.add(DocletConstants.NL);
pre.add("extends ");
Content link = getLink(new LinkInfoImpl(configuration,
LinkInfoImpl.Kind.CLASS_SIGNATURE_PARENT_NAME,
superclass));
pre.add(link);
}
}
List<? extends TypeMirror> interfaces = typeElement.getInterfaces();
if (!interfaces.isEmpty()) {
boolean isFirst = true;
for (TypeMirror type : interfaces) {
TypeElement tDoc = utils.asTypeElement(type);
if (!(utils.isPublic(tDoc) || utils.isLinkable(tDoc))) {
continue;
}
if (isFirst) {
pre.add(DocletConstants.NL);
pre.add(utils.isInterface(typeElement) ? "extends " : "implements ");
isFirst = false;
} else {
pre.add(", ");
}
Content link = getLink(new LinkInfoImpl(configuration,
LinkInfoImpl.Kind.CLASS_SIGNATURE_PARENT_NAME,
type));
pre.add(link);
}
}
}
List<? extends TypeMirror> permits = typeElement.getPermittedSubclasses();
List<? extends TypeMirror> linkablePermits = permits.stream()
.filter(t -> utils.isLinkable(utils.asTypeElement(t)))
.collect(Collectors.toList());
if (!linkablePermits.isEmpty()) {
boolean isFirst = true;
for (TypeMirror type : linkablePermits) {
TypeElement tDoc = utils.asTypeElement(type);
if (isFirst) {
pre.add(DocletConstants.NL);
pre.add("permits ");
isFirst = false;
} else {
pre.add(", ");
}
Content link = getLink(new LinkInfoImpl(configuration,
LinkInfoImpl.Kind.PERMITTED_SUBCLASSES,
type));
pre.add(link);
}
if (linkablePermits.size() < permits.size()) {
Content c = new StringContent(resources.getText("doclet.not.exhaustive"));
pre.add(" ");
pre.add(HtmlTree.SPAN(HtmlStyle.permitsNote, c));
}
}
classInfoTree.add(pre);
}
@SuppressWarnings("preview")
private Content getRecordComponents(TypeElement typeElem) {
Content content = new ContentBuilder();
content.add("(");
String sep = "";
for (RecordComponentElement e : typeElement.getRecordComponents()) {
content.add(sep);
getAnnotations(e.getAnnotationMirrors(), false)
.forEach(a -> { content.add(a); content.add(" "); });
Content link = getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.RECORD_COMPONENT,
e.asType()));
content.add(link);
content.add(Entity.NO_BREAK_SPACE);
content.add(e.getSimpleName());
sep = ", ";
}
content.add(")");
return content;
}
@Override
public void addClassDescription(Content classInfoTree) {
if (!options.noComment()) {
if (!utils.getFullBody(typeElement).isEmpty()) {
addInlineComment(typeElement, classInfoTree);
}
}
}
@Override
public void addClassTagInfo(Content classInfoTree) {
if (!options.noComment()) {
addTagsInfo(typeElement, classInfoTree);
}
}
private Content getClassInheritanceTree(TypeMirror type) {
TypeMirror sup;
HtmlTree classTree = null;
do {
sup = utils.getFirstVisibleSuperClass(type);
HtmlTree htmlElement = HtmlTree.DIV(HtmlStyle.inheritance, getTreeForClassHelper(type));
if (classTree != null)
htmlElement.add(classTree);
classTree = htmlElement;
type = sup;
} while (sup != null);
classTree.put(HtmlAttr.TITLE, contents.getContent("doclet.Inheritance_Tree").toString());
return classTree;
}
private Content getTreeForClassHelper(TypeMirror type) {
Content content = new ContentBuilder();
if (type.equals(typeElement.asType())) {
Content typeParameters = getTypeParameterLinks(
new LinkInfoImpl(configuration, LinkInfoImpl.Kind.TREE,
typeElement));
if (configuration.shouldExcludeQualifier(utils.containingPackage(typeElement).toString())) {
content.add(utils.asTypeElement(type).getSimpleName());
content.add(typeParameters);
} else {
content.add(utils.asTypeElement(type).getQualifiedName());
content.add(typeParameters);
}
} else {
Content link = getLink(new LinkInfoImpl(configuration,
LinkInfoImpl.Kind.CLASS_TREE_PARENT, type)
.label(configuration.getClassName(utils.asTypeElement(type))));
content.add(link);
}
return content;
}
@Override
public void addClassTree(Content classContentTree) {
if (!utils.isClass(typeElement)) {
return;
}
classContentTree.add(getClassInheritanceTree(typeElement.asType()));
}
@Override
public void addParamInfo(Content classInfoTree) {
if (utils.hasBlockTag(typeElement, DocTree.Kind.PARAM)) {
Content paramInfo = (new ParamTaglet()).getTagletOutput(typeElement,
getTagletWriterInstance(false));
if (!paramInfo.isEmpty()) {
classInfoTree.add(HtmlTree.DL(HtmlStyle.notes, paramInfo));
}
}
}
@Override
public void addSubClassInfo(Content classInfoTree) {
if (utils.isClass(typeElement)) {
for (String s : suppressSubtypesSet) {
if (typeElement.getQualifiedName().contentEquals(s)) {
return;
}
}
Set<TypeElement> subclasses = classtree.directSubClasses(typeElement, false);
if (!subclasses.isEmpty()) {
HtmlTree dl = HtmlTree.DL(HtmlStyle.notes);
dl.add(HtmlTree.DT(contents.subclassesLabel));
dl.add(HtmlTree.DD(getClassLinks(LinkInfoImpl.Kind.SUBCLASSES, subclasses)));
classInfoTree.add(dl);
}
}
}
@Override
public void addSubInterfacesInfo(Content classInfoTree) {
if (utils.isInterface(typeElement)) {
Set<TypeElement> subInterfaces = classtree.allSubClasses(typeElement, false);
if (!subInterfaces.isEmpty()) {
Content dl = HtmlTree.DL(HtmlStyle.notes);
dl.add(HtmlTree.DT(contents.subinterfacesLabel));
dl.add(HtmlTree.DD(getClassLinks(LinkInfoImpl.Kind.SUBINTERFACES, subInterfaces)));
classInfoTree.add(dl);
}
}
}
@Override
public void addInterfaceUsageInfo (Content classInfoTree) {
if (!utils.isInterface(typeElement)) {
return;
}
for (String s : suppressImplementingSet) {
if (typeElement.getQualifiedName().contentEquals(s)) {
return;
}
}
Set<TypeElement> implcl = classtree.implementingClasses(typeElement);
if (!implcl.isEmpty()) {
HtmlTree dl = HtmlTree.DL(HtmlStyle.notes);
dl.add(HtmlTree.DT(contents.implementingClassesLabel));
dl.add(HtmlTree.DD(getClassLinks(LinkInfoImpl.Kind.IMPLEMENTED_CLASSES, implcl)));
classInfoTree.add(dl);
}
}
@Override
public void addImplementedInterfacesInfo(Content classInfoTree) {
SortedSet<TypeMirror> interfaces = new TreeSet<>(comparators.makeTypeMirrorClassUseComparator());
interfaces.addAll(utils.getAllInterfaces(typeElement));
if (utils.isClass(typeElement) && !interfaces.isEmpty()) {
HtmlTree dl = HtmlTree.DL(HtmlStyle.notes);
dl.add(HtmlTree.DT(contents.allImplementedInterfacesLabel));
dl.add(HtmlTree.DD(getClassLinks(LinkInfoImpl.Kind.IMPLEMENTED_INTERFACES, interfaces)));
classInfoTree.add(dl);
}
}
@Override
public void addSuperInterfacesInfo(Content classInfoTree) {
SortedSet<TypeMirror> interfaces =
new TreeSet<>(comparators.makeTypeMirrorIndexUseComparator());
interfaces.addAll(utils.getAllInterfaces(typeElement));
if (utils.isInterface(typeElement) && !interfaces.isEmpty()) {
HtmlTree dl = HtmlTree.DL(HtmlStyle.notes);
dl.add(HtmlTree.DT(contents.allSuperinterfacesLabel));
dl.add(HtmlTree.DD(getClassLinks(LinkInfoImpl.Kind.SUPER_INTERFACES, interfaces)));
classInfoTree.add(dl);
}
}
@Override
public void addNestedClassInfo(final Content classInfoTree) {
Element outerClass = typeElement.getEnclosingElement();
if (outerClass == null)
return;
new SimpleElementVisitor8<Void, Void>() {
@Override
public Void visitType(TypeElement e, Void p) {
HtmlTree dl = HtmlTree.DL(HtmlStyle.notes);
dl.add(HtmlTree.DT(utils.isInterface(e)
? contents.enclosingInterfaceLabel
: contents.enclosingClassLabel));
Content dd = new HtmlTree(TagName.DD);
dd.add(getLink(new LinkInfoImpl(configuration,
LinkInfoImpl.Kind.CLASS, e)));
dl.add(dd);
classInfoTree.add(dl);
return null;
}
}.visit(outerClass);
}
@Override
public void addFunctionalInterfaceInfo (Content classInfoTree) {
if (isFunctionalInterface()) {
HtmlTree dl = HtmlTree.DL(HtmlStyle.notes);
dl.add(HtmlTree.DT(contents.functionalInterface));
Content dd = new HtmlTree(TagName.DD);
dd.add(contents.functionalInterfaceMessage);
dl.add(dd);
classInfoTree.add(dl);
}
}
public boolean isFunctionalInterface() {
List<? extends AnnotationMirror> annotationMirrors = ((Element) typeElement).getAnnotationMirrors();
for (AnnotationMirror anno : annotationMirrors) {
if (utils.isFunctionalInterface(anno)) {
return true;
}
}
return false;
}
@Override
public void addClassDeprecationInfo(Content classInfoTree) {
List<? extends DocTree> deprs = utils.getBlockTags(typeElement, DocTree.Kind.DEPRECATED);
if (utils.isDeprecated(typeElement)) {
Content deprLabel = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, getDeprecatedPhrase(typeElement));
Content div = HtmlTree.DIV(HtmlStyle.deprecationBlock, deprLabel);
if (!deprs.isEmpty()) {
CommentHelper ch = utils.getCommentHelper(typeElement);
DocTree dt = deprs.get(0);
List<? extends DocTree> commentTags = ch.getBody(dt);
if (!commentTags.isEmpty()) {
addInlineDeprecatedComment(typeElement, deprs.get(0), div);
}
}
classInfoTree.add(div);
}
}
private Content getClassLinks(LinkInfoImpl.Kind context, Collection<?> list) {
Content content = new ContentBuilder();
boolean isFirst = true;
for (Object type : list) {
if (!isFirst) {
Content separator = new StringContent(", ");
content.add(separator);
} else {
isFirst = false;
}
if (type instanceof TypeElement) {
Content link = getLink(
new LinkInfoImpl(configuration, context, (TypeElement)(type)));
content.add(HtmlTree.CODE(link));
} else {
Content link = getLink(
new LinkInfoImpl(configuration, context, ((TypeMirror)type)));
content.add(HtmlTree.CODE(link));
}
}
return content;
}
@Override
public TypeElement getTypeElement() {
return typeElement;
}
public Content getMemberDetailsTree(Content contentTree) {
HtmlTree section = HtmlTree.SECTION(HtmlStyle.details, contentTree);
if (utils.isAnnotationType(typeElement)) {
section.setId(SectionName.ANNOTATION_TYPE_ELEMENT_DETAIL.getName());
}
return section;
}
}