1 /*
   2  * Copyright (c) 2001, 2019, 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.*;
  29 
  30 import javax.lang.model.element.Element;
  31 import javax.lang.model.element.ExecutableElement;
  32 import javax.lang.model.element.TypeElement;
  33 
  34 import com.sun.source.doctree.DocTree;
  35 import com.sun.source.doctree.ParamTree;
  36 import jdk.javadoc.internal.doclets.toolkit.Content;
  37 import jdk.javadoc.internal.doclets.toolkit.Messages;
  38 import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
  39 import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
  40 import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Input;
  41 import jdk.javadoc.internal.doclets.toolkit.util.Utils;
  42 
  43 import static com.sun.source.doctree.DocTree.Kind.PARAM;
  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  */
  55 public class ParamTaglet extends BaseTaglet implements InheritableTaglet {
  56 
  57     /**
  58      * Construct a ParamTaglet.
  59      */
  60     public ParamTaglet() {
  61         super(PARAM.tagName, false, EnumSet.of(Site.TYPE, Site.CONSTRUCTOR, Site.METHOD));
  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     @Override
  89     public void inherit(DocFinder.Input input, DocFinder.Output output) {
  90         Utils utils = input.utils;
  91         if (input.tagId == null) {
  92             input.isTypeVariableParamTag = ((ParamTree)input.docTreeInfo.docTree).isTypeParameter();
  93             ExecutableElement ee = (ExecutableElement)input.docTreeInfo.element;
  94             CommentHelper ch = utils.getCommentHelper(ee);
  95             List<? extends Element> parameters = input.isTypeVariableParamTag
  96                     ? ee.getTypeParameters()
  97                     : ee.getParameters();
  98             String target = ch.getParameterName(input.docTreeInfo.docTree);
  99             for (int i = 0 ; i < parameters.size(); i++) {
 100                 Element e = parameters.get(i);
 101                 String pname = input.isTypeVariableParamTag
 102                         ? utils.getTypeName(e.asType(), false)
 103                         : utils.getSimpleName(e);
 104                 if (pname.equals(target)) {
 105                     input.tagId = String.valueOf(i);
 106                     break;
 107                 }
 108             }
 109         }
 110         ExecutableElement md = (ExecutableElement)input.element;
 111         CommentHelper ch = utils.getCommentHelper(md);
 112         List<? extends DocTree> tags = input.isTypeVariableParamTag
 113                 ? utils.getTypeParamTrees(md)
 114                 : utils.getParamTrees(md);
 115         List<? extends Element> parameters = input.isTypeVariableParamTag
 116                 ? md.getTypeParameters()
 117                 : md.getParameters();
 118         Map<String, String> rankMap = getRankMap(utils, parameters);
 119         for (DocTree tag : tags) {
 120             String paramName = ch.getParameterName(tag);
 121             if (rankMap.containsKey(paramName) && rankMap.get(paramName).equals((input.tagId))) {
 122                 output.holder = input.element;
 123                 output.holderTag = tag;
 124                 output.inlineTags = ch.getBody(utils.configuration, tag);
 125                 return;
 126             }
 127         }
 128     }
 129 
 130     @Override
 131     public Content getTagletOutput(Element holder, TagletWriter writer) {
 132         Utils utils = writer.configuration().utils;
 133         if (utils.isExecutableElement(holder)) {
 134             ExecutableElement member = (ExecutableElement) holder;
 135             Content output = getTagletOutput(false, member, writer,
 136                 member.getTypeParameters(), utils.getTypeParamTrees(member));
 137             output.add(getTagletOutput(true, member, writer,
 138                 member.getParameters(), utils.getParamTrees(member)));
 139             return output;
 140         } else {
 141             TypeElement typeElement = (TypeElement) holder;
 142             return getTagletOutput(false, typeElement, writer,
 143                 typeElement.getTypeParameters(), utils.getTypeParamTrees(typeElement));
 144         }
 145     }
 146 
 147     /**
 148      * Given an array of {@code @param DocTree}s,return its string representation.
 149      * Try to inherit the param tags that are missing.
 150      *
 151      * @param holder            the element that holds the param tags.
 152      * @param writer            the TagletWriter that will write this tag.
 153      * @param formalParameters  The array of parmeters (from type or executable
 154      *                          member) to check.
 155      *
 156      * @return the content representation of these {@code @param DocTree}s.
 157      */
 158     private Content getTagletOutput(boolean isParameters, Element holder,
 159             TagletWriter writer, List<? extends Element> formalParameters, List<? extends DocTree> paramTags) {
 160         Content result = writer.getOutputInstance();
 161         Set<String> alreadyDocumented = new HashSet<>();
 162         if (!paramTags.isEmpty()) {
 163             result.add(
 164                 processParamTags(holder, isParameters, paramTags,
 165                 getRankMap(writer.configuration().utils, formalParameters), writer, alreadyDocumented)
 166             );
 167         }
 168         if (alreadyDocumented.size() != formalParameters.size()) {
 169             //Some parameters are missing corresponding @param tags.
 170             //Try to inherit them.
 171             result.add(getInheritedTagletOutput(isParameters, holder,
 172                 writer, formalParameters, alreadyDocumented));
 173         }
 174         return result;
 175     }
 176 
 177     /**
 178      * Loop through each individual parameter, despite not having a
 179      * corresponding param tag, try to inherit it.
 180      */
 181     private Content getInheritedTagletOutput(boolean isParameters, Element holder,
 182             TagletWriter writer, List<? extends Element> formalParameters,
 183             Set<String> alreadyDocumented) {
 184         Utils utils = writer.configuration().utils;
 185         Content result = writer.getOutputInstance();
 186         if ((!alreadyDocumented.contains(null)) && utils.isExecutableElement(holder)) {
 187             for (int i = 0; i < formalParameters.size(); i++) {
 188                 if (alreadyDocumented.contains(String.valueOf(i))) {
 189                     continue;
 190                 }
 191                 // This parameter does not have any @param documentation.
 192                 // Try to inherit it.
 193                 Input input = new DocFinder.Input(writer.configuration().utils, holder, this,
 194                         Integer.toString(i), !isParameters);
 195                 DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input);
 196                 if (inheritedDoc.inlineTags != null && !inheritedDoc.inlineTags.isEmpty()) {
 197                     Element e = formalParameters.get(i);
 198                     String lname = isParameters
 199                             ? utils.getSimpleName(e)
 200                             : utils.getTypeName(e.asType(), false);
 201                     CommentHelper ch = utils.getCommentHelper(holder);
 202                     ch.setOverrideElement(inheritedDoc.holder);
 203                     Content content = processParamTag(holder, isParameters, writer,
 204                             inheritedDoc.holderTag,
 205                             lname,
 206                             alreadyDocumented.isEmpty());
 207                     result.add(content);
 208                 }
 209                 alreadyDocumented.add(String.valueOf(i));
 210             }
 211         }
 212         return result;
 213     }
 214 
 215     /**
 216      * Given an array of {@code @param DocTree}s representing this
 217      * tag, return its string representation.  Print a warning for param
 218      * tags that do not map to parameters.  Print a warning for param
 219      * tags that are duplicated.
 220      *
 221      * @param paramTags the array of {@code @param DocTree} to convert.
 222      * @param writer the TagletWriter that will write this tag.
 223      * @param alreadyDocumented the set of exceptions that have already
 224      *        been documented.
 225      * @param rankMap a {@link java.util.Map} which holds ordering
 226      *                    information about the parameters.
 227      * @param rankMap a {@link java.util.Map} which holds a mapping
 228                 of a rank of a parameter to its name.  This is
 229                 used to ensure that the right name is used
 230                 when parameter documentation is inherited.
 231      * @return the Content representation of this {@code @param DocTree}.
 232      */
 233     private Content processParamTags(Element e, boolean isParams,
 234             List<? extends DocTree> paramTags, Map<String, String> rankMap, TagletWriter writer,
 235             Set<String> alreadyDocumented) {
 236         Messages messages = writer.configuration().getMessages();
 237         Content result = writer.getOutputInstance();
 238         if (!paramTags.isEmpty()) {
 239             CommentHelper ch = writer.configuration().utils.getCommentHelper(e);
 240             for (DocTree dt : paramTags) {
 241                 String paramName = isParams
 242                         ? ch.getParameterName(dt)
 243                         : "<" + ch.getParameterName(dt) + ">";
 244                 if (!rankMap.containsKey(ch.getParameterName(dt))) {
 245                     messages.warning(ch.getDocTreePath(dt),
 246                             isParams
 247                                     ? "doclet.Parameters_warn"
 248                                     : "doclet.Type_Parameters_warn",
 249                             paramName);
 250                 }
 251                 String rank = rankMap.get(ch.getParameterName(dt));
 252                 if (rank != null && alreadyDocumented.contains(rank)) {
 253                     messages.warning(ch.getDocTreePath(dt),
 254                             isParams
 255                                     ? "doclet.Parameters_dup_warn"
 256                                     : "doclet.Type_Parameters_dup_warn",
 257                             paramName);
 258                 }
 259                 result.add(processParamTag(e, isParams, writer, dt,
 260                         ch.getParameterName(dt), alreadyDocumented.isEmpty()));
 261                 alreadyDocumented.add(rank);
 262             }
 263         }
 264         return result;
 265     }
 266 
 267     /**
 268      * Convert the individual ParamTag into Content.
 269      *
 270      * @param e               the owner element
 271      * @param isParams true   if this is just a regular param tag.  False
 272      *                        if this is a type param tag.
 273      * @param writer          the taglet writer for output writing.
 274      * @param paramTag        the tag whose inline tags will be printed.
 275      * @param name            the name of the parameter.  We can't rely on
 276      *                        the name in the param tag because we might be
 277      *                        inheriting documentation.
 278      * @param isFirstParam    true if this is the first param tag being printed.
 279      *
 280      */
 281     private Content processParamTag(Element e, boolean isParams,
 282             TagletWriter writer, DocTree paramTag, String name,
 283             boolean isFirstParam) {
 284         Content result = writer.getOutputInstance();
 285         String header = writer.configuration().getResources().getText(
 286             isParams ? "doclet.Parameters" : "doclet.TypeParameters");
 287         if (isFirstParam) {
 288             result.add(writer.getParamHeader(header));
 289         }
 290         result.add(writer.paramTagOutput(e, paramTag, name));
 291         return result;
 292     }
 293 }