1/*
2 * Copyright (c) 2001, 2016, 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
26package jdk.javadoc.internal.doclets.toolkit.taglets;
27
28import java.util.*;
29
30import javax.lang.model.element.Element;
31import javax.lang.model.element.ExecutableElement;
32import javax.lang.model.element.TypeElement;
33
34import com.sun.source.doctree.DocTree;
35import com.sun.source.doctree.ParamTree;
36import jdk.javadoc.internal.doclets.toolkit.Content;
37import jdk.javadoc.internal.doclets.toolkit.Messages;
38import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
39import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
40import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Input;
41import jdk.javadoc.internal.doclets.toolkit.util.Utils;
42
43import static com.sun.source.doctree.DocTree.Kind.*;
44
45/**
46 * A taglet that represents the @param tag.
47 *
48 *  <p><b>This is NOT part of any supported API.
49 *  If you write code that depends on this, you do so at your own risk.
50 *  This code and its internal interfaces are subject to change or
51 *  deletion without notice.</b>
52 *
53 * @author Jamie Ho
54 */
55public class ParamTaglet extends BaseTaglet implements InheritableTaglet {
56
57    /**
58     * Construct a ParamTaglet.
59     */
60    public ParamTaglet() {
61        name = PARAM.tagName;
62    }
63
64    /**
65     * Given an array of <code>Parameter</code>s, return
66     * a name/rank number map.  If the array is null, then
67     * null is returned.
68     * @param params The array of parameters (from type or executable member) to
69     *               check.
70     * @return a name-rank number map.
71     */
72    private static Map<String, String> getRankMap(Utils utils, List<? extends Element> params){
73        if (params == null) {
74            return null;
75        }
76        HashMap<String, String> result = new HashMap<>();
77        int rank = 0;
78        for (Element e : params) {
79            String name = utils.isTypeParameterElement(e)
80                    ? utils.getTypeName(e.asType(), false)
81                    : utils.getSimpleName(e);
82            result.put(name, String.valueOf(rank));
83            rank++;
84        }
85        return result;
86    }
87
88    /**
89     * {@inheritDoc}
90     */
91    public void inherit(DocFinder.Input input, DocFinder.Output output) {
92        Utils utils = input.utils;
93        if (input.tagId == null) {
94            input.isTypeVariableParamTag = ((ParamTree)input.docTreeInfo.docTree).isTypeParameter();
95            ExecutableElement ee = (ExecutableElement)input.docTreeInfo.element;
96            CommentHelper ch = utils.getCommentHelper(ee);
97            List<? extends Element> parameters = input.isTypeVariableParamTag
98                    ? ee.getTypeParameters()
99                    : ee.getParameters();
100            String target = ch.getParameterName(input.docTreeInfo.docTree);
101            for (int i = 0 ; i < parameters.size(); i++) {
102                Element e = parameters.get(i);
103                String pname = input.isTypeVariableParamTag
104                        ? utils.getTypeName(e.asType(), false)
105                        : utils.getSimpleName(e);
106                if (pname.equals(target)) {
107                    input.tagId = String.valueOf(i);
108                    break;
109                }
110            }
111        }
112        ExecutableElement md = (ExecutableElement)input.element;
113        CommentHelper ch = utils.getCommentHelper(md);
114        List<? extends DocTree> tags = input.isTypeVariableParamTag
115                ? utils.getTypeParamTrees(md)
116                : utils.getParamTrees(md);
117        List<? extends Element> parameters = input.isTypeVariableParamTag
118                ? md.getTypeParameters()
119                : md.getParameters();
120        Map<String, String> rankMap = getRankMap(utils, parameters);
121        for (DocTree tag : tags) {
122            String paramName = ch.getParameterName(tag);
123            if (rankMap.containsKey(paramName) && rankMap.get(paramName).equals((input.tagId))) {
124                output.holder = input.element;
125                output.holderTag = tag;
126                output.inlineTags = ch.getBody(utils.configuration, tag);
127                return;
128            }
129        }
130    }
131
132    /**
133     * {@inheritDoc}
134     */
135    public boolean inField() {
136        return false;
137    }
138
139    /**
140     * {@inheritDoc}
141     */
142    public boolean inMethod() {
143        return true;
144    }
145
146    /**
147     * {@inheritDoc}
148     */
149    public boolean inOverview() {
150        return false;
151    }
152
153    /**
154     * {@inheritDoc}
155     */
156    public boolean inModule() {
157        return false;
158    }
159
160    /**
161     * {@inheritDoc}
162     */
163    public boolean inPackage() {
164        return false;
165    }
166
167    /**
168     * {@inheritDoc}
169     */
170    public boolean inType() {
171        return true;
172    }
173
174    /**
175     * {@inheritDoc}
176     */
177    public boolean isInlineTag() {
178        return false;
179    }
180
181    /**
182     * Given an array of <code>ParamTag</code>s,return its string representation.
183     * @param holder the member that holds the param tags.
184     * @param writer the TagletWriter that will write this tag.
185     * @return the TagletOutput representation of these <code>ParamTag</code>s.
186     */
187    public Content getTagletOutput(Element holder, TagletWriter writer) {
188        Utils utils = writer.configuration().utils;
189        if (utils.isExecutableElement(holder)) {
190            ExecutableElement member = (ExecutableElement) holder;
191            Content output = getTagletOutput(false, member, writer,
192                member.getTypeParameters(), utils.getTypeParamTrees(member));
193            output.addContent(getTagletOutput(true, member, writer,
194                member.getParameters(), utils.getParamTrees(member)));
195            return output;
196        } else {
197            TypeElement typeElement = (TypeElement) holder;
198            return getTagletOutput(false, typeElement, writer,
199                typeElement.getTypeParameters(), utils.getTypeParamTrees(typeElement));
200        }
201    }
202
203    /**
204     * Given an array of <code>ParamTag</code>s,return its string representation.
205     * Try to inherit the param tags that are missing.
206     *
207     * @param holder            the element that holds the param tags.
208     * @param writer            the TagletWriter that will write this tag.
209     * @param formalParameters  The array of parmeters (from type or executable
210     *                          member) to check.
211     *
212     * @return the TagletOutput representation of these <code>ParamTag</code>s.
213     */
214    private Content getTagletOutput(boolean isParameters, Element holder,
215            TagletWriter writer, List<? extends Element> formalParameters, List<? extends DocTree> paramTags) {
216        Content result = writer.getOutputInstance();
217        Set<String> alreadyDocumented = new HashSet<>();
218        if (!paramTags.isEmpty()) {
219            result.addContent(
220                processParamTags(holder, isParameters, paramTags,
221                getRankMap(writer.configuration().utils, formalParameters), writer, alreadyDocumented)
222            );
223        }
224        if (alreadyDocumented.size() != formalParameters.size()) {
225            //Some parameters are missing corresponding @param tags.
226            //Try to inherit them.
227            result.addContent(getInheritedTagletOutput(isParameters, holder,
228                writer, formalParameters, alreadyDocumented));
229        }
230        return result;
231    }
232
233    /**
234     * Loop through each individual parameter, despite not having a
235     * corresponding param tag, try to inherit it.
236     */
237    private Content getInheritedTagletOutput(boolean isParameters, Element holder,
238            TagletWriter writer, List<? extends Element> formalParameters,
239            Set<String> alreadyDocumented) {
240        Utils utils = writer.configuration().utils;
241        Content result = writer.getOutputInstance();
242        if ((!alreadyDocumented.contains(null)) && utils.isExecutableElement(holder)) {
243            for (int i = 0; i < formalParameters.size(); i++) {
244                if (alreadyDocumented.contains(String.valueOf(i))) {
245                    continue;
246                }
247                // This parameter does not have any @param documentation.
248                // Try to inherit it.
249                Input input = new DocFinder.Input(writer.configuration().utils, holder, this,
250                        Integer.toString(i), !isParameters);
251                DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input);
252                if (inheritedDoc.inlineTags != null && !inheritedDoc.inlineTags.isEmpty()) {
253                    Element e = formalParameters.get(i);
254                    String lname = isParameters
255                            ? utils.getSimpleName(e)
256                            : utils.getTypeName(e.asType(), false);
257                    CommentHelper ch = utils.getCommentHelper(holder);
258                    ch.setOverrideElement(inheritedDoc.holder);
259                    Content content = processParamTag(holder, isParameters, writer,
260                            inheritedDoc.holderTag,
261                            lname,
262                            alreadyDocumented.isEmpty());
263                    result.addContent(content);
264                }
265                alreadyDocumented.add(String.valueOf(i));
266            }
267        }
268        return result;
269    }
270
271    /**
272     * Given an array of <code>Tag</code>s representing this custom
273     * tag, return its string representation.  Print a warning for param
274     * tags that do not map to parameters.  Print a warning for param
275     * tags that are duplicated.
276     *
277     * @param paramTags the array of <code>ParamTag</code>s to convert.
278     * @param writer the TagletWriter that will write this tag.
279     * @param alreadyDocumented the set of exceptions that have already
280     *        been documented.
281     * @param rankMap a {@link java.util.Map} which holds ordering
282     *                    information about the parameters.
283     * @param rankMap a {@link java.util.Map} which holds a mapping
284                of a rank of a parameter to its name.  This is
285                used to ensure that the right name is used
286                when parameter documentation is inherited.
287     * @return the Content representation of this <code>Tag</code>.
288     */
289    private Content processParamTags(Element e, boolean isParams,
290            List<? extends DocTree> paramTags, Map<String, String> rankMap, TagletWriter writer,
291            Set<String> alreadyDocumented) {
292        Messages messages = writer.configuration().getMessages();
293        Content result = writer.getOutputInstance();
294        if (!paramTags.isEmpty()) {
295            CommentHelper ch = writer.configuration().utils.getCommentHelper(e);
296            for (DocTree dt : paramTags) {
297                String paramName = isParams
298                        ? ch.getParameterName(dt)
299                        : "<" + ch.getParameterName(dt) + ">";
300                if (!rankMap.containsKey(ch.getParameterName(dt))) {
301                    messages.warning(ch.getDocTreePath(dt),
302                            isParams
303                                    ? "doclet.Parameters_warn"
304                                    : "doclet.Type_Parameters_warn",
305                            paramName);
306                }
307                String rank = rankMap.get(ch.getParameterName(dt));
308                if (rank != null && alreadyDocumented.contains(rank)) {
309                    messages.warning(ch.getDocTreePath(dt),
310                            isParams
311                                    ? "doclet.Parameters_dup_warn"
312                                    : "doclet.Type_Parameters_dup_warn",
313                            paramName);
314                }
315                result.addContent(processParamTag(e, isParams, writer, dt,
316                        ch.getParameterName(dt), alreadyDocumented.isEmpty()));
317                alreadyDocumented.add(rank);
318            }
319        }
320        return result;
321    }
322
323    /**
324     * Convert the individual ParamTag into Content.
325     *
326     * @param isNonTypeParams true if this is just a regular param tag.  False
327     *                        if this is a type param tag.
328     * @param writer          the taglet writer for output writing.
329     * @param paramTag        the tag whose inline tags will be printed.
330     * @param name            the name of the parameter.  We can't rely on
331     *                        the name in the param tag because we might be
332     *                        inheriting documentation.
333     * @param isFirstParam    true if this is the first param tag being printed.
334     *
335     */
336    private Content processParamTag(Element e, boolean isParams,
337            TagletWriter writer, DocTree paramTag, String name,
338            boolean isFirstParam) {
339        Content result = writer.getOutputInstance();
340        String header = writer.configuration().getText(
341            isParams ? "doclet.Parameters" : "doclet.TypeParameters");
342        if (isFirstParam) {
343            result.addContent(writer.getParamHeader(header));
344        }
345        result.addContent(writer.paramTagOutput(e, paramTag, name));
346        return result;
347    }
348}
349