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