001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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 antlr.collections.AST;
023import com.google.common.collect.Maps;
024import com.google.common.collect.Sets;
025import com.puppycrawl.tools.checkstyle.api.Check;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import java.util.Map;
029import java.util.Set;
030
031/**
032 * <p>
033 * Checks that classes that override equals() also override hashCode().
034 * </p>
035 * <p>
036 * Rationale: The contract of equals() and hashCode() requires that
037 * equal objects have the same hashCode. Hence, whenever you override
038 * equals() you must override hashCode() to ensure that your class can
039 * be used in collections that are hash based.
040 * </p>
041 * <p>
042 * An example of how to configure the check is:
043 * </p>
044 * <pre>
045 * &lt;module name="EqualsHashCode"/&gt;
046 * </pre>
047 * @author lkuehne
048 */
049public class EqualsHashCodeCheck
050        extends Check
051{
052    // implementation note: we have to use the following members to
053    // keep track of definitions in different inner classes
054
055    /** maps OBJ_BLOCK to the method definition of equals() */
056    private final Map<DetailAST, DetailAST> objBlockEquals = Maps.newHashMap();
057
058    /** the set of OBJ_BLOCKs that contain a definition of hashCode() */
059    private final Set<DetailAST> objBlockWithHashCode = Sets.newHashSet();
060
061    @Override
062    public int[] getDefaultTokens()
063    {
064        return new int[] {TokenTypes.METHOD_DEF};
065    }
066
067    @Override
068    public void beginTree(DetailAST rootAST)
069    {
070        objBlockEquals.clear();
071        objBlockWithHashCode.clear();
072    }
073
074    @Override
075    public void visitToken(DetailAST ast)
076    {
077        final DetailAST modifiers = ast.getFirstChild();
078        final AST type = ast.findFirstToken(TokenTypes.TYPE);
079        final AST methodName = ast.findFirstToken(TokenTypes.IDENT);
080        final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
081
082        if ((type.getFirstChild().getType() == TokenTypes.LITERAL_BOOLEAN)
083                && "equals".equals(methodName.getText())
084                && modifiers.branchContains(TokenTypes.LITERAL_PUBLIC)
085                && (parameters.getChildCount() == 1)
086                && isObjectParam(parameters.getFirstChild())
087            )
088        {
089            objBlockEquals.put(ast.getParent(), ast);
090        }
091        else if ((type.getFirstChild().getType() == TokenTypes.LITERAL_INT)
092                && "hashCode".equals(methodName.getText())
093                && modifiers.branchContains(TokenTypes.LITERAL_PUBLIC)
094                && (parameters.getFirstChild() == null)) // no params
095        {
096            objBlockWithHashCode.add(ast.getParent());
097        }
098    }
099
100    /**
101     * Determines if an AST is a formal param of type Object (or subclass).
102     * @param firstChild the AST to check
103     * @return true iff firstChild is a parameter of an Object type.
104     */
105    private boolean isObjectParam(AST firstChild)
106    {
107        final AST modifiers = firstChild.getFirstChild();
108        final AST type = modifiers.getNextSibling();
109        switch (type.getFirstChild().getType()) {
110            case TokenTypes.LITERAL_BOOLEAN:
111            case TokenTypes.LITERAL_BYTE:
112            case TokenTypes.LITERAL_CHAR:
113            case TokenTypes.LITERAL_DOUBLE:
114            case TokenTypes.LITERAL_FLOAT:
115            case TokenTypes.LITERAL_INT:
116            case TokenTypes.LITERAL_LONG:
117            case TokenTypes.LITERAL_SHORT:
118                return false;
119            default:
120                return true;
121        }
122    }
123
124    @Override
125    public void finishTree(DetailAST rootAST)
126    {
127        final Set<DetailAST> equalsDefs = objBlockEquals.keySet();
128        for (DetailAST objBlock : equalsDefs) {
129            if (!objBlockWithHashCode.contains(objBlock)) {
130                final DetailAST equalsAST = objBlockEquals.get(objBlock);
131                log(equalsAST.getLineNo(), equalsAST.getColumnNo(),
132                        "equals.noHashCode");
133            }
134        }
135
136        objBlockEquals.clear();
137        objBlockWithHashCode.clear();
138    }
139}