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