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 }