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 * <module name="EqualsHashCode"/> 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}