001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 the original author or authors. 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//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.naming; 021 022import java.util.Arrays; 023import java.util.HashSet; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Set; 027import java.util.stream.Collectors; 028 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.CheckUtils; 033import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 034 035/** 036 * <p> 037 * The Check validate abbreviations(consecutive capital letters) length in 038 * identifier name, it also allows to enforce camel case naming. Please read more at 039 * <a href="http://checkstyle.sourceforge.net/reports/google-java-style.html#s5.3-camel-case"> 040 * Google Style Guide</a> to get to know how to avoid long abbreviations in names. 041 * </p> 042 * <p> 043 * Option {@code allowedAbbreviationLength} indicates on the allowed amount of capital 044 * letters in abbreviations in the classes, interfaces, 045 * variables and methods names. Default value is '3'. 046 * </p> 047 * <p> 048 * Option {@code allowedAbbreviations} - list of abbreviations that 049 * must be skipped for checking. Abbreviations should be separated by comma, 050 * no spaces are allowed. 051 * </p> 052 * <p> 053 * Option {@code ignoreFinal} allow to skip variables with {@code final} modifier. 054 * Default value is {@code true}. 055 * </p> 056 * <p> 057 * Option {@code ignoreStatic} allow to skip variables with {@code static} modifier. 058 * Default value is {@code true}. 059 * </p> 060 * <p> 061 * Option {@code ignoreOverriddenMethod} - Allows to 062 * ignore methods tagged with {@code @Override} annotation 063 * (that usually mean inherited name). Default value is {@code true}. 064 * </p> 065 * Default configuration 066 * <pre> 067 * <module name="AbbreviationAsWordInName" /> 068 * </pre> 069 * <p> 070 * To configure to check variables and classes identifiers, do not ignore 071 * variables with static modifier 072 * and allow no abbreviations (enforce camel case phrase) but allow XML and URL abbreviations. 073 * </p> 074 * <pre> 075 * <module name="AbbreviationAsWordInName"> 076 * <property name="tokens" value="VARIABLE_DEF,CLASS_DEF"/> 077 * <property name="ignoreStatic" value="false"/> 078 * <property name="allowedAbbreviationLength" value="1"/> 079 * <property name="allowedAbbreviations" value="XML,URL"/> 080 * </module> 081 * </pre> 082 * 083 * @author Roman Ivanov, Daniil Yaroslvtsev, Baratali Izmailov 084 */ 085public class AbbreviationAsWordInNameCheck extends AbstractCheck { 086 087 /** 088 * Warning message key. 089 */ 090 public static final String MSG_KEY = "abbreviation.as.word"; 091 092 /** 093 * The default value of "allowedAbbreviationLength" option. 094 */ 095 private static final int DEFAULT_ALLOWED_ABBREVIATIONS_LENGTH = 3; 096 097 /** 098 * Variable indicates on the allowed amount of capital letters in 099 * abbreviations in the classes, interfaces, variables and methods names. 100 */ 101 private int allowedAbbreviationLength = 102 DEFAULT_ALLOWED_ABBREVIATIONS_LENGTH; 103 104 /** 105 * Set of allowed abbreviation to ignore in check. 106 */ 107 private Set<String> allowedAbbreviations = new HashSet<>(); 108 109 /** Allows to ignore variables with 'final' modifier. */ 110 private boolean ignoreFinal = true; 111 112 /** Allows to ignore variables with 'static' modifier. */ 113 private boolean ignoreStatic = true; 114 115 /** Allows to ignore methods with '@Override' annotation. */ 116 private boolean ignoreOverriddenMethods = true; 117 118 /** 119 * Sets ignore option for variables with 'final' modifier. 120 * @param ignoreFinal 121 * Defines if ignore variables with 'final' modifier or not. 122 */ 123 public void setIgnoreFinal(boolean ignoreFinal) { 124 this.ignoreFinal = ignoreFinal; 125 } 126 127 /** 128 * Sets ignore option for variables with 'static' modifier. 129 * @param ignoreStatic 130 * Defines if ignore variables with 'static' modifier or not. 131 */ 132 public void setIgnoreStatic(boolean ignoreStatic) { 133 this.ignoreStatic = ignoreStatic; 134 } 135 136 /** 137 * Sets ignore option for methods with "@Override" annotation. 138 * @param ignoreOverriddenMethods 139 * Defines if ignore methods with "@Override" annotation or not. 140 */ 141 public void setIgnoreOverriddenMethods(boolean ignoreOverriddenMethods) { 142 this.ignoreOverriddenMethods = ignoreOverriddenMethods; 143 } 144 145 /** 146 * Allowed abbreviation length in names. 147 * @param allowedAbbreviationLength 148 * amount of allowed capital letters in abbreviation. 149 */ 150 public void setAllowedAbbreviationLength(int allowedAbbreviationLength) { 151 this.allowedAbbreviationLength = allowedAbbreviationLength; 152 } 153 154 /** 155 * Set a list of abbreviations that must be skipped for checking. 156 * Abbreviations should be separated by comma, no spaces is allowed. 157 * @param allowedAbbreviations 158 * an string of abbreviations that must be skipped from checking, 159 * each abbreviation separated by comma. 160 */ 161 public void setAllowedAbbreviations(String... allowedAbbreviations) { 162 if (allowedAbbreviations != null) { 163 this.allowedAbbreviations = 164 Arrays.stream(allowedAbbreviations).collect(Collectors.toSet()); 165 } 166 } 167 168 @Override 169 public int[] getDefaultTokens() { 170 return new int[] { 171 TokenTypes.CLASS_DEF, 172 TokenTypes.INTERFACE_DEF, 173 TokenTypes.ENUM_DEF, 174 TokenTypes.ANNOTATION_DEF, 175 TokenTypes.ANNOTATION_FIELD_DEF, 176 TokenTypes.PARAMETER_DEF, 177 TokenTypes.VARIABLE_DEF, 178 TokenTypes.METHOD_DEF, 179 }; 180 } 181 182 @Override 183 public int[] getAcceptableTokens() { 184 return new int[] { 185 TokenTypes.CLASS_DEF, 186 TokenTypes.INTERFACE_DEF, 187 TokenTypes.ENUM_DEF, 188 TokenTypes.ANNOTATION_DEF, 189 TokenTypes.ANNOTATION_FIELD_DEF, 190 TokenTypes.PARAMETER_DEF, 191 TokenTypes.VARIABLE_DEF, 192 TokenTypes.METHOD_DEF, 193 TokenTypes.ENUM_CONSTANT_DEF, 194 }; 195 } 196 197 @Override 198 public int[] getRequiredTokens() { 199 return CommonUtils.EMPTY_INT_ARRAY; 200 } 201 202 @Override 203 public void visitToken(DetailAST ast) { 204 205 if (!isIgnoreSituation(ast)) { 206 207 final DetailAST nameAst = ast.findFirstToken(TokenTypes.IDENT); 208 final String typeName = nameAst.getText(); 209 210 final String abbr = getDisallowedAbbreviation(typeName); 211 if (abbr != null) { 212 log(nameAst.getLineNo(), MSG_KEY, typeName, allowedAbbreviationLength); 213 } 214 } 215 } 216 217 /** 218 * Checks if it is an ignore situation. 219 * @param ast input DetailAST node. 220 * @return true if it is an ignore situation found for given input DetailAST 221 * node. 222 */ 223 private boolean isIgnoreSituation(DetailAST ast) { 224 final DetailAST modifiers = ast.getFirstChild(); 225 226 final boolean result; 227 if (ast.getType() == TokenTypes.VARIABLE_DEF) { 228 if ((ignoreFinal || ignoreStatic) 229 && isInterfaceDeclaration(ast)) { 230 // field declarations in interface are static/final 231 result = true; 232 } 233 else { 234 result = ignoreFinal 235 && modifiers.branchContains(TokenTypes.FINAL) 236 || ignoreStatic 237 && modifiers.branchContains(TokenTypes.LITERAL_STATIC); 238 } 239 } 240 else if (ast.getType() == TokenTypes.METHOD_DEF) { 241 result = ignoreOverriddenMethods 242 && hasOverrideAnnotation(modifiers); 243 } 244 else { 245 result = CheckUtils.isReceiverParameter(ast); 246 } 247 return result; 248 } 249 250 /** 251 * Check that variable definition in interface or @interface definition. 252 * @param variableDefAst variable definition. 253 * @return true if variable definition(variableDefAst) is in interface 254 * or @interface definition. 255 */ 256 private static boolean isInterfaceDeclaration(DetailAST variableDefAst) { 257 boolean result = false; 258 final DetailAST astBlock = variableDefAst.getParent(); 259 final DetailAST astParent2 = astBlock.getParent(); 260 261 if (astParent2.getType() == TokenTypes.INTERFACE_DEF 262 || astParent2.getType() == TokenTypes.ANNOTATION_DEF) { 263 result = true; 264 } 265 return result; 266 } 267 268 /** 269 * Checks that the method has "@Override" annotation. 270 * @param methodModifiersAST 271 * A DetailAST nod is related to the given method modifiers 272 * (MODIFIERS type). 273 * @return true if method has "@Override" annotation. 274 */ 275 private static boolean hasOverrideAnnotation(DetailAST methodModifiersAST) { 276 boolean result = false; 277 for (DetailAST child : getChildren(methodModifiersAST)) { 278 if (child.getType() == TokenTypes.ANNOTATION) { 279 final DetailAST annotationIdent = child.findFirstToken(TokenTypes.IDENT); 280 281 if (annotationIdent != null && "Override".equals(annotationIdent.getText())) { 282 result = true; 283 break; 284 } 285 } 286 } 287 return result; 288 } 289 290 /** 291 * Gets the disallowed abbreviation contained in given String. 292 * @param str 293 * the given String. 294 * @return the disallowed abbreviation contained in given String as a 295 * separate String. 296 */ 297 private String getDisallowedAbbreviation(String str) { 298 int beginIndex = 0; 299 boolean abbrStarted = false; 300 String result = null; 301 302 for (int index = 0; index < str.length(); index++) { 303 final char symbol = str.charAt(index); 304 305 if (Character.isUpperCase(symbol)) { 306 if (!abbrStarted) { 307 abbrStarted = true; 308 beginIndex = index; 309 } 310 } 311 else if (abbrStarted) { 312 abbrStarted = false; 313 314 final int endIndex = index - 1; 315 // -1 as a first capital is usually beginning of next word 316 result = getAbbreviationIfIllegal(str, beginIndex, endIndex); 317 if (result != null) { 318 break; 319 } 320 beginIndex = -1; 321 } 322 } 323 // if abbreviation at the end of name and it is not single character (example: scaleX) 324 if (abbrStarted && beginIndex != str.length() - 1) { 325 final int endIndex = str.length(); 326 result = getAbbreviationIfIllegal(str, beginIndex, endIndex); 327 } 328 return result; 329 } 330 331 /** 332 * Get Abbreviation if it is illegal. 333 * @param str name 334 * @param beginIndex begin index 335 * @param endIndex end index 336 * @return true is abbreviation is bigger that required and not in ignore list 337 */ 338 private String getAbbreviationIfIllegal(String str, int beginIndex, int endIndex) { 339 String result = null; 340 final int abbrLength = endIndex - beginIndex; 341 if (abbrLength > allowedAbbreviationLength) { 342 final String abbr = str.substring(beginIndex, endIndex); 343 if (!allowedAbbreviations.contains(abbr)) { 344 result = abbr; 345 } 346 } 347 return result; 348 } 349 350 /** 351 * Gets all the children which are one level below on the current DetailAST 352 * parent node. 353 * @param node 354 * Current parent node. 355 * @return The list of children one level below on the current parent node. 356 */ 357 private static List<DetailAST> getChildren(final DetailAST node) { 358 final List<DetailAST> result = new LinkedList<>(); 359 DetailAST curNode = node.getFirstChild(); 360 while (curNode != null) { 361 result.add(curNode); 362 curNode = curNode.getNextSibling(); 363 } 364 return result; 365 } 366 367}