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