001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.coding;
021
022import java.util.Arrays;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
030import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
031import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
032
033/**
034 * <p>
035 * Checks that there are no <a href="https://en.wikipedia.org/wiki/Magic_number_%28programming%29">
036 * &quot;magic numbers&quot;</a> where a magic
037 * number is a numeric literal that is not defined as a constant.
038 * By default, -1, 0, 1, and 2 are not considered to be magic numbers.
039 * </p>
040 *
041 * <p>Constant definition is any variable/field that has 'final' modifier.
042 * It is fine to have one constant defining multiple numeric literals within one expression:
043 * <pre>
044 * {@code static final int SECONDS_PER_DAY = 24 * 60 * 60;
045 * static final double SPECIAL_RATIO = 4.0 / 3.0;
046 * static final double SPECIAL_SUM = 1 + Math.E;
047 * static final double SPECIAL_DIFFERENCE = 4 - Math.PI;
048 * static final Border STANDARD_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3);
049 * static final Integer ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE = new Integer(42);}
050 * </pre>
051 *
052 * <p>Check have following options:
053 * ignoreHashCodeMethod - ignore magic numbers in hashCode methods;
054 * ignoreAnnotation - ignore magic numbers in annotation declarations;
055 * ignoreFieldDeclaration - ignore magic numbers in field declarations.
056 * <p>
057 * To configure the check with default configuration:
058 * </p>
059 * <pre>
060 * &lt;module name=&quot;MagicNumber&quot;/&gt;
061 * </pre>
062 * <p>
063 * results is following violations:
064 * </p>
065 * <pre>
066 * {@code
067 *   {@literal @}MyAnnotation(6) // violation
068 *   class MyClass {
069 *       private field = 7; // violation
070 *
071 *       void foo() {
072 *          int i = i + 1; // no violation
073 *          int j = j + 8; // violation
074 *       }
075 *   }
076 * }
077 * </pre>
078 * <p>
079 * To configure the check so that it checks floating-point numbers
080 * that are not 0, 0.5, or 1:
081 * </p>
082 * <pre>
083 *   &lt;module name=&quot;MagicNumber&quot;&gt;
084 *       &lt;property name=&quot;tokens&quot; value=&quot;NUM_DOUBLE, NUM_FLOAT&quot;/&gt;
085 *       &lt;property name=&quot;ignoreNumbers&quot; value=&quot;0, 0.5, 1&quot;/&gt;
086 *       &lt;property name=&quot;ignoreFieldDeclaration&quot; value=&quot;true&quot;/&gt;
087 *       &lt;property name=&quot;ignoreAnnotation&quot; value=&quot;true&quot;/&gt;
088 *   &lt;/module&gt;
089 * </pre>
090 * <p>
091 * results is following violations:
092 * </p>
093 * <pre>
094 * {@code
095 *   {@literal @}MyAnnotation(6) // no violation
096 *   class MyClass {
097 *       private field = 7; // no violation
098 *
099 *       void foo() {
100 *          int i = i + 1; // no violation
101 *          int j = j + (int)0.5; // no violation
102 *       }
103 *   }
104 * }
105 * </pre>
106 * <p>
107 * Config example of constantWaiverParentToken option:
108 * </p>
109 * <pre>
110 *   &lt;module name=&quot;MagicNumber&quot;&gt;
111 *       &lt;property name=&quot;constantWaiverParentToken&quot; value=&quot;ASSIGN,ARRAY_INIT,EXPR,
112 *       UNARY_PLUS, UNARY_MINUS, TYPECAST, ELIST, DIV, PLUS &quot;/&gt;
113 *   &lt;/module&gt;
114 * </pre>
115 * <p>
116 * result is following violation:
117 * </p>
118 * <pre>
119 * {@code
120 * class TestMethodCall {
121 *     public void method2() {
122 *         final TestMethodCall dummyObject = new TestMethodCall(62);    //violation
123 *         final int a = 3;        // ok as waiver is ASSIGN
124 *         final int [] b = {4, 5} // ok as waiver is ARRAY_INIT
125 *         final int c = -3;       // ok as waiver is UNARY_MINUS
126 *         final int d = +4;       // ok as waiver is UNARY_PLUS
127 *         final int e = method(1, 2) // ELIST is there but violation due to METHOD_CALL
128 *         final int x = 3 * 4;    // violation
129 *         final int y = 3 / 4;    // ok as waiver is DIV
130 *         final int z = 3 + 4;    // ok as waiver is PLUS
131 *         final int w = 3 - 4;    // violation
132 *         final int x = (int)(3.4);    //ok as waiver is TYPECAST
133 *     }
134 * }
135 * }
136 * </pre>
137 * @author Rick Giles
138 * @author Lars Kühne
139 * @author Daniel Solano Gómez
140 */
141@StatelessCheck
142public class MagicNumberCheck extends AbstractCheck {
143
144    /**
145     * A key is pointing to the warning message text in "messages.properties"
146     * file.
147     */
148    public static final String MSG_KEY = "magic.number";
149
150    /**
151     * The token types that are allowed in the AST path from the
152     * number literal to the enclosing constant definition.
153     */
154    private int[] constantWaiverParentToken = {
155        TokenTypes.ASSIGN,
156        TokenTypes.ARRAY_INIT,
157        TokenTypes.EXPR,
158        TokenTypes.UNARY_PLUS,
159        TokenTypes.UNARY_MINUS,
160        TokenTypes.TYPECAST,
161        TokenTypes.ELIST,
162        TokenTypes.LITERAL_NEW,
163        TokenTypes.METHOD_CALL,
164        TokenTypes.STAR,
165        TokenTypes.DIV,
166        TokenTypes.PLUS,
167        TokenTypes.MINUS,
168    };
169
170    /** The numbers to ignore in the check, sorted. */
171    private double[] ignoreNumbers = {-1, 0, 1, 2};
172
173    /** Whether to ignore magic numbers in a hash code method. */
174    private boolean ignoreHashCodeMethod;
175
176    /** Whether to ignore magic numbers in annotation. */
177    private boolean ignoreAnnotation;
178
179    /** Whether to ignore magic numbers in field declaration. */
180    private boolean ignoreFieldDeclaration;
181
182    /**
183     * Constructor for MagicNumber Check.
184     * Sort the allowedTokensBetweenMagicNumberAndConstDef array for binary search.
185     */
186    public MagicNumberCheck() {
187        Arrays.sort(constantWaiverParentToken);
188    }
189
190    @Override
191    public int[] getDefaultTokens() {
192        return getAcceptableTokens();
193    }
194
195    @Override
196    public int[] getAcceptableTokens() {
197        return new int[] {
198            TokenTypes.NUM_DOUBLE,
199            TokenTypes.NUM_FLOAT,
200            TokenTypes.NUM_INT,
201            TokenTypes.NUM_LONG,
202        };
203    }
204
205    @Override
206    public int[] getRequiredTokens() {
207        return CommonUtils.EMPTY_INT_ARRAY;
208    }
209
210    @Override
211    public void visitToken(DetailAST ast) {
212        if ((!ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION))
213                && !isInIgnoreList(ast)
214                && (!ignoreHashCodeMethod || !isInHashCodeMethod(ast))) {
215            final DetailAST constantDefAST = findContainingConstantDef(ast);
216
217            if (constantDefAST == null) {
218                if (!ignoreFieldDeclaration || !isFieldDeclaration(ast)) {
219                    reportMagicNumber(ast);
220                }
221            }
222            else {
223                final boolean found = isMagicNumberExists(ast, constantDefAST);
224                if (found) {
225                    reportMagicNumber(ast);
226                }
227            }
228        }
229    }
230
231    /**
232     * Is magic number some where at ast tree.
233     * @param ast ast token
234     * @param constantDefAST constant ast
235     * @return true if magic number is present
236     */
237    private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
238        boolean found = false;
239        DetailAST astNode = ast.getParent();
240        while (astNode != constantDefAST) {
241            final int type = astNode.getType();
242            if (Arrays.binarySearch(constantWaiverParentToken, type) < 0) {
243                found = true;
244                break;
245            }
246            astNode = astNode.getParent();
247        }
248        return found;
249    }
250
251    /**
252     * Finds the constant definition that contains aAST.
253     * @param ast the AST
254     * @return the constant def or null if ast is not contained in a constant definition.
255     */
256    private static DetailAST findContainingConstantDef(DetailAST ast) {
257        DetailAST varDefAST = ast;
258        while (varDefAST != null
259                && varDefAST.getType() != TokenTypes.VARIABLE_DEF
260                && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
261            varDefAST = varDefAST.getParent();
262        }
263        DetailAST constantDef = null;
264
265        // no containing variable definition?
266        if (varDefAST != null) {
267            // implicit constant?
268            if (ScopeUtils.isInInterfaceOrAnnotationBlock(varDefAST)
269                    || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
270                constantDef = varDefAST;
271            }
272            else {
273                // explicit constant
274                final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
275
276                if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
277                    constantDef = varDefAST;
278                }
279            }
280        }
281        return constantDef;
282    }
283
284    /**
285     * Reports aAST as a magic number, includes unary operators as needed.
286     * @param ast the AST node that contains the number to report
287     */
288    private void reportMagicNumber(DetailAST ast) {
289        String text = ast.getText();
290        final DetailAST parent = ast.getParent();
291        DetailAST reportAST = ast;
292        if (parent.getType() == TokenTypes.UNARY_MINUS) {
293            reportAST = parent;
294            text = "-" + text;
295        }
296        else if (parent.getType() == TokenTypes.UNARY_PLUS) {
297            reportAST = parent;
298            text = "+" + text;
299        }
300        log(reportAST.getLineNo(),
301                reportAST.getColumnNo(),
302                MSG_KEY,
303                text);
304    }
305
306    /**
307     * Determines whether or not the given AST is in a valid hash code method.
308     * A valid hash code method is considered to be a method of the signature
309     * {@code public int hashCode()}.
310     *
311     * @param ast the AST from which to search for an enclosing hash code
312     *     method definition
313     *
314     * @return {@code true} if {@code ast} is in the scope of a valid hash code method.
315     */
316    private static boolean isInHashCodeMethod(DetailAST ast) {
317        boolean inHashCodeMethod = false;
318
319        // if not in a code block, can't be in hashCode()
320        if (ScopeUtils.isInCodeBlock(ast)) {
321            // find the method definition AST
322            DetailAST methodDefAST = ast.getParent();
323            while (methodDefAST != null
324                    && methodDefAST.getType() != TokenTypes.METHOD_DEF) {
325                methodDefAST = methodDefAST.getParent();
326            }
327
328            if (methodDefAST != null) {
329                // Check for 'hashCode' name.
330                final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
331
332                if ("hashCode".equals(identAST.getText())) {
333                    // Check for no arguments.
334                    final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
335                    // we are in a 'public int hashCode()' method! The compiler will ensure
336                    // the method returns an 'int' and is public.
337                    inHashCodeMethod = paramAST.getChildCount() == 0;
338                }
339            }
340        }
341        return inHashCodeMethod;
342    }
343
344    /**
345     * Decides whether the number of an AST is in the ignore list of this
346     * check.
347     * @param ast the AST to check
348     * @return true if the number of ast is in the ignore list of this check.
349     */
350    private boolean isInIgnoreList(DetailAST ast) {
351        double value = CheckUtils.parseDouble(ast.getText(), ast.getType());
352        final DetailAST parent = ast.getParent();
353        if (parent.getType() == TokenTypes.UNARY_MINUS) {
354            value = -1 * value;
355        }
356        return Arrays.binarySearch(ignoreNumbers, value) >= 0;
357    }
358
359    /**
360     * Determines whether or not the given AST is field declaration.
361     *
362     * @param ast AST from which to search for an enclosing field declaration
363     *
364     * @return {@code true} if {@code ast} is in the scope of field declaration
365     */
366    private static boolean isFieldDeclaration(DetailAST ast) {
367        DetailAST varDefAST = ast;
368        while (varDefAST != null
369                && varDefAST.getType() != TokenTypes.VARIABLE_DEF) {
370            varDefAST = varDefAST.getParent();
371        }
372
373        // contains variable declaration
374        // and it is directly inside class declaration
375        return varDefAST != null
376                && varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF;
377    }
378
379    /**
380     * Sets the tokens which are allowed between Magic Number and defined Object.
381     * @param tokens The string representation of the tokens interested in
382     */
383    public void setConstantWaiverParentToken(String... tokens) {
384        constantWaiverParentToken = new int[tokens.length];
385        for (int i = 0; i < tokens.length; i++) {
386            constantWaiverParentToken[i] = TokenUtils.getTokenId(tokens[i]);
387        }
388        Arrays.sort(constantWaiverParentToken);
389    }
390
391    /**
392     * Sets the numbers to ignore in the check.
393     * BeanUtils converts numeric token list to double array automatically.
394     * @param list list of numbers to ignore.
395     */
396    public void setIgnoreNumbers(double... list) {
397        if (list.length == 0) {
398            ignoreNumbers = CommonUtils.EMPTY_DOUBLE_ARRAY;
399        }
400        else {
401            ignoreNumbers = new double[list.length];
402            System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
403            Arrays.sort(ignoreNumbers);
404        }
405    }
406
407    /**
408     * Set whether to ignore hashCode methods.
409     * @param ignoreHashCodeMethod decide whether to ignore
410     *     hash code methods
411     */
412    public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
413        this.ignoreHashCodeMethod = ignoreHashCodeMethod;
414    }
415
416    /**
417     * Set whether to ignore Annotations.
418     * @param ignoreAnnotation decide whether to ignore annotations
419     */
420    public void setIgnoreAnnotation(boolean ignoreAnnotation) {
421        this.ignoreAnnotation = ignoreAnnotation;
422    }
423
424    /**
425     * Set whether to ignore magic numbers in field declaration.
426     * @param ignoreFieldDeclaration decide whether to ignore magic numbers
427     *     in field declaration
428     */
429    public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
430        this.ignoreFieldDeclaration = ignoreFieldDeclaration;
431    }
432
433    /**
434     * Determines if the given AST node has a parent node with given token type code.
435     *
436     * @param ast the AST from which to search for annotations
437     * @param type the type code of parent token
438     *
439     * @return {@code true} if the AST node has a parent with given token type.
440     */
441    private static boolean isChildOf(DetailAST ast, int type) {
442        boolean result = false;
443        DetailAST node = ast;
444        do {
445            if (node.getType() == type) {
446                result = true;
447                break;
448            }
449            node = node.getParent();
450        } while (node != null);
451
452        return result;
453    }
454
455}