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 * &lt;module name="AbbreviationAsWordInName" /&gt;
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 * &lt;module name="AbbreviationAsWordInName"&gt;
072 *     &lt;property name="tokens" value="VARIABLE_DEF,CLASS_DEF"/&gt;
073 *     &lt;property name="ignoreStatic" value="false"/&gt;
074 *     &lt;property name="allowedAbbreviationLength" value="1"/&gt;
075 *     &lt;property name="allowedAbbreviations" value="XML,URL"/&gt;
076 * &lt;/module&gt;
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}