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