package de.invesdwin.util.bean; import java.util.HashSet; import java.util.Set; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import javax.persistence.Transient; import com.fasterxml.jackson.annotation.JsonIgnore; import com.querydsl.core.annotations.QuerySupertype; import de.invesdwin.norva.apt.constants.BeanPathRoot; import de.invesdwin.norva.beanpath.annotation.Hidden; import de.invesdwin.norva.beanpath.impl.object.BeanObjectContext; import de.invesdwin.norva.beanpath.impl.object.BeanObjectProcessor; import de.invesdwin.norva.beanpath.spi.element.IPropertyBeanPathElement; import de.invesdwin.norva.beanpath.spi.visitor.SimpleBeanPathVisitorSupport; import de.invesdwin.norva.marker.ISerializableValueObject; import de.invesdwin.util.bean.internal.ValueObjectMerge; import de.invesdwin.util.error.Throwables; import de.invesdwin.util.lang.Objects; /** * ValueObjects are non persistent Entities. They do not contain any logic, but they contain data and verifications. * * This class implements toString, hashCode, equals und compareTo methods via reflection. * * Clone is done by serialization to ensure deep copies. * * @author subes * */ @SuppressWarnings("serial") @ThreadSafe @QuerySupertype @BeanPathRoot public abstract class AValueObject extends APropertyChangeSupported implements Comparable<Object>, Cloneable, ISerializableValueObject { @GuardedBy("this") @Transient @JsonIgnore private transient DirtyTracker dirtyTracker; static { Objects.REFLECTION_EXCLUDED_FIELDS.add("beanUtilsBean"); Objects.REFLECTION_EXCLUDED_FIELDS.add("dirtyTracker"); } @Override public String toString() { return Objects.toString(this); } @Hidden(skip = true) public String toStringMultiline() { return Objects.toStringMultiline(this); } @Override public int hashCode() { return Objects.reflectionHashCode(this); } @Override public boolean equals(final Object obj) { return Objects.reflectionEquals(this, obj); } /** * This method checks values by bean path which are not hidden by annotations or utility methods. Thus can be used * to compare values of entities without regards to id, time and version properties. */ @Hidden(skip = true) public boolean equalsByBeanPathValues(final Object obj) { if (obj != null && getClass().isAssignableFrom(obj.getClass())) { final BeanObjectContext ctxThis = new BeanObjectContext(this); try { new BeanObjectProcessor(ctxThis, new SimpleBeanPathVisitorSupport(ctxThis) { @Override public void visitProperty(final IPropertyBeanPathElement e) { final Object valueThis = e.getModifier().getValue(); final Object valueObj = e.getModifier().getValueFromRoot(obj); if (!Objects.equals(valueThis, valueObj)) { throw new NotEqualRuntimeException(); } } }).process(); return true; } catch (final Throwable t) { if (Throwables.isCausedByType(t, NotEqualRuntimeException.class)) { return false; } else { throw Throwables.propagate(t); } } } else { return false; } } private static class NotEqualRuntimeException extends RuntimeException { } @Override public int compareTo(final Object o) { return Objects.reflectionCompareTo(this, o); } /** * Same as mergeFrom(o, true). */ @Hidden(skip = true) public void mergeFrom(final Object o) { mergeFrom(o, true); } /** * Copies via reflection all matching getters and setters that are public. * * Null values do not get copied. * * If overwrite=true, then non null fields will be overwritten aswell. Else they are kept as they are. * * The values of AValueObject fields are recursively copied if already set. Else the AValueObject field is directly * copied. * * Entity values like id, version, created etc are preserved on merge. */ @Hidden(skip = true) public final void mergeFrom(final Object o, final boolean overwrite) { innerMergeFrom(o, overwrite, false, new HashSet<String>()); } protected void innerMergeFrom(final Object o, final boolean overwrite, final boolean clone, final Set<String> recursionFilter) { new ValueObjectMerge(this, overwrite, clone, new HashSet<String>()).merge(o); } /** * Per convention this creates a deep copy; this default implementation might be a bit slow because of serialization * being used. But on the other hand this reduces development effort by a manifold and optimizations need only be * done where needed. */ @Override public AValueObject clone() { //SUPPRESS CHECKSTYLE super.clone() return Objects.deepClone(this); } /** * Can be used if a faster implementation of clone() if needed. */ @Hidden(skip = true) public AValueObject shallowClone() { try { return (AValueObject) super.clone(); } catch (final CloneNotSupportedException e) { throw new RuntimeException(e); } } @Hidden(skip = true) public AValueObject shallowCloneReflective() { final AValueObject clone = shallowClone(); clone.innerMergeFrom(this, true, true, new HashSet<String>()); return clone; } @Hidden(skip = true) @Transient @JsonIgnore public synchronized DirtyTracker dirtyTracker() { if (dirtyTracker == null) { dirtyTracker = new DirtyTracker(this); } return dirtyTracker; } }