1 | package fr.sii.ogham.core.util; | |
2 | ||
3 | import static fr.sii.ogham.core.util.StringUtils.capitalize; | |
4 | ||
5 | import java.lang.reflect.Field; | |
6 | import java.lang.reflect.InvocationTargetException; | |
7 | import java.lang.reflect.Method; | |
8 | ||
9 | import fr.sii.ogham.core.exception.util.FieldAccessException; | |
10 | ||
11 | /** | |
12 | * <p> | |
13 | * Assists in implementing Object.equals(Object) methods. This code is an | |
14 | * abstraction of {@link org.apache.commons.lang3.builder.EqualsBuilder} | |
15 | * provided by Apache Commons Lang. The aim is not to be sticked with Apache | |
16 | * Commons Lang and to be able to use another library or to adapt the library to | |
17 | * use according of libraries that are in the classpath. | |
18 | * </p> | |
19 | * | |
20 | * <p> | |
21 | * This class provides methods to build a good equals method for any class. It | |
22 | * follows rules laid out in Effective Java , by Joshua Bloch. In particular the | |
23 | * rule for comparing doubles, floats, and arrays can be tricky. Also, making | |
24 | * sure that equals() and hashCode() are consistent can be difficult. | |
25 | * </p> | |
26 | * | |
27 | * <p> | |
28 | * Two Objects that compare as equals must generate the same hash code, but two | |
29 | * Objects with the same hash code do not have to be equal. | |
30 | * </p> | |
31 | * | |
32 | * <p> | |
33 | * All relevant fields should be included in the calculation of equals. Derived | |
34 | * fields may be ignored. In particular, any field used in generating a hash | |
35 | * code must be used in the equals method, and vice versa. | |
36 | * </p> | |
37 | * | |
38 | * <p> | |
39 | * Typical use for the code is as follows: | |
40 | * </p> | |
41 | * | |
42 | * <pre><code> | |
43 | * public boolean equals(Object obj) { | |
44 | * return new EqualsBuilder(this, obj) | |
45 | * .appendSuper(super.equals(obj)) | |
46 | * .appendFields("field1", "field2", "field3") | |
47 | * .equals(); | |
48 | * } | |
49 | * </code> | |
50 | * </pre> | |
51 | * <p> | |
52 | * Alternatively, there is a method that uses reflection to determine the fields | |
53 | * to test. Because these fields are usually private, the method, | |
54 | * reflectionEquals, uses AccessibleObject.setAccessible to change the | |
55 | * visibility of the fields. This will fail under a security manager, unless the | |
56 | * appropriate permissions are set up correctly. It is also slower than testing | |
57 | * explicitly. Non-primitive fields are compared using equals(). | |
58 | * </p> | |
59 | * <p> | |
60 | * A typical invocation for this method would look like: | |
61 | * </p> | |
62 | * | |
63 | * <pre> | |
64 | * public boolean equals(Object obj) { | |
65 | * return EqualsBuilder.reflectionEquals(this, obj); | |
66 | * } | |
67 | * </pre> | |
68 | * | |
69 | * @author Aurélien Baudet | |
70 | * | |
71 | */ | |
72 | public class EqualsBuilder { | |
73 | /** | |
74 | * The current object | |
75 | */ | |
76 | private Object object; | |
77 | ||
78 | /** | |
79 | * The other object to compare the object | |
80 | */ | |
81 | private Object other; | |
82 | ||
83 | /** | |
84 | * The equality result | |
85 | */ | |
86 | private boolean equals; | |
87 | ||
88 | /** | |
89 | * The delegate implementation (currently Apache Commons but could be | |
90 | * anything) | |
91 | */ | |
92 | private org.apache.commons.lang3.builder.EqualsBuilder delegate; | |
93 | ||
94 | /** | |
95 | * Are the two objects referencing the same instance | |
96 | */ | |
97 | private boolean same; | |
98 | ||
99 | /** | |
100 | * Initialize the builder. This version doesn't check if: | |
101 | * <ul> | |
102 | * <li>the other object is null</li> | |
103 | * <li>the other object is the same as the current object</li> | |
104 | * <li>the two object classes are identical</li> | |
105 | * </ul> | |
106 | */ | |
107 | public EqualsBuilder() { | |
108 | super(); | |
109 | delegate = new org.apache.commons.lang3.builder.EqualsBuilder(); | |
110 | equals = true; | |
111 | same = false; | |
112 | } | |
113 | ||
114 | /** | |
115 | * Initialize the builder. This version checks if: | |
116 | * <ul> | |
117 | * <li>the other object is null</li> | |
118 | * <li>the other object is the same as the current object</li> | |
119 | * <li>the two object classes are identical</li> | |
120 | * </ul> | |
121 | * | |
122 | * <p> | |
123 | * Using this version lets you use the shortcut | |
124 | * {@link #appendFields(String...)} | |
125 | * </p> | |
126 | * | |
127 | * @param object | |
128 | * the current object | |
129 | * @param other | |
130 | * the other object | |
131 | */ | |
132 | public EqualsBuilder(Object object, Object other) { | |
133 | super(); | |
134 | this.object = object; | |
135 | this.other = other; | |
136 |
4
1. <init> : negated conditional → NO_COVERAGE 2. <init> : negated conditional → SURVIVED 3. <init> : negated conditional → KILLED 4. <init> : negated conditional → KILLED |
same = object == other; |
137 |
11
1. <init> : negated conditional → NO_COVERAGE 2. <init> : negated conditional → SURVIVED 3. <init> : negated conditional → NO_COVERAGE 4. <init> : negated conditional → NO_COVERAGE 5. <init> : negated conditional → KILLED 6. <init> : negated conditional → KILLED 7. <init> : negated conditional → KILLED 8. <init> : negated conditional → KILLED 9. <init> : negated conditional → KILLED 10. <init> : negated conditional → KILLED 11. <init> : negated conditional → KILLED |
equals = same || (other != null && object.getClass() == other.getClass()); |
138 | delegate = new org.apache.commons.lang3.builder.EqualsBuilder(); | |
139 | } | |
140 | ||
141 | /** | |
142 | * Test if two Objects are equal using their equals method. | |
143 | * | |
144 | * @param objectFieldValue | |
145 | * the value of a field of the object | |
146 | * @param otherFieldValue | |
147 | * the value of a field of the other object | |
148 | * @return used to chain calls | |
149 | */ | |
150 | public EqualsBuilder append(Object objectFieldValue, Object otherFieldValue) { | |
151 |
2
1. append : negated conditional → NO_COVERAGE 2. append : negated conditional → NO_COVERAGE |
if (equals && !same) { |
152 | delegate.append(objectFieldValue, otherFieldValue); | |
153 | } | |
154 |
1
1. append : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::append → NO_COVERAGE |
return this; |
155 | } | |
156 | ||
157 | /** | |
158 | * <p> | |
159 | * Test if the two previously registered objects have the same value for | |
160 | * each provided field name. | |
161 | * </p> | |
162 | * | |
163 | * <p> | |
164 | * Because these fields are usually private, this method uses | |
165 | * AccessibleObject.setAccessible to change the visibility of the fields. | |
166 | * This will fail under a security manager, unless the appropriate | |
167 | * permissions are set up correctly. It is also slower than testing | |
168 | * explicitly. Non-primitive fields are compared using equals(). | |
169 | * </p> | |
170 | * | |
171 | * @param fields | |
172 | * the name of fields to compare between the previously | |
173 | * registered objects | |
174 | * @return used to chain calls | |
175 | * @throws IllegalStateException | |
176 | * when calling this method but you haven't used the constructor | |
177 | * {@link #EqualsBuilder(Object, Object)} | |
178 | * @throws FieldAccessException | |
179 | * when the field is not accessible or cannot be read | |
180 | */ | |
181 | public EqualsBuilder appendFields(String... fields) { | |
182 |
4
1. appendFields : negated conditional → NO_COVERAGE 2. appendFields : negated conditional → KILLED 3. appendFields : negated conditional → KILLED 4. appendFields : negated conditional → KILLED |
if (object == null) { |
183 | throw new IllegalStateException("You can't use this method if you didn't use the constructor with object and other parameters"); | |
184 | } | |
185 |
8
1. appendFields : negated conditional → NO_COVERAGE 2. appendFields : negated conditional → SURVIVED 3. appendFields : negated conditional → NO_COVERAGE 4. appendFields : negated conditional → SURVIVED 5. appendFields : negated conditional → KILLED 6. appendFields : negated conditional → KILLED 7. appendFields : negated conditional → KILLED 8. appendFields : negated conditional → KILLED |
if (equals && !same) { |
186 | for (String field : fields) { | |
187 | try { | |
188 | delegate.append(getFieldValue(object, field), getFieldValue(other, field)); | |
189 | } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { | |
190 | throw new FieldAccessException("Failed to access field " + field, e); | |
191 | } | |
192 | } | |
193 | } | |
194 |
4
1. appendFields : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::appendFields → NO_COVERAGE 2. appendFields : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::appendFields → KILLED 3. appendFields : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::appendFields → KILLED 4. appendFields : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::appendFields → KILLED |
return this; |
195 | } | |
196 | ||
197 | /** | |
198 | * Adds the result of super.equals() to this builder. | |
199 | * | |
200 | * @param superEquals | |
201 | * the result of calling super.equals() | |
202 | * @return used to chain calls | |
203 | */ | |
204 | public EqualsBuilder appendSuper(boolean superEquals) { | |
205 |
2
1. appendSuper : negated conditional → NO_COVERAGE 2. appendSuper : negated conditional → NO_COVERAGE |
if (equals && !same) { |
206 | delegate.appendSuper(superEquals); | |
207 | } | |
208 |
1
1. appendSuper : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::appendSuper → NO_COVERAGE |
return this; |
209 | } | |
210 | ||
211 | /** | |
212 | * Returns true if the fields that have been checked are all equal. | |
213 | * | |
214 | * @return true if objects are equal, false otherwise | |
215 | */ | |
216 | public boolean isEqual() { | |
217 |
16
1. isEqual : replaced boolean return with true for fr/sii/ogham/core/util/EqualsBuilder::isEqual → NO_COVERAGE 2. isEqual : replaced boolean return with true for fr/sii/ogham/core/util/EqualsBuilder::isEqual → SURVIVED 3. isEqual : negated conditional → NO_COVERAGE 4. isEqual : negated conditional → SURVIVED 5. isEqual : negated conditional → NO_COVERAGE 6. isEqual : negated conditional → SURVIVED 7. isEqual : negated conditional → NO_COVERAGE 8. isEqual : replaced boolean return with true for fr/sii/ogham/core/util/EqualsBuilder::isEqual → KILLED 9. isEqual : replaced boolean return with true for fr/sii/ogham/core/util/EqualsBuilder::isEqual → KILLED 10. isEqual : negated conditional → KILLED 11. isEqual : negated conditional → KILLED 12. isEqual : negated conditional → KILLED 13. isEqual : negated conditional → KILLED 14. isEqual : negated conditional → KILLED 15. isEqual : negated conditional → KILLED 16. isEqual : negated conditional → KILLED |
return same || (equals && delegate.isEquals()); |
218 | } | |
219 | ||
220 | /** | |
221 | * <p> | |
222 | * This method uses reflection to determine if the two Objects are equal. | |
223 | * </p> | |
224 | * | |
225 | * <p> | |
226 | * If a getter exists, it uses the getter otherwise direct access to the | |
227 | * field is used. It uses AccessibleObject.setAccessible to gain access to | |
228 | * private fields. This means that it will throw a security exception if run | |
229 | * under a security manager, if the permissions are not set up correctly. It | |
230 | * is also not as efficient as testing explicitly. Non-primitive fields are | |
231 | * compared using equals(). | |
232 | * </p> | |
233 | * | |
234 | * <p> | |
235 | * Transient members will be not be tested, as they are likely derived | |
236 | * fields, and not part of the value of the Object. | |
237 | * </p> | |
238 | * | |
239 | * <p> | |
240 | * Static fields will not be tested. Superclass fields will be included. | |
241 | * </p> | |
242 | * | |
243 | * @param object | |
244 | * this object | |
245 | * @param other | |
246 | * the other object | |
247 | * @param excludeFields | |
248 | * array of field names to exclude from testing | |
249 | * @return true if the two Objects have tested equals. | |
250 | */ | |
251 | public static boolean reflectionsEquals(Object object, Object other, String... excludeFields) { | |
252 |
2
1. reflectionsEquals : replaced boolean return with false for fr/sii/ogham/core/util/EqualsBuilder::reflectionsEquals → NO_COVERAGE 2. reflectionsEquals : replaced boolean return with true for fr/sii/ogham/core/util/EqualsBuilder::reflectionsEquals → NO_COVERAGE |
return org.apache.commons.lang3.builder.EqualsBuilder.reflectionEquals(object, other, excludeFields); |
253 | } | |
254 | ||
255 | private static Object getFieldValue(Object object, String fieldName) throws IllegalAccessException, NoSuchFieldException, InvocationTargetException { | |
256 | Method getter = findMethod(object, "is" + capitalize(fieldName)); | |
257 |
2
1. getFieldValue : negated conditional → SURVIVED 2. getFieldValue : negated conditional → NO_COVERAGE |
if (getter == null) { |
258 | getter = findMethod(object, "get" + capitalize(fieldName)); | |
259 | } | |
260 |
4
1. getFieldValue : negated conditional → SURVIVED 2. getFieldValue : negated conditional → NO_COVERAGE 3. getFieldValue : negated conditional → KILLED 4. getFieldValue : negated conditional → KILLED |
if (getter != null) { |
261 |
4
1. getFieldValue : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::getFieldValue → NO_COVERAGE 2. getFieldValue : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::getFieldValue → SURVIVED 3. getFieldValue : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::getFieldValue → KILLED 4. getFieldValue : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::getFieldValue → KILLED |
return getter.invoke(object); |
262 | } | |
263 | Field field = getField(object, fieldName); | |
264 |
3
1. getFieldValue : removed call to java/lang/reflect/Field::setAccessible → NO_COVERAGE 2. getFieldValue : removed call to java/lang/reflect/Field::setAccessible → KILLED 3. getFieldValue : removed call to java/lang/reflect/Field::setAccessible → KILLED |
field.setAccessible(true); |
265 |
3
1. getFieldValue : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::getFieldValue → SURVIVED 2. getFieldValue : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::getFieldValue → NO_COVERAGE 3. getFieldValue : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::getFieldValue → KILLED |
return field.get(object); |
266 | } | |
267 | ||
268 | private static Method findMethod(Object object, String methodName) { | |
269 | try { | |
270 |
2
1. findMethod : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::findMethod → NO_COVERAGE 2. findMethod : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::findMethod → SURVIVED |
return object.getClass().getMethod(methodName); |
271 | } catch (NoSuchMethodException e) { | |
272 | return null; | |
273 | } | |
274 | } | |
275 | ||
276 | private static Field getField(Object object, String fieldName) throws NoSuchFieldException { | |
277 | Class<?> clazz = object.getClass(); | |
278 |
3
1. getField : negated conditional → NO_COVERAGE 2. getField : negated conditional → KILLED 3. getField : negated conditional → KILLED |
while (clazz != null) { |
279 | Field[] fields = clazz.getDeclaredFields(); | |
280 | for (Field f : fields) { | |
281 |
3
1. getField : negated conditional → NO_COVERAGE 2. getField : negated conditional → KILLED 3. getField : negated conditional → KILLED |
if (fieldName.equals(f.getName())) { |
282 |
3
1. getField : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::getField → NO_COVERAGE 2. getField : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::getField → KILLED 3. getField : replaced return value with null for fr/sii/ogham/core/util/EqualsBuilder::getField → KILLED |
return f; |
283 | } | |
284 | } | |
285 | clazz = clazz.getSuperclass(); | |
286 | } | |
287 | throw new NoSuchFieldException("Field " + fieldName + " not found on object " + object.getClass()); | |
288 | } | |
289 | } | |
Mutations | ||
136 |
1.1 2.2 3.3 4.4 |
|
137 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 10.10 11.11 |
|
151 |
1.1 2.2 |
|
154 |
1.1 |
|
182 |
1.1 2.2 3.3 4.4 |
|
185 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 |
|
194 |
1.1 2.2 3.3 4.4 |
|
205 |
1.1 2.2 |
|
208 |
1.1 |
|
217 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 10.10 11.11 12.12 13.13 14.14 15.15 16.16 |
|
252 |
1.1 2.2 |
|
257 |
1.1 2.2 |
|
260 |
1.1 2.2 3.3 4.4 |
|
261 |
1.1 2.2 3.3 4.4 |
|
264 |
1.1 2.2 3.3 |
|
265 |
1.1 2.2 3.3 |
|
270 |
1.1 2.2 |
|
278 |
1.1 2.2 3.3 |
|
281 |
1.1 2.2 3.3 |
|
282 |
1.1 2.2 3.3 |