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 }