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         STATE_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     public Content getTagletOutput(Element holder, TagletWriter writer) {
 141         Utils utils = writer.configuration().utils;
 142         if (utils.isExecutableElement(holder)) {
 143             ExecutableElement member = (ExecutableElement) holder;
 144             Content output = getTagletOutput(ParamKind.TYPE_PARAMETER, member, writer,
 145                 member.getTypeParameters(), utils.getTypeParamTrees(member));
 146             output.add(getTagletOutput(ParamKind.PARAMETER, member, writer,
 147                 member.getParameters(), utils.getParamTrees(member)));
 148             return output;
 149         } else {
 150             TypeElement typeElement = (TypeElement) holder;
 151             Content output = getTagletOutput(ParamKind.TYPE_PARAMETER, typeElement, writer,
 152                 typeElement.getTypeParameters(), utils.getTypeParamTrees(typeElement));
 153             output.add(getTagletOutput(ParamKind.STATE_COMPONENT, typeElement, writer,
 154                     typeElement.getStateComponents(), utils.getParamTrees(typeElement)));
 155             return output;
 156         }
 157     }
 158 
 159     /**
 160      * Given an array of {@code @param DocTree}s,return its string representation.
 161      * Try to inherit the param tags that are missing.
 162      *
 163      * @param holder            the element that holds the param tags.
 164      * @param writer            the TagletWriter that will write this tag.
 165      * @param formalParameters  The array of parameters (from type or executable
 166      *                          member) to check.
 167      *
 168      * @return the content representation of these {@code @param DocTree}s.
 169      */
 170     private Content getTagletOutput(ParamKind kind, Element holder,
 171             TagletWriter writer, List<? extends Element> formalParameters, List<? extends DocTree> paramTags) {
 172         Content result = writer.getOutputInstance();
 173         Set<String> alreadyDocumented = new HashSet<>();
 174         if (!paramTags.isEmpty()) {
 175             result.add(
 176                 processParamTags(holder, kind, paramTags,
 177                 getRankMap(writer.configuration().utils, formalParameters), writer, alreadyDocumented)
 178             );
 179         }
 180         if (alreadyDocumented.size() != formalParameters.size()) {
 181             //Some parameters are missing corresponding @param tags.
 182             //Try to inherit them.
 183             result.add(getInheritedTagletOutput(kind, holder,
 184                 writer, formalParameters, alreadyDocumented));
 185         }
 186         return result;
 187     }
 188 
 189     /**
 190      * Loop through each individual parameter, despite not having a
 191      * corresponding param tag, try to inherit it.
 192      */
 193     private Content getInheritedTagletOutput(ParamKind kind, Element holder,
 194             TagletWriter writer, List<? extends Element> formalParameters,
 195             Set<String> alreadyDocumented) {
 196         Utils utils = writer.configuration().utils;
 197         Content result = writer.getOutputInstance();
 198         if ((!alreadyDocumented.contains(null)) && utils.isExecutableElement(holder)) {
 199             for (int i = 0; i < formalParameters.size(); i++) {
 200                 if (alreadyDocumented.contains(String.valueOf(i))) {
 201                     continue;
 202                 }
 203                 // This parameter does not have any @param documentation.
 204                 // Try to inherit it.
 205                 Input input = new DocFinder.Input(writer.configuration().utils, holder, this,
 206                         Integer.toString(i), kind == ParamKind.TYPE_PARAMETER);
 207                 DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input);
 208                 if (inheritedDoc.inlineTags != null && !inheritedDoc.inlineTags.isEmpty()) {
 209                     Element e = formalParameters.get(i);
 210                     String lname = kind != ParamKind.TYPE_PARAMETER
 211                             ? utils.getSimpleName(e)
 212                             : utils.getTypeName(e.asType(), false);
 213                     CommentHelper ch = utils.getCommentHelper(holder);
 214                     ch.setOverrideElement(inheritedDoc.holder);
 215                     Content content = processParamTag(holder, kind, writer,
 216                             inheritedDoc.holderTag,
 217                             lname,
 218                             alreadyDocumented.isEmpty());
 219                     result.add(content);
 220                 }
 221                 alreadyDocumented.add(String.valueOf(i));
 222             }
 223         }
 224         return result;
 225     }
 226 
 227     /**
 228      * Given an array of {@code @param DocTree}s representing this
 229      * tag, return its string representation.  Print a warning for param
 230      * tags that do not map to parameters.  Print a warning for param
 231      * tags that are duplicated.
 232      *
 233      * @param paramTags the array of {@code @param DocTree} to convert.
 234      * @param writer the TagletWriter that will write this tag.
 235      * @param alreadyDocumented the set of exceptions that have already
 236      *        been documented.
 237      * @param rankMap a {@link java.util.Map} which holds ordering
 238      *                    information about the parameters.
 239      * @param rankMap a {@link java.util.Map} which holds a mapping
 240                 of a rank of a parameter to its name.  This is
 241                 used to ensure that the right name is used
 242                 when parameter documentation is inherited.
 243      * @return the Content representation of this {@code @param DocTree}.
 244      */
 245     private Content processParamTags(Element e, ParamKind kind,
 246             List<? extends DocTree> paramTags, Map<String, String> rankMap, TagletWriter writer,
 247             Set<String> alreadyDocumented) {
 248         Messages messages = writer.configuration().getMessages();
 249         Content result = writer.getOutputInstance();
 250         if (!paramTags.isEmpty()) {
 251             CommentHelper ch = writer.configuration().utils.getCommentHelper(e);
 252             for (DocTree dt : paramTags) {
 253                 String name = ch.getParameterName(dt);
 254                 String paramName = kind != ParamKind.TYPE_PARAMETER
 255                         ? name.toString()
 256                         : "<" + name + ">";
 257                 if (!rankMap.containsKey(name)) {
 258                     String key;
 259                     switch (kind) {
 260                         case PARAMETER:       key = "doclet.Parameters_warn" ; break;
 261                         case TYPE_PARAMETER:  key = "doclet.TypeParameters_warn" ; break;
 262                         case STATE_COMPONENT: key = "doclet.StateComponents_warn" ; break;
 263                         default: throw new IllegalArgumentException(kind.toString());
 264                     }
 265                     messages.warning(ch.getDocTreePath(dt), key, paramName);
 266                 }
 267                 String rank = rankMap.get(name);
 268                 if (rank != null && alreadyDocumented.contains(rank)) {
 269                     String key;
 270                     switch (kind) {
 271                         case PARAMETER:       key = "doclet.Parameters_dup_warn" ; break;
 272                         case TYPE_PARAMETER:  key = "doclet.TypeParameters_dup_warn" ; break;
 273                         case STATE_COMPONENT: key = "doclet.StateComponents_dup_warn" ; break;
 274                         default: throw new IllegalArgumentException(kind.toString());
 275                     }
 276                     messages.warning(ch.getDocTreePath(dt), key, paramName);
 277                 }
 278                 result.add(processParamTag(e, kind, writer, dt,
 279                         name, alreadyDocumented.isEmpty()));
 280                 alreadyDocumented.add(rank);
 281             }
 282         }
 283         return result;
 284     }
 285 
 286     /**
 287      * Convert the individual ParamTag into Content.
 288      *
 289      * @param e               the owner element
 290      * @param kind            the kind of param tag
 291      * @param writer          the taglet writer for output writing.
 292      * @param paramTag        the tag whose inline tags will be printed.
 293      * @param name            the name of the parameter.  We can't rely on
 294      *                        the name in the param tag because we might be
 295      *                        inheriting documentation.
 296      * @param isFirstParam    true if this is the first param tag being printed.
 297      *
 298      */
 299     private Content processParamTag(Element e, ParamKind kind,
 300             TagletWriter writer, DocTree paramTag, String name,
 301             boolean isFirstParam) {
 302         Content result = writer.getOutputInstance();
 303         if (isFirstParam) {
 304             String key;
 305             switch (kind) {
 306                 case PARAMETER:       key = "doclet.Parameters" ; break;
 307                 case TYPE_PARAMETER:  key = "doclet.TypeParameters" ; break;
 308                 case STATE_COMPONENT: key = "doclet.StateComponents" ; break;
 309                 default: throw new IllegalArgumentException(kind.toString());
 310             }
 311             String header = writer.configuration().getResources().getText(key);
 312             result.add(writer.getParamHeader(header));
 313         }
 314         result.add(writer.paramTagOutput(e, paramTag, name));
 315         return result;
 316     }
 317 }