Source code: com/puppycrawl/tools/checkstyle/checks/EqualsHashCodeCheck.java
1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2002 Oliver Burn
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks;
21
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.Iterator;
25 import java.util.Map;
26 import java.util.Set;
27
28 import antlr.collections.AST;
29 import com.puppycrawl.tools.checkstyle.api.Check;
30 import com.puppycrawl.tools.checkstyle.api.DetailAST;
31 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32
33 /**
34 * <p>
35 * Checks that classes that override equals() also override hashCode().
36 * </p>
37 * <p>
38 * Rationale: The contract of equals() and hashCode() requires that
39 * equal objects have the same hashCode. Hence, whenever you override
40 * equals() you must override hashCode() to ensure that your class can
41 * be used in collections that are hash based.
42 * </p>
43 * <p>
44 * An example of how to configure the check is:
45 * </p>
46 * <pre>
47 * <module name="EqualsHashCode"/>
48 * </pre>
49 * @author lkuehne
50 */
51 public class EqualsHashCodeCheck
52 extends Check
53 {
54 // implementation note: we have to use the following members to
55 // keep track of definitions in different inner classes
56
57 /** maps OBJ_BLOCK to the method definition of equals() */
58 private final Map mObjBlockEquals = new HashMap();
59
60 /** the set of OBJ_BLOCKs that contain a definition of hashCode() */
61 private final Set mObjBlockWithHashCode = new HashSet();
62
63
64 /** @see Check */
65 public int[] getDefaultTokens()
66 {
67 return new int[] {TokenTypes.METHOD_DEF};
68 }
69
70 /** @see Check */
71 public void beginTree()
72 {
73 mObjBlockEquals.clear();
74 mObjBlockWithHashCode.clear();
75 }
76
77 /** @see Check */
78 public void visitToken(DetailAST aAST)
79 {
80 DetailAST modifiers = (DetailAST) aAST.getFirstChild();
81
82 AST type = modifiers.getNextSibling();
83 AST methodName = type.getNextSibling();
84 DetailAST parameters = (DetailAST) methodName.getNextSibling();
85
86 if (type.getFirstChild().getType() == TokenTypes.LITERAL_BOOLEAN
87 && "equals".equals(methodName.getText())
88 && modifiers.branchContains(TokenTypes.LITERAL_PUBLIC)
89 && parameters.getChildCount() == 1
90 && isObjectParam(parameters.getFirstChild())
91 )
92 {
93 mObjBlockEquals.put(aAST.getParent(), aAST);
94 }
95 else if (type.getFirstChild().getType() == TokenTypes.LITERAL_INT
96 && "hashCode".equals(methodName.getText())
97 && modifiers.branchContains(TokenTypes.LITERAL_PUBLIC)
98 && parameters.getFirstChild() == null) // no params
99 {
100 mObjBlockWithHashCode.add(aAST.getParent());
101 }
102 }
103
104 /**
105 * Determines if an AST is a formal param of type Object (or subclass).
106 * @param aFirstChild the AST to check
107 * @return true iff aFirstChild is a parameter of an Object type.
108 */
109 private boolean isObjectParam(AST aFirstChild)
110 {
111 final AST modifiers = aFirstChild.getFirstChild();
112 AST type = modifiers.getNextSibling();
113 switch (type.getFirstChild().getType()) {
114 case TokenTypes.LITERAL_BOOLEAN:
115 case TokenTypes.LITERAL_BYTE:
116 case TokenTypes.LITERAL_CHAR:
117 case TokenTypes.LITERAL_DOUBLE:
118 case TokenTypes.LITERAL_FLOAT:
119 case TokenTypes.LITERAL_INT:
120 case TokenTypes.LITERAL_LONG:
121 case TokenTypes.LITERAL_SHORT:
122 return false;
123 default:
124 return true;
125 }
126 }
127
128 /**
129 * @see Check
130 */
131 public void finishTree()
132 {
133 final Set equalsDefs = mObjBlockEquals.keySet();
134 for (Iterator it = equalsDefs.iterator(); it.hasNext();) {
135 Object objBlock = it.next();
136 if (!mObjBlockWithHashCode.contains(objBlock)) {
137 DetailAST equalsAST = (DetailAST) mObjBlockEquals.get(objBlock);
138 log(equalsAST.getLineNo(), equalsAST.getColumnNo(),
139 "equals.noHashCode");
140 }
141 }
142
143 mObjBlockEquals.clear();
144 mObjBlockWithHashCode.clear();
145 }
146 }