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 java.util.Map; 023 024import antlr.collections.AST; 025 026import com.google.common.collect.Maps; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 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 AbstractCheck { 051 // implementation note: we have to use the following members to 052 // keep track of definitions in different inner classes 053 054 /** 055 * A key is pointing to the warning message text in "messages.properties" 056 * file. 057 */ 058 public static final String MSG_KEY_HASHCODE = "equals.noHashCode"; 059 060 /** 061 * A key is pointing to the warning message text in "messages.properties" 062 * file. 063 */ 064 public static final String MSG_KEY_EQUALS = "equals.noEquals"; 065 066 /** Maps OBJ_BLOCK to the method definition of equals(). */ 067 private final Map<DetailAST, DetailAST> objBlockWithEquals = Maps.newHashMap(); 068 069 /** Maps OBJ_BLOCKs to the method definition of hashCode(). */ 070 private final Map<DetailAST, DetailAST> objBlockWithHashCode = Maps.newHashMap(); 071 072 @Override 073 public int[] getDefaultTokens() { 074 return getAcceptableTokens(); 075 } 076 077 @Override 078 public int[] getAcceptableTokens() { 079 return new int[] {TokenTypes.METHOD_DEF}; 080 } 081 082 @Override 083 public int[] getRequiredTokens() { 084 return getAcceptableTokens(); 085 } 086 087 @Override 088 public void beginTree(DetailAST rootAST) { 089 objBlockWithEquals.clear(); 090 objBlockWithHashCode.clear(); 091 } 092 093 @Override 094 public void visitToken(DetailAST ast) { 095 final DetailAST modifiers = ast.getFirstChild(); 096 final AST type = ast.findFirstToken(TokenTypes.TYPE); 097 final AST methodName = ast.findFirstToken(TokenTypes.IDENT); 098 final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS); 099 100 if (type.getFirstChild().getType() == TokenTypes.LITERAL_BOOLEAN 101 && "equals".equals(methodName.getText()) 102 && modifiers.branchContains(TokenTypes.LITERAL_PUBLIC) 103 && parameters.getChildCount() == 1 104 && isObjectParam(parameters.getFirstChild()) 105 ) { 106 objBlockWithEquals.put(ast.getParent(), ast); 107 } 108 else if (type.getFirstChild().getType() == TokenTypes.LITERAL_INT 109 && "hashCode".equals(methodName.getText()) 110 && modifiers.branchContains(TokenTypes.LITERAL_PUBLIC) 111 && parameters.getFirstChild() == null) { 112 objBlockWithHashCode.put(ast.getParent(), ast); 113 } 114 } 115 116 /** 117 * Determines if an AST is a formal param of type Object (or subclass). 118 * @param firstChild the AST to check 119 * @return true iff firstChild is a parameter of an Object type. 120 */ 121 private static boolean isObjectParam(AST firstChild) { 122 final AST modifiers = firstChild.getFirstChild(); 123 final AST type = modifiers.getNextSibling(); 124 switch (type.getFirstChild().getType()) { 125 case TokenTypes.LITERAL_BOOLEAN: 126 case TokenTypes.LITERAL_BYTE: 127 case TokenTypes.LITERAL_CHAR: 128 case TokenTypes.LITERAL_DOUBLE: 129 case TokenTypes.LITERAL_FLOAT: 130 case TokenTypes.LITERAL_INT: 131 case TokenTypes.LITERAL_LONG: 132 case TokenTypes.LITERAL_SHORT: 133 return false; 134 default: 135 return true; 136 } 137 } 138 139 @Override 140 public void finishTree(DetailAST rootAST) { 141 for (Map.Entry<DetailAST, DetailAST> detailASTDetailASTEntry : objBlockWithEquals 142 .entrySet()) { 143 if (objBlockWithHashCode.remove(detailASTDetailASTEntry.getKey()) == null) { 144 final DetailAST equalsAST = detailASTDetailASTEntry.getValue(); 145 log(equalsAST.getLineNo(), equalsAST.getColumnNo(), MSG_KEY_HASHCODE); 146 } 147 } 148 for (Map.Entry<DetailAST, DetailAST> detailASTDetailASTEntry : objBlockWithHashCode 149 .entrySet()) { 150 final DetailAST equalsAST = detailASTDetailASTEntry.getValue(); 151 log(equalsAST.getLineNo(), equalsAST.getColumnNo(), MSG_KEY_EQUALS); 152 } 153 154 objBlockWithEquals.clear(); 155 objBlockWithHashCode.clear(); 156 } 157}