001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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.Check;
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 Check {
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            return;
078        }
079
080        final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
081        final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
082        final DetailAST exprStart =
083            assign.getFirstChild().getFirstChild();
084        final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
085        if (isObjectType(type)
086            && exprStart.getType() == TokenTypes.LITERAL_NULL) {
087            log(ident, MSG_KEY, ident.getText(), "null");
088        }
089
090        final int primitiveType = type.getFirstChild().getType();
091        if (primitiveType == TokenTypes.LITERAL_BOOLEAN
092            && exprStart.getType() == TokenTypes.LITERAL_FALSE) {
093            log(ident, MSG_KEY, ident.getText(), "false");
094        }
095        if (isNumericType(primitiveType) && isZero(exprStart)) {
096            log(ident, MSG_KEY, ident.getText(), "0");
097        }
098        if (primitiveType == TokenTypes.LITERAL_CHAR
099            && isZeroChar(exprStart)) {
100            log(ident, MSG_KEY, ident.getText(), "\\0");
101        }
102    }
103
104    /**
105     * Examine char literal for initializing to default value.
106     * @param exprStart expression
107     * @return true is literal is initialized by zero symbol
108     */
109    private static boolean isZeroChar(DetailAST exprStart) {
110        return isZero(exprStart)
111            || exprStart.getType() == TokenTypes.CHAR_LITERAL
112            && "'\\0'".equals(exprStart.getText());
113    }
114
115    /**
116     * Checks for cases that should be skipped: no assignment, local variable, final variables
117     * @param ast Variable def AST
118     * @return true is that is a case that need to be skipped.
119     */
120    private static boolean isSkipCase(DetailAST ast) {
121        boolean skipCase = true;
122
123        // do not check local variables and
124        // fields declared in interface/annotations
125        if (!ScopeUtils.isLocalVariableDef(ast)
126                && !ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
127            final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
128
129            if (assign != null) {
130                final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
131                skipCase = modifiers.branchContains(TokenTypes.FINAL);
132            }
133        }
134        return skipCase;
135    }
136
137    /**
138     * Determines if a given type is an object type.
139     * @param type type to check.
140     * @return true if it is an object type.
141     */
142    private static boolean isObjectType(DetailAST type) {
143        final int objectType = type.getFirstChild().getType();
144        return objectType == TokenTypes.IDENT || objectType == TokenTypes.DOT
145                || objectType == TokenTypes.ARRAY_DECLARATOR;
146    }
147
148    /**
149     * Determine if a given type is a numeric type.
150     * @param type code of the type for check.
151     * @return true if it's a numeric type.
152     * @see TokenTypes
153     */
154    private static boolean isNumericType(int type) {
155        return type == TokenTypes.LITERAL_BYTE
156                || type == TokenTypes.LITERAL_SHORT
157                || type == TokenTypes.LITERAL_INT
158                || type == TokenTypes.LITERAL_FLOAT
159                || type == TokenTypes.LITERAL_LONG
160                || type == TokenTypes.LITERAL_DOUBLE;
161    }
162
163    /**
164     * @param expr node to check.
165     * @return true if given node contains numeric constant for zero.
166     */
167    private static boolean isZero(DetailAST expr) {
168        final int type = expr.getType();
169        switch (type) {
170            case TokenTypes.NUM_FLOAT:
171            case TokenTypes.NUM_DOUBLE:
172            case TokenTypes.NUM_INT:
173            case TokenTypes.NUM_LONG:
174                final String text = expr.getText();
175                return Double.compare(
176                    CheckUtils.parseDouble(text, type), 0.0) == 0;
177            default:
178                return false;
179        }
180    }
181}