001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2016 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 com.puppycrawl.tools.checkstyle.api.AbstractCheck;
023import com.puppycrawl.tools.checkstyle.api.DetailAST;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
026import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
027
028/**
029 * <p>
030 * Checks if any class or object member explicitly initialized
031 * to default for its type value ({@code null} for object
032 * references, zero for numeric types and {@code char}
033 * and {@code false} for {@code boolean}.
034 * </p>
035 * <p>
036 * Rationale: each instance variable gets
037 * initialized twice, to the same value.  Java
038 * initializes each instance variable to its default
039 * value (0 or null) before performing any
040 * initialization specified in the code.  So in this case,
041 * x gets initialized to 0 twice, and bar gets initialized
042 * to null twice.  So there is a minor inefficiency.  This style of
043 * coding is a hold-over from C/C++ style coding,
044 * and it shows that the developer isn't really confident that
045 * Java really initializes instance variables to default
046 * values.
047 * </p>
048 *
049 * @author o_sukhodolsky
050 */
051public class ExplicitInitializationCheck extends AbstractCheck {
052
053    /**
054     * A key is pointing to the warning message text in "messages.properties"
055     * file.
056     */
057    public static final String MSG_KEY = "explicit.init";
058
059    @Override
060    public final int[] getDefaultTokens() {
061        return new int[] {TokenTypes.VARIABLE_DEF};
062    }
063
064    @Override
065    public final int[] getRequiredTokens() {
066        return getDefaultTokens();
067    }
068
069    @Override
070    public final int[] getAcceptableTokens() {
071        return new int[] {TokenTypes.VARIABLE_DEF};
072    }
073
074    @Override
075    public void visitToken(DetailAST ast) {
076        if (!isSkipCase(ast)) {
077            final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
078            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
079            final DetailAST exprStart =
080                assign.getFirstChild().getFirstChild();
081            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
082            if (isObjectType(type)
083                && exprStart.getType() == TokenTypes.LITERAL_NULL) {
084                log(ident, MSG_KEY, ident.getText(), "null");
085            }
086
087            final int primitiveType = type.getFirstChild().getType();
088            if (primitiveType == TokenTypes.LITERAL_BOOLEAN
089                && exprStart.getType() == TokenTypes.LITERAL_FALSE) {
090                log(ident, MSG_KEY, ident.getText(), "false");
091            }
092            if (isNumericType(primitiveType) && isZero(exprStart)) {
093                log(ident, MSG_KEY, ident.getText(), "0");
094            }
095            if (primitiveType == TokenTypes.LITERAL_CHAR
096                && isZeroChar(exprStart)) {
097                log(ident, MSG_KEY, ident.getText(), "\\0");
098            }
099        }
100    }
101
102    /**
103     * Examine char literal for initializing to default value.
104     * @param exprStart expression
105     * @return true is literal is initialized by zero symbol
106     */
107    private static boolean isZeroChar(DetailAST exprStart) {
108        return isZero(exprStart)
109            || exprStart.getType() == TokenTypes.CHAR_LITERAL
110            && "'\\0'".equals(exprStart.getText());
111    }
112
113    /**
114     * Checks for cases that should be skipped: no assignment, local variable, final variables
115     * @param ast Variable def AST
116     * @return true is that is a case that need to be skipped.
117     */
118    private static boolean isSkipCase(DetailAST ast) {
119        boolean skipCase = true;
120
121        // do not check local variables and
122        // fields declared in interface/annotations
123        if (!ScopeUtils.isLocalVariableDef(ast)
124                && !ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
125            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
126
127            if (assign != null) {
128                final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
129                skipCase = modifiers.branchContains(TokenTypes.FINAL);
130            }
131        }
132        return skipCase;
133    }
134
135    /**
136     * Determines if a given type is an object type.
137     * @param type type to check.
138     * @return true if it is an object type.
139     */
140    private static boolean isObjectType(DetailAST type) {
141        final int objectType = type.getFirstChild().getType();
142        return objectType == TokenTypes.IDENT || objectType == TokenTypes.DOT
143                || objectType == TokenTypes.ARRAY_DECLARATOR;
144    }
145
146    /**
147     * Determine if a given type is a numeric type.
148     * @param type code of the type for check.
149     * @return true if it's a numeric type.
150     * @see TokenTypes
151     */
152    private static boolean isNumericType(int type) {
153        return type == TokenTypes.LITERAL_BYTE
154                || type == TokenTypes.LITERAL_SHORT
155                || type == TokenTypes.LITERAL_INT
156                || type == TokenTypes.LITERAL_FLOAT
157                || type == TokenTypes.LITERAL_LONG
158                || type == TokenTypes.LITERAL_DOUBLE;
159    }
160
161    /**
162     * @param expr node to check.
163     * @return true if given node contains numeric constant for zero.
164     */
165    private static boolean isZero(DetailAST expr) {
166        final int type = expr.getType();
167        switch (type) {
168            case TokenTypes.NUM_FLOAT:
169            case TokenTypes.NUM_DOUBLE:
170            case TokenTypes.NUM_INT:
171            case TokenTypes.NUM_LONG:
172                final String text = expr.getText();
173                return Double.compare(
174                    CheckUtils.parseDouble(text, type), 0.0) == 0;
175            default:
176                return false;
177        }
178    }
179}