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