1 | //////////////////////////////////////////////////////////////////////////////// | |
2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
3 | // Copyright (C) 2001-2018 the original author or authors. | |
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.coding; | |
21 | ||
22 | import java.util.HashMap; | |
23 | import java.util.Map; | |
24 | ||
25 | import antlr.collections.AST; | |
26 | import com.puppycrawl.tools.checkstyle.FileStatefulCheck; | |
27 | import com.puppycrawl.tools.checkstyle.api.AbstractCheck; | |
28 | import com.puppycrawl.tools.checkstyle.api.DetailAST; | |
29 | import com.puppycrawl.tools.checkstyle.api.FullIdent; | |
30 | import com.puppycrawl.tools.checkstyle.api.TokenTypes; | |
31 | import com.puppycrawl.tools.checkstyle.utils.CheckUtils; | |
32 | ||
33 | /** | |
34 | * <p> | |
35 | * Checks that classes that either override {@code equals()} or {@code hashCode()} also | |
36 | * overrides the other. | |
37 | * This checks only verifies that the method declarations match {@link Object#equals(Object)} and | |
38 | * {@link Object#hashCode()} exactly to be considered an override. This check does not verify | |
39 | * invalid method names, parameters other than {@code Object}, or anything else. | |
40 | * </p> | |
41 | * <p> | |
42 | * Rationale: The contract of equals() and hashCode() requires that | |
43 | * equal objects have the same hashCode. Hence, whenever you override | |
44 | * equals() you must override hashCode() to ensure that your class can | |
45 | * be used in collections that are hash based. | |
46 | * </p> | |
47 | * <p> | |
48 | * An example of how to configure the check is: | |
49 | * </p> | |
50 | * <pre> | |
51 | * <module name="EqualsHashCode"/> | |
52 | * </pre> | |
53 | * @author lkuehne | |
54 | */ | |
55 | @FileStatefulCheck | |
56 | public class EqualsHashCodeCheck | |
57 | extends AbstractCheck { | |
58 | ||
59 | // implementation note: we have to use the following members to | |
60 | // keep track of definitions in different inner classes | |
61 | ||
62 | /** | |
63 | * A key is pointing to the warning message text in "messages.properties" | |
64 | * file. | |
65 | */ | |
66 | public static final String MSG_KEY_HASHCODE = "equals.noHashCode"; | |
67 | ||
68 | /** | |
69 | * A key is pointing to the warning message text in "messages.properties" | |
70 | * file. | |
71 | */ | |
72 | public static final String MSG_KEY_EQUALS = "equals.noEquals"; | |
73 | ||
74 | /** Maps OBJ_BLOCK to the method definition of equals(). */ | |
75 | private final Map<DetailAST, DetailAST> objBlockWithEquals = new HashMap<>(); | |
76 | ||
77 | /** Maps OBJ_BLOCKs to the method definition of hashCode(). */ | |
78 | private final Map<DetailAST, DetailAST> objBlockWithHashCode = new HashMap<>(); | |
79 | ||
80 | @Override | |
81 | public int[] getDefaultTokens() { | |
82 |
1
1. getDefaultTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsHashCodeCheck::getDefaultTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return getRequiredTokens(); |
83 | } | |
84 | ||
85 | @Override | |
86 | public int[] getAcceptableTokens() { | |
87 |
1
1. getAcceptableTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsHashCodeCheck::getAcceptableTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return getRequiredTokens(); |
88 | } | |
89 | ||
90 | @Override | |
91 | public int[] getRequiredTokens() { | |
92 |
1
1. getRequiredTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsHashCodeCheck::getRequiredTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return new int[] {TokenTypes.METHOD_DEF}; |
93 | } | |
94 | ||
95 | @Override | |
96 | public void beginTree(DetailAST rootAST) { | |
97 |
1
1. beginTree : removed call to java/util/Map::clear → KILLED |
objBlockWithEquals.clear(); |
98 |
1
1. beginTree : removed call to java/util/Map::clear → KILLED |
objBlockWithHashCode.clear(); |
99 | } | |
100 | ||
101 | @Override | |
102 | public void visitToken(DetailAST ast) { | |
103 |
1
1. visitToken : negated conditional → KILLED |
if (isEqualsMethod(ast)) { |
104 | objBlockWithEquals.put(ast.getParent(), ast); | |
105 | } | |
106 |
1
1. visitToken : negated conditional → KILLED |
else if (isHashCodeMethod(ast)) { |
107 | objBlockWithHashCode.put(ast.getParent(), ast); | |
108 | } | |
109 | } | |
110 | ||
111 | /** | |
112 | * Determines if an AST is a valid Equals method implementation. | |
113 | * | |
114 | * @param ast the AST to check | |
115 | * @return true if the {code ast} is a Equals method. | |
116 | */ | |
117 | private static boolean isEqualsMethod(DetailAST ast) { | |
118 | final DetailAST modifiers = ast.getFirstChild(); | |
119 | final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS); | |
120 | ||
121 |
2
1. isEqualsMethod : negated conditional → KILLED 2. isEqualsMethod : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return CheckUtils.isEqualsMethod(ast) |
122 |
1
1. isEqualsMethod : negated conditional → KILLED |
&& modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null |
123 |
1
1. isEqualsMethod : negated conditional → KILLED |
&& isObjectParam(parameters.getFirstChild()) |
124 |
1
1. isEqualsMethod : negated conditional → KILLED |
&& (ast.findFirstToken(TokenTypes.SLIST) != null |
125 |
1
1. isEqualsMethod : negated conditional → KILLED |
|| modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) != null); |
126 | } | |
127 | ||
128 | /** | |
129 | * Determines if an AST is a valid HashCode method implementation. | |
130 | * | |
131 | * @param ast the AST to check | |
132 | * @return true if the {code ast} is a HashCode method. | |
133 | */ | |
134 | private static boolean isHashCodeMethod(DetailAST ast) { | |
135 | final DetailAST modifiers = ast.getFirstChild(); | |
136 | final AST type = ast.findFirstToken(TokenTypes.TYPE); | |
137 | final AST methodName = ast.findFirstToken(TokenTypes.IDENT); | |
138 | final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS); | |
139 | ||
140 |
2
1. isHashCodeMethod : negated conditional → KILLED 2. isHashCodeMethod : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return type.getFirstChild().getType() == TokenTypes.LITERAL_INT |
141 |
1
1. isHashCodeMethod : negated conditional → KILLED |
&& "hashCode".equals(methodName.getText()) |
142 |
1
1. isHashCodeMethod : negated conditional → KILLED |
&& modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null |
143 |
1
1. isHashCodeMethod : negated conditional → KILLED |
&& modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null |
144 |
1
1. isHashCodeMethod : negated conditional → KILLED |
&& parameters.getFirstChild() == null |
145 |
1
1. isHashCodeMethod : negated conditional → KILLED |
&& (ast.findFirstToken(TokenTypes.SLIST) != null |
146 |
1
1. isHashCodeMethod : negated conditional → KILLED |
|| modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) != null); |
147 | } | |
148 | ||
149 | /** | |
150 | * Determines if an AST is a formal param of type Object. | |
151 | * @param paramNode the AST to check | |
152 | * @return true if firstChild is a parameter of an Object type. | |
153 | */ | |
154 | private static boolean isObjectParam(DetailAST paramNode) { | |
155 | final DetailAST typeNode = paramNode.findFirstToken(TokenTypes.TYPE); | |
156 | final FullIdent fullIdent = FullIdent.createFullIdentBelow(typeNode); | |
157 | final String name = fullIdent.getText(); | |
158 |
3
1. isObjectParam : negated conditional → KILLED 2. isObjectParam : negated conditional → KILLED 3. isObjectParam : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return "Object".equals(name) || "java.lang.Object".equals(name); |
159 | } | |
160 | ||
161 | @Override | |
162 | public void finishTree(DetailAST rootAST) { | |
163 | objBlockWithEquals | |
164 | .entrySet().stream().filter(detailASTDetailASTEntry -> { | |
165 |
2
1. lambda$finishTree$0 : negated conditional → KILLED 2. lambda$finishTree$0 : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return objBlockWithHashCode.remove(detailASTDetailASTEntry.getKey()) == null; |
166 |
1
1. finishTree : removed call to java/util/stream/Stream::forEach → KILLED |
}).forEach(detailASTDetailASTEntry -> { |
167 | final DetailAST equalsAST = detailASTDetailASTEntry.getValue(); | |
168 |
1
1. lambda$finishTree$1 : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsHashCodeCheck::log → KILLED |
log(equalsAST.getLineNo(), equalsAST.getColumnNo(), MSG_KEY_HASHCODE); |
169 | }); | |
170 |
1
1. finishTree : removed call to java/util/Map::forEach → KILLED |
objBlockWithHashCode.forEach((key, equalsAST) -> { |
171 |
1
1. lambda$finishTree$2 : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsHashCodeCheck::log → KILLED |
log(equalsAST.getLineNo(), equalsAST.getColumnNo(), MSG_KEY_EQUALS); |
172 | }); | |
173 | } | |
174 | ||
175 | } | |
Mutations | ||
82 |
1.1 |
|
87 |
1.1 |
|
92 |
1.1 |
|
97 |
1.1 |
|
98 |
1.1 |
|
103 |
1.1 |
|
106 |
1.1 |
|
121 |
1.1 2.2 |
|
122 |
1.1 |
|
123 |
1.1 |
|
124 |
1.1 |
|
125 |
1.1 |
|
140 |
1.1 2.2 |
|
141 |
1.1 |
|
142 |
1.1 |
|
143 |
1.1 |
|
144 |
1.1 |
|
145 |
1.1 |
|
146 |
1.1 |
|
158 |
1.1 2.2 3.3 |
|
165 |
1.1 2.2 |
|
166 |
1.1 |
|
168 |
1.1 |
|
170 |
1.1 |
|
171 |
1.1 |