/** * Performs a deep comparison (using reflection) of two objects to determine whether they are * different. * * <p>If the object is a Question then the answer is treated specially because the original value * of the answer from our point of view is the value provided by the client in an Answer fact. If * no such fact exists then the value on the question itself is used. Scenarios are: * * <ul> * <li>Question which client has just answered - the new object is different if the answer is * not the value provided by the client. i.e. if the rules have changed it to something else * e.g. converting text to upper case. * <li>Another question - the new object is different if the answer is not the value on the * original object. * </ul> * * @param originalObject * @param newObject * @return */ private boolean different(TohuObject originalObject, TohuObject newObject) { if (!originalObject.equals(newObject)) { return true; } // special handling for Question answers if (originalObject instanceof Question) { Question originalQuestion = (Question) originalObject; String originalAnswer; if (clientAnswers != null && clientAnswers.containsKey(originalQuestion.getId())) { // original answer is the one provided by the client originalAnswer = clientAnswers.get(originalQuestion.getId()); } else { // original answer not provided by client so is contained in the original question originalAnswer = originalQuestion.getAnswer() == null ? null : originalQuestion.getAnswer().toString(); } Question newQuestion = (Question) newObject; String newAnswer = newQuestion.getAnswer() == null ? null : newQuestion.getAnswer().toString(); if (originalAnswer == null ? newAnswer != null : !originalAnswer.equals(newAnswer)) { return true; } } Class<?> clazz = originalObject.getClass(); do { // compare all non-static non-transient fields for (Field field : clazz.getDeclaredFields()) { int modifiers = field.getModifiers(); if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) { boolean answerField = field.isAnnotationPresent(Question.AnswerField.class); // answer fields are skipped because we have checked this already if (!answerField) { field.setAccessible(true); try { Object originalValue = field.get(originalObject); Object newValue = field.get(newObject); if (originalValue == null ? newValue != null : !originalValue.equals(newValue)) { return true; } } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } } clazz = clazz.getSuperclass(); } while (clazz != null); return false; }