001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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 * {@code "magic number"}</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 * {@snippet lang="text" :
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 * }
056 *
057 * @since 3.1
058 */
059@StatelessCheck
060public class MagicNumberCheck extends AbstractCheck {
061
062    /**
063     * A key is pointing to the warning message text in "messages.properties" file.
064     */
065    public static final String MSG_KEY = "magic.number";
066
067    /**
068     * Specify tokens that are allowed in the AST path from the
069     * number literal to the enclosing constant definition.
070     */
071    @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
072    private BitSet constantWaiverParentToken = TokenUtil.asBitSet(
073        TokenTypes.ASSIGN,
074        TokenTypes.ARRAY_INIT,
075        TokenTypes.EXPR,
076        TokenTypes.UNARY_PLUS,
077        TokenTypes.UNARY_MINUS,
078        TokenTypes.TYPECAST,
079        TokenTypes.ELIST,
080        TokenTypes.LITERAL_NEW,
081        TokenTypes.METHOD_CALL,
082        TokenTypes.STAR,
083        TokenTypes.DIV,
084        TokenTypes.PLUS,
085        TokenTypes.MINUS,
086        TokenTypes.QUESTION,
087        TokenTypes.COLON,
088        TokenTypes.EQUAL,
089        TokenTypes.NOT_EQUAL,
090        TokenTypes.MOD,
091        TokenTypes.SR,
092        TokenTypes.BSR,
093        TokenTypes.GE,
094        TokenTypes.GT,
095        TokenTypes.SL,
096        TokenTypes.LE,
097        TokenTypes.LT,
098        TokenTypes.BXOR,
099        TokenTypes.BOR,
100        TokenTypes.BNOT,
101        TokenTypes.BAND
102    );
103
104    /** Specify non-magic numbers. */
105    private double[] ignoreNumbers = {-1, 0, 1, 2};
106
107    /** Ignore magic numbers in hashCode methods. */
108    private boolean ignoreHashCodeMethod;
109
110    /** Ignore magic numbers in annotation declarations. */
111    private boolean ignoreAnnotation;
112
113    /** Ignore magic numbers in field declarations. */
114    private boolean ignoreFieldDeclaration;
115
116    /** Ignore magic numbers in annotation elements defaults. */
117    private boolean ignoreAnnotationElementDefaults = true;
118
119    /**
120     * Creates a new {@code MagicNumberCheck} instance.
121     */
122    public MagicNumberCheck() {
123        // no code by default
124    }
125
126    @Override
127    public int[] getDefaultTokens() {
128        return getAcceptableTokens();
129    }
130
131    @Override
132    public int[] getAcceptableTokens() {
133        return new int[] {
134            TokenTypes.NUM_DOUBLE,
135            TokenTypes.NUM_FLOAT,
136            TokenTypes.NUM_INT,
137            TokenTypes.NUM_LONG,
138        };
139    }
140
141    @Override
142    public int[] getRequiredTokens() {
143        return CommonUtil.EMPTY_INT_ARRAY;
144    }
145
146    @Override
147    public void visitToken(DetailAST ast) {
148        if (shouldTestAnnotationArgs(ast)
149                && shouldTestAnnotationDefaults(ast)
150                && !isInIgnoreList(ast)
151                && shouldCheckHashCodeMethod(ast)
152                && shouldCheckFieldDeclaration(ast)) {
153            final DetailAST constantDefAST = findContainingConstantDef(ast);
154            if (isMagicNumberExists(ast, constantDefAST)) {
155                reportMagicNumber(ast);
156            }
157        }
158    }
159
160    /**
161     * Checks if ast is annotation argument and should be checked.
162     *
163     * @param ast token to check
164     * @return true if element is skipped, false otherwise
165     */
166    private boolean shouldTestAnnotationArgs(DetailAST ast) {
167        return !ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION);
168    }
169
170    /**
171     * Checks if ast is annotation element default value and should be checked.
172     *
173     * @param ast token to check
174     * @return true if element is skipped, false otherwise
175     */
176    private boolean shouldTestAnnotationDefaults(DetailAST ast) {
177        return !ignoreAnnotationElementDefaults || !isChildOf(ast, TokenTypes.LITERAL_DEFAULT);
178    }
179
180    /**
181     * Checks if the given AST node is a HashCode Method and should be checked.
182     *
183     * @param ast the AST node to check
184     * @return true if element should be checked, false otherwise
185     */
186    private boolean shouldCheckHashCodeMethod(DetailAST ast) {
187        return !ignoreHashCodeMethod || !isInHashCodeMethod(ast);
188    }
189
190    /**
191     * Checks if the given AST node is a field declaration and should be checked.
192     *
193     * @param ast the AST node to check
194     * @return true if element should be checked, false otherwise
195     */
196    private boolean shouldCheckFieldDeclaration(DetailAST ast) {
197        return !ignoreFieldDeclaration || !isFieldDeclaration(ast);
198    }
199
200    /**
201     * Is magic number somewhere at ast tree.
202     *
203     * @param ast ast token
204     * @param constantDefAST constant ast
205     * @return true if magic number is present
206     */
207    private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
208        boolean found = false;
209        DetailAST astNode = ast.getParent();
210        while (astNode != constantDefAST) {
211            final int type = astNode.getType();
212
213            if (!constantWaiverParentToken.get(type)) {
214                found = true;
215                break;
216            }
217
218            astNode = astNode.getParent();
219        }
220        return found;
221    }
222
223    /**
224     * Finds the constant definition that contains aAST.
225     *
226     * @param ast the AST
227     * @return the constant def or null if ast is not contained in a constant definition.
228     */
229    private static DetailAST findContainingConstantDef(DetailAST ast) {
230        DetailAST varDefAST = ast;
231        while (varDefAST != null
232                && varDefAST.getType() != TokenTypes.VARIABLE_DEF
233                && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
234            varDefAST = varDefAST.getParent();
235        }
236        DetailAST constantDef = null;
237
238        // no containing variable definition?
239        if (varDefAST != null) {
240            // implicit constant?
241            if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST)
242                    || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
243                constantDef = varDefAST;
244            }
245            else {
246                // explicit constant
247                final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
248
249                if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
250                    constantDef = varDefAST;
251                }
252            }
253        }
254        return constantDef;
255    }
256
257    /**
258     * Reports aAST as a magic number, includes unary operators as needed.
259     *
260     * @param ast the AST node that contains the number to report
261     */
262    private void reportMagicNumber(DetailAST ast) {
263        String text = ast.getText();
264        final DetailAST parent = ast.getParent();
265        DetailAST reportAST = ast;
266        if (parent.getType() == TokenTypes.UNARY_MINUS) {
267            reportAST = parent;
268            text = "-" + text;
269        }
270        else if (parent.getType() == TokenTypes.UNARY_PLUS) {
271            reportAST = parent;
272            text = "+" + text;
273        }
274        log(reportAST,
275                MSG_KEY,
276                text);
277    }
278
279    /**
280     * Determines whether or not the given AST is in a valid hash code method.
281     * A valid hash code method is considered to be a method of the signature
282     * {@code public int hashCode()}.
283     *
284     * @param ast the AST from which to search for an enclosing hash code method definition
285     *
286     * @return {@code true} if {@code ast} is in the scope of a valid hash code method.
287     */
288    private static boolean isInHashCodeMethod(DetailAST ast) {
289        // find the method definition AST
290        DetailAST currentAST = ast;
291        while (currentAST != null
292                && currentAST.getType() != TokenTypes.METHOD_DEF) {
293            currentAST = currentAST.getParent();
294        }
295        final DetailAST methodDefAST = currentAST;
296        boolean inHashCodeMethod = false;
297
298        if (methodDefAST != null) {
299            // Check for 'hashCode' name.
300            final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
301
302            if ("hashCode".equals(identAST.getText())) {
303                // Check for no arguments.
304                final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
305                // we are in a 'public int hashCode()' method! The compiler will ensure
306                // the method returns an 'int' and is public.
307                inHashCodeMethod = !paramAST.hasChildren();
308            }
309        }
310        return inHashCodeMethod;
311    }
312
313    /**
314     * Decides whether the number of an AST is in the ignore list of this check.
315     *
316     * @param ast the AST to check
317     * @return true if the number of ast is in the ignore list of this check.
318     */
319    private boolean isInIgnoreList(DetailAST ast) {
320        double value = CheckUtil.parseDouble(ast.getText(), ast.getType());
321        final DetailAST parent = ast.getParent();
322        if (parent.getType() == TokenTypes.UNARY_MINUS) {
323            value = -1 * value;
324        }
325        return Arrays.binarySearch(ignoreNumbers, value) >= 0;
326    }
327
328    /**
329     * Determines whether or not the given AST is field declaration.
330     *
331     * @param ast AST from which to search for an enclosing field declaration
332     *
333     * @return {@code true} if {@code ast} is in the scope of field declaration
334     */
335    private static boolean isFieldDeclaration(DetailAST ast) {
336        DetailAST varDefAST = null;
337        DetailAST node = ast;
338        while (node != null && node.getType() != TokenTypes.OBJBLOCK) {
339            if (node.getType() == TokenTypes.VARIABLE_DEF) {
340                varDefAST = node;
341                break;
342            }
343            node = node.getParent();
344        }
345
346        boolean result = false;
347
348        if (varDefAST != null) {
349            final DetailAST parent = varDefAST.getParent();
350
351            if (parent.getType() == TokenTypes.COMPACT_COMPILATION_UNIT) {
352                result = true;
353            }
354            else {
355                final DetailAST grandParent = parent.getParent();
356
357                result = grandParent.getType() == TokenTypes.CLASS_DEF
358                    || grandParent.getType() == TokenTypes.RECORD_DEF
359                    || grandParent.getType() == TokenTypes.LITERAL_NEW;
360            }
361        }
362
363        return result;
364    }
365
366    /**
367     * Setter to specify tokens that are allowed in the AST path from the
368     * number literal to the enclosing constant definition.
369     *
370     * @param tokens The string representation of the tokens interested in
371     * @since 6.11
372     */
373    public void setConstantWaiverParentToken(String... tokens) {
374        constantWaiverParentToken = TokenUtil.asBitSet(tokens);
375    }
376
377    /**
378     * Setter to specify non-magic numbers.
379     *
380     * @param list numbers to ignore.
381     * @since 3.1
382     */
383    public void setIgnoreNumbers(double... list) {
384        ignoreNumbers = new double[list.length];
385        System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
386        Arrays.sort(ignoreNumbers);
387    }
388
389    /**
390     * Setter to ignore magic numbers in hashCode methods.
391     *
392     * @param ignoreHashCodeMethod decide whether to ignore hash code methods
393     * @since 5.3
394     */
395    public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
396        this.ignoreHashCodeMethod = ignoreHashCodeMethod;
397    }
398
399    /**
400     * Setter to ignore magic numbers in annotation declarations.
401     *
402     * @param ignoreAnnotation decide whether to ignore annotations
403     * @since 5.4
404     */
405    public void setIgnoreAnnotation(boolean ignoreAnnotation) {
406        this.ignoreAnnotation = ignoreAnnotation;
407    }
408
409    /**
410     * Setter to ignore magic numbers in field declarations.
411     *
412     * @param ignoreFieldDeclaration decide whether to ignore magic numbers in field declaration
413     * @since 6.6
414     */
415    public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
416        this.ignoreFieldDeclaration = ignoreFieldDeclaration;
417    }
418
419    /**
420     * Setter to ignore magic numbers in annotation elements defaults.
421     *
422     * @param ignoreAnnotationElementDefaults decide whether to ignore annotation elements defaults
423     * @since 8.23
424     */
425    public void setIgnoreAnnotationElementDefaults(boolean ignoreAnnotationElementDefaults) {
426        this.ignoreAnnotationElementDefaults = ignoreAnnotationElementDefaults;
427    }
428
429    /**
430     * Determines if the given AST node has a parent node with given token type code.
431     *
432     * @param ast the AST from which to search for annotations
433     * @param type the type code of parent token
434     *
435     * @return {@code true} if the AST node has a parent with given token type.
436     */
437    private static boolean isChildOf(DetailAST ast, int type) {
438        boolean result = false;
439        DetailAST node = ast;
440        do {
441            if (node.getType() == type) {
442                result = true;
443                break;
444            }
445            node = node.getParent();
446        } while (node != null);
447
448        return result;
449    }
450
451}