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