| 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 |