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