Skip to contentMethod: hasChangesInternal(Object, Object)
1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
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 3.0 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.
17: */
18: package cz.cvut.kbss.jopa.sessions.change;
19:
20: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
21: import cz.cvut.kbss.jopa.model.metamodel.Identifier;
22: import cz.cvut.kbss.jopa.sessions.MetamodelProvider;
23: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
24: import cz.cvut.kbss.jopa.utils.JOPALazyUtils;
25: import org.slf4j.Logger;
26: import org.slf4j.LoggerFactory;
27:
28: import java.lang.reflect.Field;
29: import java.util.IdentityHashMap;
30: import java.util.Map;
31: import java.util.Objects;
32: import java.util.Set;
33:
34: /**
35: * Calculates changes made on objects by comparing a clone with its registered original.
36: */
37: public class ChangeCalculator {
38:
39: private static final Logger LOG = LoggerFactory.getLogger(ChangeCalculator.class);
40:
41: private final Map<Object, Object> visitedObjects;
42:
43: private final MetamodelProvider metamodelProvider;
44: private final ChangeDetector changeDetector;
45:
46: public ChangeCalculator(MetamodelProvider metamodelProvider) {
47: this.metamodelProvider = metamodelProvider;
48: this.changeDetector = new ChangeDetectors(metamodelProvider);
49: visitedObjects = new IdentityHashMap<>();
50: }
51:
52: /**
53: * Checks whether there are any changes to the clone.
54: * <p>
55: * It does an object value comparison, i.e. it compares each value of the clone against the original value and
56: * returns true if a change is found.
57: *
58: * @param original The original object.
59: * @param clone The clone, whose changes we are looking for.
60: * @return True if there is a change (at least one) or false, if the values are identical.
61: */
62: public boolean hasChanges(Object original, Object clone) {
63: boolean res = hasChangesInternal(original, clone);
64: visitedObjects.clear();
65: return res;
66: }
67:
68: /**
69: * This method does the actual check for changes. It is wrapped in the public method since the IdentityMap for
70: * visited objects has to be cleared after the whole check is done.
71: *
72: * @param original The original object.
73: * @param clone The clone that may have changed.
74: * @return True if the clone is in different state than the original.
75: */
76: private boolean hasChangesInternal(Object original, Object clone) {
77:• if (clone == null && original == null) {
78: return false;
79: }
80:• if (clone == null || original == null) {
81: return true;
82: }
83:• if (visitedObjects.containsKey(clone)) {
84: return false;
85: }
86: final Class<?> cls = clone.getClass();
87:• for (FieldSpecification<?, ?> fs : getFields(cls)) {
88:• if (fs instanceof Identifier<?, ?>) {
89: continue;
90: }
91: final Field f = fs.getJavaField();
92: final Object clVal = EntityPropertiesUtils.getFieldValue(f, clone);
93: final Object origVal = EntityPropertiesUtils.getFieldValue(f, original);
94: final boolean valueChanged = valueChanged(origVal, clVal);
95:• if (valueChanged) {
96: return true;
97: }
98: }
99: return false;
100: }
101:
102: private <X> Set<FieldSpecification<? super X, ?>> getFields(Class<X> cls) {
103: return metamodelProvider.getMetamodel().entity(cls).getFieldSpecifications();
104: }
105:
106: private boolean valueChanged(Object orig, Object clone) {
107: return changeDetector.hasChanges(clone, orig);
108: }
109:
110: /**
111: * Calculates the changes that happened to the clone object.
112: * <p>
113: * The changes are written into the {@link Change} passed in as argument.
114: *
115: * @param changeSet Contains references to the original and clone objects. Into this change set the changes should
116: * be propagated
117: * @return {@code true} if there were any changes, {@code false} otherwise
118: * @throws NullPointerException If {@code changeSet} is {@code null}
119: */
120: public boolean calculateChanges(ObjectChangeSet changeSet) {
121: return calculateChangesInternal(Objects.requireNonNull(changeSet));
122: }
123:
124: /**
125: * This internal method does the actual change calculation.
126: * <p>
127: * It compares every persistent attribute of the clone to the original value. If the values are different, a change
128: * record is added to the change set.
129: *
130: * @param changeSet The change set where change records will be put in
131: */
132: private boolean calculateChangesInternal(ObjectChangeSet changeSet) {
133: LOG.trace("Calculating changes for change set {}.", changeSet);
134: Object original = changeSet.getOriginal();
135: Object clone = changeSet.getClone();
136: boolean changesFound = false;
137: for (FieldSpecification<?, ?> fs : getFields(clone.getClass())) {
138: if (fs instanceof Identifier<?, ?>) {
139: continue;
140: }
141: Object clVal = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), clone);
142: Object origVal = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), original);
143: if (JOPALazyUtils.isLazyLoadingProxy(clVal)) {
144: continue;
145: }
146: boolean changed = valueChanged(origVal, clVal);
147: if (changed) {
148: changeSet.addChangeRecord(new ChangeRecord(fs, clVal));
149: changesFound = true;
150: }
151: }
152: return changesFound;
153: }
154: }