1 /* 2 * Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.javadoc.internal.doclets.toolkit.taglets; 27 28 import java.util.Collections; 29 import java.util.EnumSet; 30 import java.util.HashMap; 31 import java.util.HashSet; 32 import java.util.Iterator; 33 import java.util.LinkedHashMap; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Map.Entry; 37 import java.util.Set; 38 39 import javax.lang.model.element.Element; 40 import javax.lang.model.element.ExecutableElement; 41 import javax.lang.model.element.TypeElement; 42 import javax.lang.model.type.ExecutableType; 43 import javax.lang.model.type.TypeMirror; 44 45 import com.sun.source.doctree.DocTree; 46 47 import jdk.javadoc.doclet.Taglet.Location; 48 import jdk.javadoc.internal.doclets.toolkit.Content; 49 import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; 50 import jdk.javadoc.internal.doclets.toolkit.util.DocFinder; 51 import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Input; 52 import jdk.javadoc.internal.doclets.toolkit.util.Utils; 53 54 /** 55 * A taglet that represents the @throws tag. 56 * 57 * <p><b>This is NOT part of any supported API. 58 * If you write code that depends on this, you do so at your own risk. 59 * This code and its internal interfaces are subject to change or 60 * deletion without notice.</b> 61 */ 62 public class ThrowsTaglet extends BaseTaglet 63 implements InheritableTaglet { 64 ThrowsTaglet()65 public ThrowsTaglet() { 66 super(DocTree.Kind.THROWS, false, EnumSet.of(Location.CONSTRUCTOR, Location.METHOD)); 67 } 68 69 @Override inherit(DocFinder.Input input, DocFinder.Output output)70 public void inherit(DocFinder.Input input, DocFinder.Output output) { 71 Utils utils = input.utils; 72 Element exception; 73 CommentHelper ch = utils.getCommentHelper(input.element); 74 if (input.tagId == null) { 75 exception = ch.getException(input.docTreeInfo.docTree); 76 input.tagId = exception == null 77 ? ch.getExceptionName(input.docTreeInfo.docTree).getSignature() 78 : utils.getFullyQualifiedName(exception); 79 } else { 80 exception = input.utils.findClass(input.element, input.tagId); 81 } 82 83 for (DocTree dt : input.utils.getThrowsTrees(input.element)) { 84 Element exc = ch.getException(dt); 85 if (exc != null && (input.tagId.equals(utils.getSimpleName(exc)) || 86 (input.tagId.equals(utils.getFullyQualifiedName(exc))))) { 87 output.holder = input.element; 88 output.holderTag = dt; 89 output.inlineTags = ch.getBody(output.holderTag); 90 output.tagList.add(dt); 91 } else if (exception != null && exc != null && 92 utils.isTypeElement(exc) && utils.isTypeElement(exception) && 93 utils.isSubclassOf((TypeElement)exc, (TypeElement)exception)) { 94 output.tagList.add(dt); 95 } 96 } 97 } 98 99 /** 100 * Add links for exceptions that are declared but not documented. 101 */ linkToUndocumentedDeclaredExceptions(List<? extends TypeMirror> declaredExceptionTypes, Set<String> alreadyDocumented, TagletWriter writer)102 private Content linkToUndocumentedDeclaredExceptions(List<? extends TypeMirror> declaredExceptionTypes, 103 Set<String> alreadyDocumented, TagletWriter writer) { 104 Utils utils = writer.configuration().utils; 105 Content result = writer.getOutputInstance(); 106 //Add links to the exceptions declared but not documented. 107 for (TypeMirror declaredExceptionType : declaredExceptionTypes) { 108 TypeElement te = utils.asTypeElement(declaredExceptionType); 109 if (te != null && 110 !alreadyDocumented.contains(declaredExceptionType.toString()) && 111 !alreadyDocumented.contains(utils.getFullyQualifiedName(te, false))) { 112 if (alreadyDocumented.isEmpty()) { 113 result.add(writer.getThrowsHeader()); 114 } 115 result.add(writer.throwsTagOutput(declaredExceptionType)); 116 alreadyDocumented.add(utils.getSimpleName(te)); 117 } 118 } 119 return result; 120 } 121 122 /** 123 * Inherit throws documentation for exceptions that were declared but not 124 * documented. 125 */ inheritThrowsDocumentation(Element holder, List<? extends TypeMirror> declaredExceptionTypes, Set<String> alreadyDocumented, Map<String, TypeMirror> typeSubstitutions, TagletWriter writer)126 private Content inheritThrowsDocumentation(Element holder, 127 List<? extends TypeMirror> declaredExceptionTypes, Set<String> alreadyDocumented, 128 Map<String, TypeMirror> typeSubstitutions, TagletWriter writer) { 129 Utils utils = writer.configuration().utils; 130 Content result = writer.getOutputInstance(); 131 if (utils.isExecutableElement(holder)) { 132 Map<List<? extends DocTree>, ExecutableElement> declaredExceptionTags = new LinkedHashMap<>(); 133 for (TypeMirror declaredExceptionType : declaredExceptionTypes) { 134 Input input = new DocFinder.Input(utils, holder, this, 135 utils.getTypeName(declaredExceptionType, false)); 136 DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input); 137 if (inheritedDoc.tagList.isEmpty()) { 138 String typeName = utils.getTypeName(declaredExceptionType, true); 139 input = new DocFinder.Input(utils, holder, this, typeName); 140 inheritedDoc = DocFinder.search(writer.configuration(), input); 141 } 142 if (!inheritedDoc.tagList.isEmpty()) { 143 if (inheritedDoc.holder == null) { 144 inheritedDoc.holder = holder; 145 } 146 declaredExceptionTags.put(inheritedDoc.tagList, (ExecutableElement)inheritedDoc.holder); 147 } 148 } 149 result.add(throwsTagsOutput(declaredExceptionTags, writer, alreadyDocumented, 150 typeSubstitutions, false)); 151 } 152 return result; 153 } 154 155 @Override getTagletOutput(Element holder, TagletWriter writer)156 public Content getTagletOutput(Element holder, TagletWriter writer) { 157 Utils utils = writer.configuration().utils; 158 ExecutableElement execHolder = (ExecutableElement) holder; 159 ExecutableType instantiatedType = utils.asInstantiatedMethodType( 160 writer.getCurrentPageElement(), (ExecutableElement)holder); 161 List<? extends TypeMirror> thrownTypes = instantiatedType.getThrownTypes(); 162 Map<String, TypeMirror> typeSubstitutions = getSubstitutedThrownTypes( 163 ((ExecutableElement) holder).getThrownTypes(), thrownTypes); 164 Map<List<? extends DocTree>, ExecutableElement> tagsMap = new LinkedHashMap<>(); 165 tagsMap.put(utils.getThrowsTrees(execHolder), execHolder); 166 Content result = writer.getOutputInstance(); 167 HashSet<String> alreadyDocumented = new HashSet<>(); 168 if (!tagsMap.isEmpty()) { 169 result.add(throwsTagsOutput(tagsMap, writer, alreadyDocumented, typeSubstitutions, true)); 170 } 171 result.add(inheritThrowsDocumentation(holder, 172 thrownTypes, alreadyDocumented, typeSubstitutions, writer)); 173 result.add(linkToUndocumentedDeclaredExceptions(thrownTypes, alreadyDocumented, writer)); 174 return result; 175 } 176 177 /** 178 * Returns the generated content for a collection of {@code @throws} tags. 179 * 180 * @param throwTags the collection of tags to be converted 181 * @param writer the taglet-writer used by the doclet 182 * @param alreadyDocumented the set of exceptions that have already been documented 183 * @param allowDuplicates {@code true} if we allow duplicate tags to be documented 184 * @return the generated content for the tags 185 */ throwsTagsOutput(Map<List<? extends DocTree>, ExecutableElement> throwTags, TagletWriter writer, Set<String> alreadyDocumented, Map<String,TypeMirror> typeSubstitutions, boolean allowDuplicates)186 protected Content throwsTagsOutput(Map<List<? extends DocTree>, ExecutableElement> throwTags, 187 TagletWriter writer, Set<String> alreadyDocumented, 188 Map<String,TypeMirror> typeSubstitutions, boolean allowDuplicates) { 189 Utils utils = writer.configuration().utils; 190 Content result = writer.getOutputInstance(); 191 if (!throwTags.isEmpty()) { 192 for (Entry<List<? extends DocTree>, ExecutableElement> entry : throwTags.entrySet()) { 193 CommentHelper ch = utils.getCommentHelper(entry.getValue()); 194 Element e = entry.getValue(); 195 for (DocTree dt : entry.getKey()) { 196 Element te = ch.getException(dt); 197 String excName = ch.getExceptionName(dt).toString(); 198 TypeMirror substituteType = typeSubstitutions.get(excName); 199 if ((!allowDuplicates) && 200 (alreadyDocumented.contains(excName) || 201 (te != null && alreadyDocumented.contains(utils.getFullyQualifiedName(te, false)))) || 202 (substituteType != null && alreadyDocumented.contains(substituteType.toString()))) { 203 continue; 204 } 205 if (alreadyDocumented.isEmpty()) { 206 result.add(writer.getThrowsHeader()); 207 } 208 result.add(writer.throwsTagOutput(e, dt, substituteType)); 209 if (substituteType != null) { 210 alreadyDocumented.add(substituteType.toString()); 211 } else { 212 alreadyDocumented.add(te != null 213 ? utils.getFullyQualifiedName(te, false) 214 : excName); 215 } 216 } 217 } 218 } 219 return result; 220 } 221 222 /** 223 * Returns a map of substitutions for a list of thrown types with the original type-variable 224 * name as key and the instantiated type as value. If no types need to be substituted 225 * an empty map is returned. 226 * @param declaredThrownTypes the originally declared thrown types. 227 * @param instantiatedThrownTypes the thrown types in the context of the current type. 228 * @return map of declared to instantiated thrown types or an empty map. 229 */ getSubstitutedThrownTypes(List<? extends TypeMirror> declaredThrownTypes, List<? extends TypeMirror> instantiatedThrownTypes)230 private Map<String, TypeMirror> getSubstitutedThrownTypes(List<? extends TypeMirror> declaredThrownTypes, 231 List<? extends TypeMirror> instantiatedThrownTypes) { 232 if (!instantiatedThrownTypes.equals(declaredThrownTypes)) { 233 Map<String, TypeMirror> map = new HashMap<>(); 234 Iterator<? extends TypeMirror> i1 = instantiatedThrownTypes.iterator(); 235 Iterator<? extends TypeMirror> i2 = declaredThrownTypes.iterator(); 236 while (i1.hasNext() && i2.hasNext()) { 237 TypeMirror t1 = i1.next(); 238 TypeMirror t2 = i2.next(); 239 if (!t1.equals(t2)) 240 map.put(t2.toString(), t1); 241 } 242 return map; 243 } 244 return Collections.emptyMap(); 245 } 246 } 247