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