001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.javadoc; 020 021import java.lang.reflect.Field; 022import java.lang.reflect.Modifier; 023import java.util.List; 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026 027import com.google.common.collect.ImmutableMap; 028import com.google.common.collect.Lists; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.DetailNode; 031import com.puppycrawl.tools.checkstyle.api.JavadocTagInfo; 032import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 033import com.puppycrawl.tools.checkstyle.api.TextBlock; 034import com.puppycrawl.tools.checkstyle.api.Utils; 035 036/** 037 * Contains utility methods for working with Javadoc. 038 * @author Lyle Hanson 039 */ 040public final class JavadocUtils 041{ 042 /** maps from a token name to value */ 043 private static final ImmutableMap<String, Integer> TOKEN_NAME_TO_VALUE; 044 /** maps from a token value to name */ 045 private static final String[] TOKEN_VALUE_TO_NAME; 046 047 // Using reflection gets all token names and values from JavadocTokenTypes class 048 // and saves to TOKEN_NAME_TO_VALUE and TOKEN_VALUE_TO_NAME collections. 049 static { 050 final ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder(); 051 052 final Field[] fields = JavadocTokenTypes.class.getDeclaredFields(); 053 054 String[] tempTokenValueToName = new String[0]; 055 056 for (final Field f : fields) { 057 058 // Only process public int fields. 059 if (!Modifier.isPublic(f.getModifiers()) 060 || f.getType() != Integer.TYPE) 061 { 062 continue; 063 } 064 065 final String name = f.getName(); 066 067 try { 068 final int tokenValue = f.getInt(name); 069 builder.put(name, tokenValue); 070 if (tokenValue > tempTokenValueToName.length - 1) { 071 final String[] temp = new String[tokenValue + 1]; 072 System.arraycopy(tempTokenValueToName, 0, temp, 0, tempTokenValueToName.length); 073 tempTokenValueToName = temp; 074 } 075 if (tokenValue == -1) { 076 tempTokenValueToName[0] = name; 077 } 078 else { 079 tempTokenValueToName[tokenValue] = name; 080 } 081 } 082 catch (Exception e) { 083 throw new IllegalStateException("Failed to instantiate collection of Javadoc tokens" 084 , e); 085 } 086 } 087 088 TOKEN_NAME_TO_VALUE = builder.build(); 089 TOKEN_VALUE_TO_NAME = tempTokenValueToName; 090 } 091 092 ///CLOVER:OFF 093 /** prevent instantiation */ 094 private JavadocUtils() 095 { 096 } 097 098 ///CLOVER:ON 099 100 /** 101 * Gets validTags from a given piece of Javadoc. 102 * @param cmt 103 * the Javadoc comment to process. 104 * @param tagType 105 * the type of validTags we're interested in 106 * @return all standalone validTags from the given javadoc. 107 */ 108 public static JavadocTags getJavadocTags(TextBlock cmt, 109 JavadocTagType tagType) 110 { 111 final String[] text = cmt.getText(); 112 final List<JavadocTag> tags = Lists.newArrayList(); 113 final List<InvalidJavadocTag> invalidTags = Lists.newArrayList(); 114 Pattern blockTagPattern = 115 Utils.getPattern("/\\*{2,}\\s*@(\\p{Alpha}+)\\s"); 116 for (int i = 0; i < text.length; i++) { 117 final String s = text[i]; 118 final Matcher blockTagMatcher = blockTagPattern.matcher(s); 119 if ((tagType.equals(JavadocTagType.ALL) || tagType 120 .equals(JavadocTagType.BLOCK)) && blockTagMatcher.find()) 121 { 122 final String tagName = blockTagMatcher.group(1); 123 String content = s.substring(blockTagMatcher.end(1)); 124 if (content.endsWith("*/")) { 125 content = content.substring(0, content.length() - 2); 126 } 127 final int line = cmt.getStartLineNo() + i; 128 int col = blockTagMatcher.start(1) - 1; 129 if (i == 0) { 130 col += cmt.getStartColNo(); 131 } 132 if (JavadocTagInfo.isValidName(tagName)) { 133 tags.add( 134 new JavadocTag(line, col, tagName, content.trim())); 135 } 136 else { 137 invalidTags.add(new InvalidJavadocTag(line, col, tagName)); 138 } 139 } 140 // No block tag, so look for inline validTags 141 else if (tagType.equals(JavadocTagType.ALL) 142 || tagType.equals(JavadocTagType.INLINE)) 143 { 144 // Match Javadoc text after comment characters 145 final Pattern commentPattern = 146 Utils.getPattern("^\\s*(?:/\\*{2,}|\\*+)\\s*(.*)"); 147 final Matcher commentMatcher = commentPattern.matcher(s); 148 final String commentContents; 149 final int commentOffset; // offset including comment characters 150 if (!commentMatcher.find()) { 151 commentContents = s; // No leading asterisks, still valid 152 commentOffset = 0; 153 } 154 else { 155 commentContents = commentMatcher.group(1); 156 commentOffset = commentMatcher.start(1) - 1; 157 } 158 final Pattern tagPattern = 159 Utils.getPattern(".*?\\{@(\\p{Alpha}+)\\s+(.*?)\\}"); 160 final Matcher tagMatcher = tagPattern.matcher(commentContents); 161 while (tagMatcher.find()) { 162 if (tagMatcher.groupCount() == 2) { 163 final String tagName = tagMatcher.group(1); 164 final String tagValue = tagMatcher.group(2).trim(); 165 final int line = cmt.getStartLineNo() + i; 166 int col = commentOffset + (tagMatcher.start(1) - 1); 167 if (i == 0) { 168 col += cmt.getStartColNo(); 169 } 170 if (JavadocTagInfo.isValidName(tagName)) { 171 tags.add(new JavadocTag(line, col, tagName, 172 tagValue)); 173 } 174 else { 175 invalidTags.add(new InvalidJavadocTag(line, col, 176 tagName)); 177 } 178 } 179 // else Error: Unexpected match count for inline Javadoc 180 // tag! 181 } 182 } 183 blockTagPattern = 184 Utils.getPattern("^\\s*\\**\\s*@(\\p{Alpha}+)\\s"); 185 } 186 return new JavadocTags(tags, invalidTags); 187 } 188 189 /** 190 * The type of Javadoc tag we want returned. 191 */ 192 public enum JavadocTagType 193 { 194 /** block type. */ 195 BLOCK, 196 /** inline type. */ 197 INLINE, 198 /** all validTags. */ 199 ALL; 200 } 201 202 /** 203 * Checks that commentContent starts with '*' javadoc comment identifier. 204 * @param commentContent 205 * content of block comment 206 * @return true if commentContent starts with '*' javadoc comment 207 * identifier. 208 */ 209 public static boolean isJavadocComment(String commentContent) 210 { 211 boolean result = false; 212 213 if (!commentContent.isEmpty()) { 214 final char docCommentIdentificator = commentContent.charAt(0); 215 result = docCommentIdentificator == '*'; 216 } 217 218 return result; 219 } 220 221 /** 222 * Checks block comment content starts with '*' javadoc comment identifier. 223 * @param blockCommentBegin 224 * block comment AST 225 * @return true if block comment content starts with '*' javadoc comment 226 * identifier. 227 */ 228 public static boolean isJavadocComment(DetailAST blockCommentBegin) 229 { 230 final String commentContent = getBlockCommentContent(blockCommentBegin); 231 return isJavadocComment(commentContent); 232 } 233 234 /** 235 * Gets content of block comment. 236 * @param blockCommentBegin 237 * block comment AST. 238 * @return content of block comment. 239 */ 240 public static String getBlockCommentContent(DetailAST blockCommentBegin) 241 { 242 final DetailAST commentContent = blockCommentBegin.getFirstChild(); 243 return commentContent.getText(); 244 } 245 246 /** 247 * Get content of Javadoc comment. 248 * @param javdocCommentBegin 249 * Javadoc comment AST 250 * @return content of Javadoc comment. 251 */ 252 public static String getJavadocCommentContent(DetailAST javdocCommentBegin) 253 { 254 final DetailAST commentContent = javdocCommentBegin.getFirstChild(); 255 return commentContent.getText().substring(1); 256 } 257 258 /** 259 * Returns the first child token that has a specified type. 260 * @param node 261 * Javadoc AST node 262 * @param type 263 * the token type to match 264 * @return the matching token, or null if no match 265 */ 266 public static DetailNode findFirstToken(DetailNode node, int type) 267 { 268 DetailNode retVal = null; 269 for (DetailNode i = getFirstChild(node); i != null; i = getNextSibling(i)) { 270 if (i.getType() == type) { 271 retVal = i; 272 break; 273 } 274 } 275 return retVal; 276 } 277 278 /** 279 * Gets first child node of specified node. 280 * 281 * @param node DetailNode 282 * @return first child 283 */ 284 public static DetailNode getFirstChild(DetailNode node) 285 { 286 return node.getChildren().length > 0 ? node.getChildren()[0] : null; 287 } 288 289 /** 290 * Checks whether node contains any node of specified type among children on any deep level. 291 * 292 * @param node DetailNode 293 * @param type token type 294 * @return true if node contains any node of type type among children on any deep level. 295 */ 296 public static boolean branchContains(DetailNode node, int type) 297 { 298 DetailNode curNode = node; 299 while (curNode != null) { 300 301 if (type == curNode.getType()) { 302 return true; 303 } 304 305 DetailNode toVisit = getFirstChild(curNode); 306 while ((curNode != null) && (toVisit == null)) { 307 toVisit = getNextSibling(curNode); 308 if (toVisit == null) { 309 curNode = curNode.getParent(); 310 } 311 } 312 313 if (curNode == toVisit) { 314 break; 315 } 316 317 curNode = toVisit; 318 } 319 320 return false; 321 } 322 323 /** 324 * Gets next sibling of specified node. 325 * 326 * @param node DetailNode 327 * @return next sibling. 328 */ 329 public static DetailNode getNextSibling(DetailNode node) 330 { 331 final DetailNode parent = node.getParent(); 332 if (parent != null) { 333 final int nextSiblingIndex = node.getIndex() + 1; 334 final DetailNode[] children = parent.getChildren(); 335 if (nextSiblingIndex <= children.length - 1) { 336 return children[nextSiblingIndex]; 337 } 338 } 339 return null; 340 } 341 342 /** 343 * Gets previous sibling of specified node. 344 * @param node DetailNode 345 * @return previous sibling 346 */ 347 public static DetailNode getPreviousSibling(DetailNode node) 348 { 349 final DetailNode parent = node.getParent(); 350 if (parent != null) { 351 final int previousSiblingIndex = node.getIndex() - 1; 352 final DetailNode[] children = parent.getChildren(); 353 if (previousSiblingIndex >= 0) { 354 return children[previousSiblingIndex]; 355 } 356 } 357 return null; 358 } 359 360 /** 361 * Returns the name of a token for a given ID. 362 * @param iD 363 * the ID of the token name to get 364 * @return a token name 365 */ 366 public static String getTokenName(int iD) 367 { 368 if (iD == JavadocTokenTypes.EOF) { 369 return "EOF"; 370 } 371 if (iD > TOKEN_VALUE_TO_NAME.length - 1) { 372 throw new IllegalArgumentException("Unknown javdoc token id. Given id: " + iD); 373 } 374 final String name = TOKEN_VALUE_TO_NAME[iD]; 375 if (name == null) { 376 throw new IllegalArgumentException("Unknown javdoc token id. Given id: " + iD); 377 } 378 return name; 379 } 380 381 /** 382 * Returns the ID of a token for a given name. 383 * @param name 384 * the name of the token ID to get 385 * @return a token ID 386 */ 387 public static int getTokenId(String name) 388 { 389 final Integer id = TOKEN_NAME_TO_VALUE.get(name); 390 if (id == null) { 391 throw new IllegalArgumentException("Unknown javdoc token name. Given name " + name); 392 } 393 return id.intValue(); 394 } 395 396}