001/* 002 * Copyright (c) 2003, 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.EnumSet; 030import java.util.List; 031import java.util.Set; 032 033import javax.lang.model.element.AnnotationMirror; 034import javax.lang.model.element.Element; 035import javax.lang.model.element.TypeElement; 036import javax.lang.model.element.TypeParameterElement; 037import javax.lang.model.type.ArrayType; 038import javax.lang.model.type.DeclaredType; 039import javax.lang.model.type.TypeMirror; 040import javax.lang.model.type.TypeVariable; 041import javax.lang.model.type.WildcardType; 042import javax.lang.model.util.SimpleTypeVisitor14; 043 044import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder; 045import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree; 046import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.TagName; 047import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Text; 048import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseConfiguration; 049import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 050import org.jdrupes.mdoclet.internal.doclets.toolkit.Resources; 051import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath; 052import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths; 053import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 054import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils.ElementFlag; 055 056/** 057 * A factory that returns a link given the information about it. 058 */ 059public class HtmlLinkFactory { 060 061 private final HtmlDocletWriter m_writer; 062 private final DocPaths docPaths; 063 private final Utils utils; 064 065 /** 066 * Constructs a new link factory. 067 * 068 * @param writer the HTML doclet writer 069 */ 070 public HtmlLinkFactory(HtmlDocletWriter writer) { 071 m_writer = writer; 072 docPaths = writer.configuration.docPaths; 073 utils = writer.configuration.utils; 074 } 075 076 /** 077 * {@return a new instance of a content object} 078 */ 079 protected Content newContent() { 080 return new ContentBuilder(); 081 } 082 083 /** 084 * Constructs a link from the given link information. 085 * 086 * @param linkInfo the information about the link. 087 * @return the link. 088 */ 089 public Content getLink(HtmlLinkInfo linkInfo) { 090 if (linkInfo.getType() != null) { 091 SimpleTypeVisitor14<Content, HtmlLinkInfo> linkVisitor 092 = new SimpleTypeVisitor14<>() { 093 094 final Content link = newContent(); 095 096 // handles primitives, no types and error types 097 @Override 098 protected Content defaultAction(TypeMirror type, 099 HtmlLinkInfo linkInfo) { 100 link.add(utils.getTypeName(type, false)); 101 return link; 102 } 103 104 int currentDepth = 0; 105 106 @Override 107 public Content visitArray(ArrayType type, 108 HtmlLinkInfo linkInfo) { 109 // keep track of the dimension depth and replace the 110 // last dimension 111 // specifier with varargs, when the stack is fully 112 // unwound. 113 currentDepth++; 114 var componentType = type.getComponentType(); 115 visit(componentType, linkInfo.forType(componentType)); 116 currentDepth--; 117 if (utils.isAnnotated(type)) { 118 link.add(" "); 119 link.add(getTypeAnnotationLinks(linkInfo)); 120 } 121 // use vararg if required 122 if (linkInfo.isVarArg() && currentDepth == 0) { 123 link.add("..."); 124 } else { 125 link.add("[]"); 126 } 127 return link; 128 } 129 130 @Override 131 public Content visitWildcard(WildcardType type, 132 HtmlLinkInfo linkInfo) { 133 link.add(getTypeAnnotationLinks(linkInfo)); 134 link.add("?"); 135 TypeMirror extendsBound = type.getExtendsBound(); 136 if (extendsBound != null) { 137 link.add(" extends "); 138 link.add(getLink( 139 getBoundsLinkInfo(linkInfo, extendsBound))); 140 } 141 TypeMirror superBound = type.getSuperBound(); 142 if (superBound != null) { 143 link.add(" super "); 144 link.add(getLink( 145 getBoundsLinkInfo(linkInfo, superBound))); 146 } 147 return link; 148 } 149 150 @Override 151 public Content visitTypeVariable(TypeVariable type, 152 HtmlLinkInfo linkInfo) { 153 link.add(getTypeAnnotationLinks(linkInfo)); 154 TypeVariable typevariable = (utils.isArrayType(type)) 155 ? (TypeVariable) utils.getComponentType(type) 156 : type; 157 Element owner 158 = typevariable.asElement().getEnclosingElement(); 159 if (linkInfo.linkTypeParameters() 160 && utils.isTypeElement(owner)) { 161 linkInfo.setTypeElement((TypeElement) owner); 162 Content label = newContent(); 163 label.add(utils.getTypeName(type, false)); 164 linkInfo.label(label).skipPreview(true); 165 link.add(getClassLink(linkInfo)); 166 } else { 167 // No need to link method type parameters. 168 link.add(utils.getTypeName(typevariable, false)); 169 } 170 171 if (linkInfo.showTypeBounds()) { 172 linkInfo.showTypeBounds(false); 173 TypeParameterElement tpe 174 = ((TypeParameterElement) typevariable 175 .asElement()); 176 boolean more = false; 177 List<? extends TypeMirror> bounds 178 = utils.getBounds(tpe); 179 for (TypeMirror bound : bounds) { 180 // we get everything as extends java.lang.Object 181 // we suppress 182 // all of them except those that have multiple 183 // extends 184 if (bounds.size() == 1 && 185 utils.typeUtils.isSameType(bound, 186 utils.getObjectType()) 187 && 188 !utils.isAnnotated(bound)) { 189 continue; 190 } 191 link.add(more ? " & " : " extends "); 192 link.add(getLink( 193 getBoundsLinkInfo(linkInfo, bound))); 194 more = true; 195 } 196 } 197 return link; 198 } 199 200 @Override 201 public Content visitDeclared(DeclaredType type, 202 HtmlLinkInfo linkInfo) { 203 TypeMirror enc = type.getEnclosingType(); 204 if (enc instanceof DeclaredType dt 205 && utils.isGenericType(dt)) { 206 // If an enclosing type has type parameters render 207 // them as separate links as 208 // otherwise this information is lost. On the other 209 // hand, plain enclosing types 210 // are not linked separately as they are easy to 211 // reach from the nested type. 212 visitDeclared(dt, linkInfo.forType(dt)); 213 link.add("."); 214 } 215 link.add(getTypeAnnotationLinks(linkInfo)); 216 linkInfo.setTypeElement(utils.asTypeElement(type)); 217 link.add(getClassLink(linkInfo)); 218 if (linkInfo.showTypeParameters()) { 219 link.add(getTypeParameterLinks(linkInfo)); 220 } 221 return link; 222 } 223 }; 224 return linkVisitor.visit(linkInfo.getType(), linkInfo); 225 } else if (linkInfo.getTypeElement() != null) { 226 Content link = newContent(); 227 link.add(getClassLink(linkInfo)); 228 if (linkInfo.showTypeParameters()) { 229 link.add(getTypeParameterLinks(linkInfo)); 230 } 231 return link; 232 } else { 233 return null; 234 } 235 } 236 237 /** 238 * Returns a link to the given class. 239 * 240 * @param linkInfo the information about the link to construct 241 * @return the link for the given class. 242 */ 243 protected Content getClassLink(HtmlLinkInfo linkInfo) { 244 BaseConfiguration configuration = m_writer.configuration; 245 TypeElement typeElement = linkInfo.getTypeElement(); 246 // Create a tool tip if we are linking to a class or interface. Don't 247 // create one if we are linking to a member. 248 String title = ""; 249 String fragment = linkInfo.getFragment(); 250 boolean hasFragment = fragment != null && !fragment.isEmpty(); 251 if (!hasFragment) { 252 boolean isTypeLink = linkInfo.getType() != null && 253 utils 254 .isTypeVariable(utils.getComponentType(linkInfo.getType())); 255 title = getClassToolTip(typeElement, isTypeLink); 256 } 257 Content label = linkInfo.getClassLinkLabel(configuration); 258 if (linkInfo 259 .getContext() == HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS_IN_LABEL) { 260 // For this kind of link, type parameters are included in the link 261 // label 262 // (and obviously not added after the link). 263 label.add(getTypeParameterLinks(linkInfo)); 264 } 265 Set<ElementFlag> flags; 266 Element previewTarget; 267 boolean showPreview = !linkInfo.isSkipPreview(); 268 if (!hasFragment && showPreview) { 269 flags = utils.elementFlags(typeElement); 270 previewTarget = typeElement; 271 } else if (linkInfo.getContext() == HtmlLinkInfo.Kind.SHOW_PREVIEW 272 && linkInfo.getTargetMember() != null && showPreview) { 273 flags = utils.elementFlags(linkInfo.getTargetMember()); 274 TypeElement enclosing 275 = utils.getEnclosingTypeElement(linkInfo.getTargetMember()); 276 Set<ElementFlag> enclosingFlags = utils.elementFlags(enclosing); 277 if (flags.contains(ElementFlag.PREVIEW) 278 && enclosingFlags.contains(ElementFlag.PREVIEW)) { 279 if (enclosing.equals(m_writer.getCurrentPageElement())) { 280 // skip the PREVIEW tag: 281 flags = EnumSet.copyOf(flags); 282 flags.remove(ElementFlag.PREVIEW); 283 } 284 previewTarget = enclosing; 285 } else { 286 previewTarget = linkInfo.getTargetMember(); 287 } 288 } else { 289 flags = EnumSet.noneOf(ElementFlag.class); 290 previewTarget = null; 291 } 292 293 Content link = new ContentBuilder(); 294 if (utils.isIncluded(typeElement)) { 295 if (configuration.isGeneratedDoc(typeElement) 296 && !utils.hasHiddenTag(typeElement)) { 297 DocPath filename = getPath(linkInfo); 298 if (linkInfo.linkToSelf() 299 || typeElement != m_writer.getCurrentPageElement()) { 300 link.add(m_writer.links.createLink( 301 filename.fragment(linkInfo.getFragment()), 302 label, 303 linkInfo.getStyle(), 304 title)); 305 if (flags.contains(ElementFlag.PREVIEW)) { 306 link.add(HtmlTree.SUP(m_writer.links.createLink( 307 filename.fragment(m_writer.htmlIds 308 .forPreviewSection(previewTarget).name()), 309 m_writer.contents.previewMark))); 310 } 311 return link; 312 } 313 } 314 } else { 315 Content crossLink = m_writer.getCrossClassLink( 316 typeElement, linkInfo.getFragment(), 317 label, linkInfo.getStyle(), true); 318 if (crossLink != null) { 319 link.add(crossLink); 320 if (flags.contains(ElementFlag.PREVIEW)) { 321 link.add(HtmlTree.SUP(m_writer.getCrossClassLink( 322 typeElement, 323 m_writer.htmlIds.forPreviewSection(previewTarget) 324 .name(), 325 m_writer.contents.previewMark, 326 null, false))); 327 } 328 return link; 329 } 330 } 331 // Can't link so just write label. 332 link.add(label); 333 if (flags.contains(ElementFlag.PREVIEW)) { 334 link.add(HtmlTree.SUP(m_writer.contents.previewMark)); 335 } 336 return link; 337 } 338 339 /** 340 * Returns links to the type parameters. 341 * 342 * @param linkInfo the information about the link to construct 343 * @return the links to the type parameters 344 */ 345 protected Content getTypeParameterLinks(HtmlLinkInfo linkInfo) { 346 Content links = newContent(); 347 List<TypeMirror> vars = new ArrayList<>(); 348 TypeMirror ctype = linkInfo.getType() != null 349 ? utils.getComponentType(linkInfo.getType()) 350 : null; 351 if (linkInfo.getExecutableElement() != null) { 352 linkInfo.getExecutableElement().getTypeParameters() 353 .forEach(t -> vars.add(t.asType())); 354 } else if (linkInfo.getType() != null 355 && utils.isDeclaredType(linkInfo.getType())) { 356 vars.addAll(((DeclaredType) linkInfo.getType()).getTypeArguments()); 357 } else if (ctype != null && utils.isDeclaredType(ctype)) { 358 vars.addAll(((DeclaredType) ctype).getTypeArguments()); 359 } else if (ctype == null && linkInfo.getTypeElement() != null) { 360 linkInfo.getTypeElement().getTypeParameters() 361 .forEach(t -> vars.add(t.asType())); 362 } else { 363 // Nothing to document. 364 return links; 365 } 366 if (!vars.isEmpty()) { 367 if (linkInfo.addLineBreakOpportunitiesInTypeParameters()) { 368 links.add(new HtmlTree(TagName.WBR)); 369 } 370 links.add("<"); 371 boolean many = false; 372 for (TypeMirror t : vars) { 373 if (many) { 374 links.add(","); 375 links.add(new HtmlTree(TagName.WBR)); 376 if (linkInfo.addLineBreaksInTypeParameters()) { 377 links.add(Text.NL); 378 } 379 } 380 links.add(getLink(linkInfo.forType(t))); 381 many = true; 382 } 383 links.add(">"); 384 } 385 return links; 386 } 387 388 /** 389 * Returns links to the type annotations. 390 * 391 * @param linkInfo the information about the link to construct 392 * @return the links to the type annotations 393 */ 394 public Content getTypeAnnotationLinks(HtmlLinkInfo linkInfo) { 395 ContentBuilder links = new ContentBuilder(); 396 List<? extends AnnotationMirror> annotations; 397 if (utils.isAnnotated(linkInfo.getType())) { 398 annotations = linkInfo.getType().getAnnotationMirrors(); 399 } else if (utils.isTypeVariable(linkInfo.getType()) 400 && linkInfo.showTypeParameterAnnotations()) { 401 Element element = utils.typeUtils.asElement(linkInfo.getType()); 402 annotations = element.getAnnotationMirrors(); 403 } else { 404 return links; 405 } 406 407 if (annotations.isEmpty()) 408 return links; 409 410 m_writer.getAnnotations(annotations, false) 411 .forEach(a -> { 412 links.add(a); 413 links.add(" "); 414 }); 415 416 return links; 417 } 418 419 /* 420 * Returns a link info for a type bounds link. 421 */ 422 private HtmlLinkInfo getBoundsLinkInfo(HtmlLinkInfo linkInfo, 423 TypeMirror bound) { 424 return linkInfo.forType(bound).skipPreview(false); 425 } 426 427 /** 428 * Given a class, return the appropriate tool tip. 429 * 430 * @param typeElement the class to get the tool tip for. 431 * @return the tool tip for the appropriate class. 432 */ 433 private String getClassToolTip(TypeElement typeElement, 434 boolean isTypeLink) { 435 Resources resources = m_writer.configuration.getDocResources(); 436 if (isTypeLink) { 437 return resources.getText("doclet.Href_Type_Param_Title", 438 utils.getSimpleName(typeElement)); 439 } else if (utils.isPlainInterface(typeElement)) { 440 return resources.getText("doclet.Href_Interface_Title", 441 m_writer.getLocalizedPackageName( 442 utils.containingPackage(typeElement))); 443 } else if (utils.isAnnotationInterface(typeElement)) { 444 return resources.getText("doclet.Href_Annotation_Title", 445 m_writer.getLocalizedPackageName( 446 utils.containingPackage(typeElement))); 447 } else if (utils.isEnum(typeElement)) { 448 return resources.getText("doclet.Href_Enum_Title", 449 m_writer.getLocalizedPackageName( 450 utils.containingPackage(typeElement))); 451 } else { 452 return resources.getText("doclet.Href_Class_Title", 453 m_writer.getLocalizedPackageName( 454 utils.containingPackage(typeElement))); 455 } 456 } 457 458 /** 459 * Return path to the given file name in the given package. So if the name 460 * passed is "Object.html" and the name of the package is "java.lang", and 461 * if the relative path is "../.." then returned string will be 462 * "../../java/lang/Object.html" 463 * 464 * @param linkInfo the information about the link. 465 */ 466 private DocPath getPath(HtmlLinkInfo linkInfo) { 467 return m_writer.pathToRoot 468 .resolve(docPaths.forClass(linkInfo.getTypeElement())); 469 } 470}