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