1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.naming;
21  
22  import java.util.Arrays;
23  import java.util.HashSet;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Set;
27  import java.util.stream.Collectors;
28  
29  import com.puppycrawl.tools.checkstyle.StatelessCheck;
30  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31  import com.puppycrawl.tools.checkstyle.api.DetailAST;
32  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
34  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
35  
36  /**
37   * <p>
38   * The Check validate abbreviations(consecutive capital letters) length in
39   * identifier name, it also allows to enforce camel case naming. Please read more at
40   * <a href="styleguides/google-java-style-20170228.html#s5.3-camel-case">
41   * Google Style Guide</a> to get to know how to avoid long abbreviations in names.
42   * </p>
43   * <p>
44   * {@code allowedAbbreviationLength} specifies how many consecutive capital letters are
45   * allowed in the identifier.
46   * A value of <i>3</i> indicates that up to 4 consecutive capital letters are allowed,
47   * one after the other, before a violation is printed. The identifier 'MyTEST' would be
48   * allowed, but 'MyTESTS' would not be.
49   * A value of <i>0</i> indicates that only 1 consecutive capital letter is allowed. This
50   * is what should be used to enforce strict camel casing. The identifier 'MyTest' would
51   * be allowed, but 'MyTEst' would not be.
52   * </p>
53   * <ul>
54   * <li>
55   * Property {@code allowedAbbreviationLength} - Indicate the number of consecutive capital
56   * letters allowed in targeted identifiers (abbreviations in the classes, interfaces, variables
57   * and methods names, ... ). Default value is {@code 3}.
58   * </li>
59   * <li>
60   * Property {@code allowedAbbreviations} - Specify list of abbreviations that must be skipped for
61   * checking. Abbreviations should be separated by comma. Default value is {@code {}}.
62   * </li>
63   * <li>
64   * Property {@code ignoreFinal} - Allow to skip variables with {@code final} modifier. Default
65   * value is {@code true}.
66   * </li>
67   * <li>
68   * Property {@code ignoreStatic} - Allow to skip variables with {@code static} modifier. Default
69   * value is {@code true}.
70   * </li>
71   * <li>
72   * Property {@code ignoreOverriddenMethods} - Allow to ignore methods tagged with {@code @Override}
73   * annotation (that usually mean inherited name). Default value is {@code true}.
74   * </li>
75   * <li>
76   * Property {@code tokens} - tokens to check Default value is:
77   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">CLASS_DEF</a>,
78   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">INTERFACE_DEF</a>,
79   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">ENUM_DEF</a>,
80   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">ANNOTATION_DEF</a>,
81   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">ANNOTATION_FIELD_DEF</a>,
82   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PARAMETER_DEF">PARAMETER_DEF</a>,
83   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF">VARIABLE_DEF</a>,
84   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">METHOD_DEF</a>.
85   * </li>
86   * </ul>
87   * <p>
88   * Default configuration
89   * </p>
90   * <pre>
91   * &lt;module name="AbbreviationAsWordInName"/&gt;
92   * </pre>
93   * <p>
94   * To configure to check all variables and identifiers
95   * (including ones with the static modifier) and enforce
96   * no abbreviations (essentially camel case) except for
97   * words like 'XML' and 'URL'.
98   * </p>
99   * <p>Configuration:</p>
100  * <pre>
101  * &lt;module name="AbbreviationAsWordInName"&gt;
102  *   &lt;property name="tokens" value="VARIABLE_DEF,CLASS_DEF"/&gt;
103  *   &lt;property name="ignoreStatic" value="false"/&gt;
104  *   &lt;property name="allowedAbbreviationLength" value="0"/&gt;
105  *   &lt;property name="allowedAbbreviations" value="XML,URL"/&gt;
106  * &lt;/module&gt;
107  * </pre>
108  * <p>Example:</p>
109  * <pre>
110  * public class MyClass { // OK
111  *   int firstNum; // OK
112  *   int secondNUM; // violation, it allowed only 1 consecutive capital letter
113  *   static int thirdNum; // OK, the static modifier would be checked
114  *   static int fourthNUm; // violation, the static modifier would be checked,
115  *                         // and only 1 consecutive capital letter is allowed
116  *   String firstXML; // OK, XML abbreviation is allowed
117  *   String firstURL; // OK, URL abbreviation is allowed
118  * }
119  * </pre>
120  * <p>
121  * To configure to check variables, excluding fields with
122  * the static modifier, and allow abbreviations up to 2
123  * consecutive capital letters ignoring the longer word 'CSV'.
124  * </p>
125  * <p>Configuration:</p>
126  * <pre>
127  * &lt;module name="AbbreviationAsWordInName"&gt;
128  *   &lt;property name="tokens" value="VARIABLE_DEF"/&gt;
129  *   &lt;property name="ignoreStatic" value="true"/&gt;
130  *   &lt;property name="allowedAbbreviationLength" value="1"/&gt;
131  *   &lt;property name="allowedAbbreviations" value="CSV"/&gt;
132  * &lt;/module&gt;
133  * </pre>
134  * <p>Example:</p>
135  * <pre>
136  * public class MyClass { // OK, ignore checking the class name
137  *   int firstNum; // OK, abbreviation "N" is of allowed length 1
138  *   int secondNUm; // OK
139  *   int secondMYNum; // violation, found "MYN" but only
140  *                    // 2 consecutive capital letters are allowed
141  *   int thirdNUM; // violation, found "NUM" but it is allowed
142  *                 // only 2 consecutive capital letters
143  *   static int fourthNUM; // OK, variables with static modifier
144  *                         // would be ignored
145  *   String firstCSV; // OK, CSV abbreviation is allowed
146  *   String firstXML; // violation, XML abbreviation is not allowed
147  * }
148  * </pre>
149  *
150  * @since 5.8
151  */
152 @StatelessCheck
153 public class AbbreviationAsWordInNameCheck extends AbstractCheck {
154 
155     /**
156      * Warning message key.
157      */
158     public static final String MSG_KEY = "abbreviation.as.word";
159 
160     /**
161      * The default value of "allowedAbbreviationLength" option.
162      */
163     private static final int DEFAULT_ALLOWED_ABBREVIATIONS_LENGTH = 3;
164 
165     /**
166      * Indicate the number of consecutive capital letters allowed in
167      * targeted identifiers (abbreviations in the classes, interfaces, variables
168      * and methods names, ... ).
169      */
170     private int allowedAbbreviationLength =
171             DEFAULT_ALLOWED_ABBREVIATIONS_LENGTH;
172 
173     /**
174      * Specify list of abbreviations that must be skipped for checking. Abbreviations
175      * should be separated by comma.
176      */
177     private Set<String> allowedAbbreviations = new HashSet<>();
178 
179     /** Allow to skip variables with {@code final} modifier. */
180     private boolean ignoreFinal = true;
181 
182     /** Allow to skip variables with {@code static} modifier. */
183     private boolean ignoreStatic = true;
184 
185     /**
186      * Allow to ignore methods tagged with {@code @Override} annotation (that
187      * usually mean inherited name).
188      */
189     private boolean ignoreOverriddenMethods = true;
190 
191     /**
192      * Setter to allow to skip variables with {@code final} modifier.
193      * @param ignoreFinal
194      *        Defines if ignore variables with 'final' modifier or not.
195      */
196     public void setIgnoreFinal(boolean ignoreFinal) {
197         this.ignoreFinal = ignoreFinal;
198     }
199 
200     /**
201      * Setter to allow to skip variables with {@code static} modifier.
202      * @param ignoreStatic
203      *        Defines if ignore variables with 'static' modifier or not.
204      */
205     public void setIgnoreStatic(boolean ignoreStatic) {
206         this.ignoreStatic = ignoreStatic;
207     }
208 
209     /**
210      * Setter to allow to ignore methods tagged with {@code @Override}
211      * annotation (that usually mean inherited name).
212      * @param ignoreOverriddenMethods
213      *        Defines if ignore methods with "@Override" annotation or not.
214      */
215     public void setIgnoreOverriddenMethods(boolean ignoreOverriddenMethods) {
216         this.ignoreOverriddenMethods = ignoreOverriddenMethods;
217     }
218 
219     /**
220      * Setter to indicate the number of consecutive capital letters allowed
221      * in targeted identifiers (abbreviations in the classes, interfaces,
222      * variables and methods names, ... ).
223      * @param allowedAbbreviationLength amount of allowed capital letters in
224      *        abbreviation.
225      */
226     public void setAllowedAbbreviationLength(int allowedAbbreviationLength) {
227         this.allowedAbbreviationLength = allowedAbbreviationLength;
228     }
229 
230     /**
231      * Setter to specify list of abbreviations that must be skipped for checking.
232      * Abbreviations should be separated by comma.
233      * @param allowedAbbreviations an string of abbreviations that must be
234      *        skipped from checking, each abbreviation separated by comma.
235      */
236     public void setAllowedAbbreviations(String... allowedAbbreviations) {
237         if (allowedAbbreviations != null) {
238             this.allowedAbbreviations =
239                 Arrays.stream(allowedAbbreviations).collect(Collectors.toSet());
240         }
241     }
242 
243     @Override
244     public int[] getDefaultTokens() {
245         return new int[] {
246             TokenTypes.CLASS_DEF,
247             TokenTypes.INTERFACE_DEF,
248             TokenTypes.ENUM_DEF,
249             TokenTypes.ANNOTATION_DEF,
250             TokenTypes.ANNOTATION_FIELD_DEF,
251             TokenTypes.PARAMETER_DEF,
252             TokenTypes.VARIABLE_DEF,
253             TokenTypes.METHOD_DEF,
254         };
255     }
256 
257     @Override
258     public int[] getAcceptableTokens() {
259         return new int[] {
260             TokenTypes.CLASS_DEF,
261             TokenTypes.INTERFACE_DEF,
262             TokenTypes.ENUM_DEF,
263             TokenTypes.ANNOTATION_DEF,
264             TokenTypes.ANNOTATION_FIELD_DEF,
265             TokenTypes.PARAMETER_DEF,
266             TokenTypes.VARIABLE_DEF,
267             TokenTypes.METHOD_DEF,
268             TokenTypes.ENUM_CONSTANT_DEF,
269         };
270     }
271 
272     @Override
273     public int[] getRequiredTokens() {
274         return CommonUtil.EMPTY_INT_ARRAY;
275     }
276 
277     @Override
278     public void visitToken(DetailAST ast) {
279         if (!isIgnoreSituation(ast)) {
280             final DetailAST nameAst = ast.findFirstToken(TokenTypes.IDENT);
281             final String typeName = nameAst.getText();
282 
283             final String abbr = getDisallowedAbbreviation(typeName);
284             if (abbr != null) {
285                 log(nameAst.getLineNo(), MSG_KEY, typeName, allowedAbbreviationLength + 1);
286             }
287         }
288     }
289 
290     /**
291      * Checks if it is an ignore situation.
292      * @param ast input DetailAST node.
293      * @return true if it is an ignore situation found for given input DetailAST
294      *         node.
295      * @noinspection SimplifiableIfStatement
296      */
297     private boolean isIgnoreSituation(DetailAST ast) {
298         final DetailAST modifiers = ast.getFirstChild();
299 
300         final boolean result;
301         if (ast.getType() == TokenTypes.VARIABLE_DEF) {
302             if ((ignoreFinal || ignoreStatic)
303                     && isInterfaceDeclaration(ast)) {
304                 // field declarations in interface are static/final
305                 result = true;
306             }
307             else {
308                 result = ignoreFinal
309                           && modifiers.findFirstToken(TokenTypes.FINAL) != null
310                     || ignoreStatic
311                         && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
312             }
313         }
314         else if (ast.getType() == TokenTypes.METHOD_DEF) {
315             result = ignoreOverriddenMethods && hasOverrideAnnotation(modifiers);
316         }
317         else {
318             result = CheckUtil.isReceiverParameter(ast);
319         }
320         return result;
321     }
322 
323     /**
324      * Check that variable definition in interface or @interface definition.
325      * @param variableDefAst variable definition.
326      * @return true if variable definition(variableDefAst) is in interface
327      *     or @interface definition.
328      */
329     private static boolean isInterfaceDeclaration(DetailAST variableDefAst) {
330         boolean result = false;
331         final DetailAST astBlock = variableDefAst.getParent();
332         final DetailAST astParent2 = astBlock.getParent();
333 
334         if (astParent2.getType() == TokenTypes.INTERFACE_DEF
335                 || astParent2.getType() == TokenTypes.ANNOTATION_DEF) {
336             result = true;
337         }
338         return result;
339     }
340 
341     /**
342      * Checks that the method has "@Override" annotation.
343      * @param methodModifiersAST
344      *        A DetailAST nod is related to the given method modifiers
345      *        (MODIFIERS type).
346      * @return true if method has "@Override" annotation.
347      */
348     private static boolean hasOverrideAnnotation(DetailAST methodModifiersAST) {
349         boolean result = false;
350         for (DetailAST child : getChildren(methodModifiersAST)) {
351             final DetailAST annotationIdent = child.findFirstToken(TokenTypes.IDENT);
352 
353             if (annotationIdent != null && "Override".equals(annotationIdent.getText())) {
354                 result = true;
355                 break;
356             }
357         }
358         return result;
359     }
360 
361     /**
362      * Gets the disallowed abbreviation contained in given String.
363      * @param str
364      *        the given String.
365      * @return the disallowed abbreviation contained in given String as a
366      *         separate String.
367      */
368     private String getDisallowedAbbreviation(String str) {
369         int beginIndex = 0;
370         boolean abbrStarted = false;
371         String result = null;
372 
373         for (int index = 0; index < str.length(); index++) {
374             final char symbol = str.charAt(index);
375 
376             if (Character.isUpperCase(symbol)) {
377                 if (!abbrStarted) {
378                     abbrStarted = true;
379                     beginIndex = index;
380                 }
381             }
382             else if (abbrStarted) {
383                 abbrStarted = false;
384 
385                 final int endIndex = index - 1;
386                 result = getAbbreviationIfIllegal(str, beginIndex, endIndex);
387                 if (result != null) {
388                     break;
389                 }
390                 beginIndex = -1;
391             }
392         }
393         // if abbreviation at the end of name (example: scaleX)
394         if (abbrStarted) {
395             final int endIndex = str.length() - 1;
396             result = getAbbreviationIfIllegal(str, beginIndex, endIndex);
397         }
398         return result;
399     }
400 
401     /**
402      * Get Abbreviation if it is illegal, where {@code beginIndex} and {@code endIndex} are
403      * inclusive indexes of a sequence of consecutive upper-case characters.
404      * @param str name
405      * @param beginIndex begin index
406      * @param endIndex end index
407      * @return the abbreviation if it is bigger than required and not in the
408      *         ignore list, otherwise {@code null}
409      */
410     private String getAbbreviationIfIllegal(String str, int beginIndex, int endIndex) {
411         String result = null;
412         final int abbrLength = endIndex - beginIndex;
413         if (abbrLength > allowedAbbreviationLength) {
414             final String abbr = getAbbreviation(str, beginIndex, endIndex);
415             if (!allowedAbbreviations.contains(abbr)) {
416                 result = abbr;
417             }
418         }
419         return result;
420     }
421 
422     /**
423      * Gets the abbreviation, where {@code beginIndex} and {@code endIndex} are
424      * inclusive indexes of a sequence of consecutive upper-case characters.
425      * <p>
426      * The character at {@code endIndex} is only included in the abbreviation if
427      * it is the last character in the string; otherwise it is usually the first
428      * capital in the next word.
429      * </p>
430      * <p>
431      * For example, {@code getAbbreviation("getXMLParser", 3, 6)} returns "XML"
432      * (not "XMLP"), and so does {@code getAbbreviation("parseXML", 5, 7)}.
433      * </p>
434      * @param str name
435      * @param beginIndex begin index
436      * @param endIndex end index
437      * @return the specified abbreviation
438      */
439     private static String getAbbreviation(String str, int beginIndex, int endIndex) {
440         final String result;
441         if (endIndex == str.length() - 1) {
442             result = str.substring(beginIndex);
443         }
444         else {
445             result = str.substring(beginIndex, endIndex);
446         }
447         return result;
448     }
449 
450     /**
451      * Gets all the children which are one level below on the current DetailAST
452      * parent node.
453      * @param node
454      *        Current parent node.
455      * @return The list of children one level below on the current parent node.
456      */
457     private static List<DetailAST> getChildren(final DetailAST node) {
458         final List<DetailAST> result = new LinkedList<>();
459         DetailAST curNode = node.getFirstChild();
460         while (curNode != null) {
461             result.add(curNode);
462             curNode = curNode.getNextSibling();
463         }
464         return result;
465     }
466 
467 }