001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.StatelessCheck;
030import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
031import com.puppycrawl.tools.checkstyle.api.DetailAST;
032import com.puppycrawl.tools.checkstyle.api.TokenTypes;
033import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
034import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
035
036/**
037 * <p>
038 * Validates abbreviations (consecutive capital letters) length in
039 * identifier name, it also allows to enforce camel case naming. Please read more at
040 * <a href="https://checkstyle.org/styleguides/google-java-style-20180523/javaguide.html#s5.3-camel-case">
041 * Google Style Guide</a> to get to know how to avoid long abbreviations in names.
042 * </p>
043 * <p>
044 * {@code allowedAbbreviationLength} specifies how many consecutive capital letters are
045 * allowed in the identifier.
046 * A value of <i>3</i> indicates that up to 4 consecutive capital letters are allowed,
047 * one after the other, before a violation is printed. The identifier 'MyTEST' would be
048 * allowed, but 'MyTESTS' would not be.
049 * A value of <i>0</i> indicates that only 1 consecutive capital letter is allowed. This
050 * is what should be used to enforce strict camel casing. The identifier 'MyTest' would
051 * be allowed, but 'MyTEst' would not be.
052 * </p>
053 * <ul>
054 * <li>
055 * Property {@code allowedAbbreviationLength} - Indicate the number of consecutive capital
056 * letters allowed in targeted identifiers (abbreviations in the classes, interfaces, variables
057 * and methods names, ... ). Default value is {@code 3}.
058 * </li>
059 * <li>
060 * Property {@code allowedAbbreviations} - Specify list of abbreviations that must be skipped for
061 * checking. Abbreviations should be separated by comma. Default value is {@code {}}.
062 * </li>
063 * <li>
064 * Property {@code ignoreFinal} - Allow to skip variables with {@code final} modifier. Default
065 * value is {@code true}.
066 * </li>
067 * <li>
068 * Property {@code ignoreStatic} - Allow to skip variables with {@code static} modifier. Default
069 * value is {@code true}.
070 * </li>
071 * <li>
072 * Property {@code ignoreOverriddenMethods} - Allow to ignore methods tagged with {@code @Override}
073 * annotation (that usually mean inherited name). Default value is {@code true}.
074 * </li>
075 * <li>
076 * Property {@code tokens} - tokens to check Default value is:
077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
078 * CLASS_DEF</a>,
079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
080 * INTERFACE_DEF</a>,
081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
082 * ENUM_DEF</a>,
083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
084 * ANNOTATION_DEF</a>,
085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
086 * ANNOTATION_FIELD_DEF</a>,
087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PARAMETER_DEF">
088 * PARAMETER_DEF</a>,
089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF">
090 * VARIABLE_DEF</a>,
091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
092 * METHOD_DEF</a>.
093 * </li>
094 * </ul>
095 * <p>
096 * Default configuration
097 * </p>
098 * <pre>
099 * &lt;module name="AbbreviationAsWordInName"/&gt;
100 * </pre>
101 * <p>
102 * To configure to check all variables and identifiers
103 * (including ones with the static modifier) and enforce
104 * no abbreviations (essentially camel case) except for
105 * words like 'XML' and 'URL'.
106 * </p>
107 * <p>Configuration:</p>
108 * <pre>
109 * &lt;module name="AbbreviationAsWordInName"&gt;
110 *   &lt;property name="tokens" value="VARIABLE_DEF,CLASS_DEF"/&gt;
111 *   &lt;property name="ignoreStatic" value="false"/&gt;
112 *   &lt;property name="allowedAbbreviationLength" value="0"/&gt;
113 *   &lt;property name="allowedAbbreviations" value="XML,URL"/&gt;
114 * &lt;/module&gt;
115 * </pre>
116 * <p>Example:</p>
117 * <pre>
118 * public class MyClass { // OK
119 *   int firstNum; // OK
120 *   int secondNUM; // violation, it allowed only 1 consecutive capital letter
121 *   static int thirdNum; // OK, the static modifier would be checked
122 *   static int fourthNUm; // violation, the static modifier would be checked,
123 *                         // and only 1 consecutive capital letter is allowed
124 *   String firstXML; // OK, XML abbreviation is allowed
125 *   String firstURL; // OK, URL abbreviation is allowed
126 * }
127 * </pre>
128 * <p>
129 * To configure to check variables, excluding fields with
130 * the static modifier, and allow abbreviations up to 2
131 * consecutive capital letters ignoring the longer word 'CSV'.
132 * </p>
133 * <p>Configuration:</p>
134 * <pre>
135 * &lt;module name="AbbreviationAsWordInName"&gt;
136 *   &lt;property name="tokens" value="VARIABLE_DEF"/&gt;
137 *   &lt;property name="ignoreStatic" value="true"/&gt;
138 *   &lt;property name="allowedAbbreviationLength" value="1"/&gt;
139 *   &lt;property name="allowedAbbreviations" value="CSV"/&gt;
140 * &lt;/module&gt;
141 * </pre>
142 * <p>Example:</p>
143 * <pre>
144 * public class MyClass { // OK, ignore checking the class name
145 *   int firstNum; // OK, abbreviation "N" is of allowed length 1
146 *   int secondNUm; // OK
147 *   int secondMYNum; // violation, found "MYN" but only
148 *                    // 2 consecutive capital letters are allowed
149 *   int thirdNUM; // violation, found "NUM" but it is allowed
150 *                 // only 2 consecutive capital letters
151 *   static int fourthNUM; // OK, variables with static modifier
152 *                         // would be ignored
153 *   String firstCSV; // OK, CSV abbreviation is allowed
154 *   String firstXML; // violation, XML abbreviation is not allowed
155 * }
156 * </pre>
157 *
158 * @since 5.8
159 */
160@StatelessCheck
161public class AbbreviationAsWordInNameCheck extends AbstractCheck {
162
163    /**
164     * Warning message key.
165     */
166    public static final String MSG_KEY = "abbreviation.as.word";
167
168    /**
169     * The default value of "allowedAbbreviationLength" option.
170     */
171    private static final int DEFAULT_ALLOWED_ABBREVIATIONS_LENGTH = 3;
172
173    /**
174     * Indicate the number of consecutive capital letters allowed in
175     * targeted identifiers (abbreviations in the classes, interfaces, variables
176     * and methods names, ... ).
177     */
178    private int allowedAbbreviationLength =
179            DEFAULT_ALLOWED_ABBREVIATIONS_LENGTH;
180
181    /**
182     * Specify list of abbreviations that must be skipped for checking. Abbreviations
183     * should be separated by comma.
184     */
185    private Set<String> allowedAbbreviations = new HashSet<>();
186
187    /** Allow to skip variables with {@code final} modifier. */
188    private boolean ignoreFinal = true;
189
190    /** Allow to skip variables with {@code static} modifier. */
191    private boolean ignoreStatic = true;
192
193    /**
194     * Allow to ignore methods tagged with {@code @Override} annotation (that
195     * usually mean inherited name).
196     */
197    private boolean ignoreOverriddenMethods = true;
198
199    /**
200     * Setter to allow to skip variables with {@code final} modifier.
201     * @param ignoreFinal
202     *        Defines if ignore variables with 'final' modifier or not.
203     */
204    public void setIgnoreFinal(boolean ignoreFinal) {
205        this.ignoreFinal = ignoreFinal;
206    }
207
208    /**
209     * Setter to allow to skip variables with {@code static} modifier.
210     * @param ignoreStatic
211     *        Defines if ignore variables with 'static' modifier or not.
212     */
213    public void setIgnoreStatic(boolean ignoreStatic) {
214        this.ignoreStatic = ignoreStatic;
215    }
216
217    /**
218     * Setter to allow to ignore methods tagged with {@code @Override}
219     * annotation (that usually mean inherited name).
220     * @param ignoreOverriddenMethods
221     *        Defines if ignore methods with "@Override" annotation or not.
222     */
223    public void setIgnoreOverriddenMethods(boolean ignoreOverriddenMethods) {
224        this.ignoreOverriddenMethods = ignoreOverriddenMethods;
225    }
226
227    /**
228     * Setter to indicate the number of consecutive capital letters allowed
229     * in targeted identifiers (abbreviations in the classes, interfaces,
230     * variables and methods names, ... ).
231     * @param allowedAbbreviationLength amount of allowed capital letters in
232     *        abbreviation.
233     */
234    public void setAllowedAbbreviationLength(int allowedAbbreviationLength) {
235        this.allowedAbbreviationLength = allowedAbbreviationLength;
236    }
237
238    /**
239     * Setter to specify list of abbreviations that must be skipped for checking.
240     * Abbreviations should be separated by comma.
241     * @param allowedAbbreviations an string of abbreviations that must be
242     *        skipped from checking, each abbreviation separated by comma.
243     */
244    public void setAllowedAbbreviations(String... allowedAbbreviations) {
245        if (allowedAbbreviations != null) {
246            this.allowedAbbreviations =
247                Arrays.stream(allowedAbbreviations).collect(Collectors.toSet());
248        }
249    }
250
251    @Override
252    public int[] getDefaultTokens() {
253        return new int[] {
254            TokenTypes.CLASS_DEF,
255            TokenTypes.INTERFACE_DEF,
256            TokenTypes.ENUM_DEF,
257            TokenTypes.ANNOTATION_DEF,
258            TokenTypes.ANNOTATION_FIELD_DEF,
259            TokenTypes.PARAMETER_DEF,
260            TokenTypes.VARIABLE_DEF,
261            TokenTypes.METHOD_DEF,
262        };
263    }
264
265    @Override
266    public int[] getAcceptableTokens() {
267        return new int[] {
268            TokenTypes.CLASS_DEF,
269            TokenTypes.INTERFACE_DEF,
270            TokenTypes.ENUM_DEF,
271            TokenTypes.ANNOTATION_DEF,
272            TokenTypes.ANNOTATION_FIELD_DEF,
273            TokenTypes.PARAMETER_DEF,
274            TokenTypes.VARIABLE_DEF,
275            TokenTypes.METHOD_DEF,
276            TokenTypes.ENUM_CONSTANT_DEF,
277        };
278    }
279
280    @Override
281    public int[] getRequiredTokens() {
282        return CommonUtil.EMPTY_INT_ARRAY;
283    }
284
285    @Override
286    public void visitToken(DetailAST ast) {
287        if (!isIgnoreSituation(ast)) {
288            final DetailAST nameAst = ast.findFirstToken(TokenTypes.IDENT);
289            final String typeName = nameAst.getText();
290
291            final String abbr = getDisallowedAbbreviation(typeName);
292            if (abbr != null) {
293                log(nameAst, MSG_KEY, typeName, allowedAbbreviationLength + 1);
294            }
295        }
296    }
297
298    /**
299     * Checks if it is an ignore situation.
300     * @param ast input DetailAST node.
301     * @return true if it is an ignore situation found for given input DetailAST
302     *         node.
303     * @noinspection SimplifiableIfStatement
304     */
305    private boolean isIgnoreSituation(DetailAST ast) {
306        final DetailAST modifiers = ast.getFirstChild();
307
308        final boolean result;
309        if (ast.getType() == TokenTypes.VARIABLE_DEF) {
310            if ((ignoreFinal || ignoreStatic)
311                    && isInterfaceDeclaration(ast)) {
312                // field declarations in interface are static/final
313                result = true;
314            }
315            else {
316                result = ignoreFinal
317                          && modifiers.findFirstToken(TokenTypes.FINAL) != null
318                    || ignoreStatic
319                        && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
320            }
321        }
322        else if (ast.getType() == TokenTypes.METHOD_DEF) {
323            result = ignoreOverriddenMethods && hasOverrideAnnotation(modifiers);
324        }
325        else {
326            result = CheckUtil.isReceiverParameter(ast);
327        }
328        return result;
329    }
330
331    /**
332     * Check that variable definition in interface or @interface definition.
333     * @param variableDefAst variable definition.
334     * @return true if variable definition(variableDefAst) is in interface
335     *     or @interface definition.
336     */
337    private static boolean isInterfaceDeclaration(DetailAST variableDefAst) {
338        boolean result = false;
339        final DetailAST astBlock = variableDefAst.getParent();
340        final DetailAST astParent2 = astBlock.getParent();
341
342        if (astParent2.getType() == TokenTypes.INTERFACE_DEF
343                || astParent2.getType() == TokenTypes.ANNOTATION_DEF) {
344            result = true;
345        }
346        return result;
347    }
348
349    /**
350     * Checks that the method has "@Override" annotation.
351     * @param methodModifiersAST
352     *        A DetailAST nod is related to the given method modifiers
353     *        (MODIFIERS type).
354     * @return true if method has "@Override" annotation.
355     */
356    private static boolean hasOverrideAnnotation(DetailAST methodModifiersAST) {
357        boolean result = false;
358        for (DetailAST child : getChildren(methodModifiersAST)) {
359            final DetailAST annotationIdent = child.findFirstToken(TokenTypes.IDENT);
360
361            if (annotationIdent != null && "Override".equals(annotationIdent.getText())) {
362                result = true;
363                break;
364            }
365        }
366        return result;
367    }
368
369    /**
370     * Gets the disallowed abbreviation contained in given String.
371     * @param str
372     *        the given String.
373     * @return the disallowed abbreviation contained in given String as a
374     *         separate String.
375     */
376    private String getDisallowedAbbreviation(String str) {
377        int beginIndex = 0;
378        boolean abbrStarted = false;
379        String result = null;
380
381        for (int index = 0; index < str.length(); index++) {
382            final char symbol = str.charAt(index);
383
384            if (Character.isUpperCase(symbol)) {
385                if (!abbrStarted) {
386                    abbrStarted = true;
387                    beginIndex = index;
388                }
389            }
390            else if (abbrStarted) {
391                abbrStarted = false;
392
393                final int endIndex = index - 1;
394                result = getAbbreviationIfIllegal(str, beginIndex, endIndex);
395                if (result != null) {
396                    break;
397                }
398                beginIndex = -1;
399            }
400        }
401        // if abbreviation at the end of name (example: scaleX)
402        if (abbrStarted) {
403            final int endIndex = str.length() - 1;
404            result = getAbbreviationIfIllegal(str, beginIndex, endIndex);
405        }
406        return result;
407    }
408
409    /**
410     * Get Abbreviation if it is illegal, where {@code beginIndex} and {@code endIndex} are
411     * inclusive indexes of a sequence of consecutive upper-case characters.
412     * @param str name
413     * @param beginIndex begin index
414     * @param endIndex end index
415     * @return the abbreviation if it is bigger than required and not in the
416     *         ignore list, otherwise {@code null}
417     */
418    private String getAbbreviationIfIllegal(String str, int beginIndex, int endIndex) {
419        String result = null;
420        final int abbrLength = endIndex - beginIndex;
421        if (abbrLength > allowedAbbreviationLength) {
422            final String abbr = getAbbreviation(str, beginIndex, endIndex);
423            if (!allowedAbbreviations.contains(abbr)) {
424                result = abbr;
425            }
426        }
427        return result;
428    }
429
430    /**
431     * Gets the abbreviation, where {@code beginIndex} and {@code endIndex} are
432     * inclusive indexes of a sequence of consecutive upper-case characters.
433     * <p>
434     * The character at {@code endIndex} is only included in the abbreviation if
435     * it is the last character in the string; otherwise it is usually the first
436     * capital in the next word.
437     * </p>
438     * <p>
439     * For example, {@code getAbbreviation("getXMLParser", 3, 6)} returns "XML"
440     * (not "XMLP"), and so does {@code getAbbreviation("parseXML", 5, 7)}.
441     * </p>
442     * @param str name
443     * @param beginIndex begin index
444     * @param endIndex end index
445     * @return the specified abbreviation
446     */
447    private static String getAbbreviation(String str, int beginIndex, int endIndex) {
448        final String result;
449        if (endIndex == str.length() - 1) {
450            result = str.substring(beginIndex);
451        }
452        else {
453            result = str.substring(beginIndex, endIndex);
454        }
455        return result;
456    }
457
458    /**
459     * Gets all the children which are one level below on the current DetailAST
460     * parent node.
461     * @param node
462     *        Current parent node.
463     * @return The list of children one level below on the current parent node.
464     */
465    private static List<DetailAST> getChildren(final DetailAST node) {
466        final List<DetailAST> result = new LinkedList<>();
467        DetailAST curNode = node.getFirstChild();
468        while (curNode != null) {
469            result.add(curNode);
470            curNode = curNode.getNextSibling();
471        }
472        return result;
473    }
474
475}