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