Skip to content

Package: CloneBuilderImpl$Builders

CloneBuilderImpl$Builders

nameinstructionbranchcomplexitylinemethod
CloneBuilderImpl.Builders(CloneBuilderImpl)
M: 0 C: 30
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 5
100%
M: 0 C: 1
100%
getBuilder(Object)
M: 0 C: 53
100%
M: 0 C: 12
100%
M: 0 C: 7
100%
M: 0 C: 13
100%
M: 0 C: 1
100%

Coverage

1: /**
2: * Copyright (C) 2020 Czech Technical University in Prague
3: * <p>
4: * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
5: * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
6: * version.
7: * <p>
8: * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9: * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10: * details. You should have received a copy of the GNU General Public License along with this program. If not, see
11: * <http://www.gnu.org/licenses/>.
12: */
13: package cz.cvut.kbss.jopa.sessions;
14:
15: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
16: import cz.cvut.kbss.jopa.model.MultilingualString;
17: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
18: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
19: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
20: import cz.cvut.kbss.jopa.model.metamodel.Identifier;
21: import cz.cvut.kbss.jopa.model.metamodel.Metamodel;
22: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
23: import org.slf4j.Logger;
24: import org.slf4j.LoggerFactory;
25:
26: import java.lang.reflect.Field;
27: import java.net.URI;
28: import java.net.URL;
29: import java.time.Instant;
30: import java.time.LocalDate;
31: import java.time.LocalDateTime;
32: import java.time.ZonedDateTime;
33: import java.util.*;
34:
35: public class CloneBuilderImpl implements CloneBuilder {
36:
37: private static final Logger LOG = LoggerFactory.getLogger(CloneBuilderImpl.class);
38:
39: private static final Set<Class<?>> IMMUTABLE_TYPES = getImmutableTypes();
40:
41: // Contains entities that are already cloned, so that we don't clone them again
42: private final RepositoryMap visitedEntities;
43:
44: private final Builders builders;
45:
46: private final UnitOfWorkImpl uow;
47:
48: public CloneBuilderImpl(UnitOfWorkImpl uow) {
49: this.uow = uow;
50: this.visitedEntities = new RepositoryMap();
51: this.builders = new Builders();
52: }
53:
54: @Override
55: public Object buildClone(Object original, CloneConfiguration cloneConfiguration) {
56: Objects.requireNonNull(original);
57: Objects.requireNonNull(cloneConfiguration);
58: // TODO Replace with a lambda after migration to new SLF4J
59: LOG.trace("Cloning object {}.", stringify(original));
60: return buildCloneImpl(null, null, original, cloneConfiguration);
61: }
62:
63: @Override
64: public Object buildClone(Object cloneOwner, Field clonedField, Object original, Descriptor descriptor) {
65: if (cloneOwner == null || original == null || descriptor == null) {
66: throw new NullPointerException();
67: }
68: // TODO Replace with a lambda after migration to new SLF4J
69: LOG.trace("Cloning object {} with owner {}", stringify(original), stringify(cloneOwner));
70: return buildCloneImpl(cloneOwner, clonedField, original, new CloneConfiguration(descriptor));
71: }
72:
73: private Object buildCloneImpl(Object cloneOwner, Field clonedField, Object original,
74: CloneConfiguration cloneConfiguration) {
75: assert original != null;
76: if (isOriginalInUoW(original)) {
77: return uow.getCloneForOriginal(original);
78: }
79: final Class<?> cls = original.getClass();
80: final boolean managed = isTypeManaged(cls);
81: final Descriptor descriptor = cloneConfiguration.getDescriptor();
82: if (managed) {
83: final Object visitedClone = getVisitedEntity(descriptor, original);
84: if (visitedClone != null) {
85: return visitedClone;
86: }
87: }
88: final AbstractInstanceBuilder builder = getInstanceBuilder(original);
89: Object clone = builder.buildClone(cloneOwner, clonedField, original, cloneConfiguration);
90: if (managed) {
91: // Register visited object before populating attributes to prevent endless cloning cycles
92: putVisitedEntity(descriptor, original, clone);
93: }
94: if (!builder.populatesAttributes() && !isImmutable(cls)) {
95: populateAttributes(original, clone, cloneConfiguration);
96: }
97: return clone;
98: }
99:
100: /**
101: * Clone all the attributes of the original and set the clone values. This also means cloning any relationships and
102: * their targets.
103: */
104: private void populateAttributes(Object original, Object clone, CloneConfiguration configuration) {
105: final Class<?> originalClass = original.getClass();
106: final EntityType<?> et = getMetamodel().entity(originalClass);
107: // Ensure the identifier is cloned before any other attributes
108: // This prevents problems where circular references between entities lead to clones being registered with null identifier
109: cloneIdentifier(original, clone, et);
110: for (FieldSpecification<?, ?> fs : et.getFieldSpecifications()) {
111: if (fs == et.getIdentifier()) {
112: continue; // Already cloned
113: }
114: final Field f = fs.getJavaField();
115: final Object origVal = EntityPropertiesUtils.getFieldValue(f, original);
116: if (origVal == null) {
117: continue;
118: }
119: final Class<?> origValueClass = origVal.getClass();
120: Object clonedValue;
121: if (isImmutable(origValueClass)) {
122: // The field is an immutable type
123: clonedValue = origVal;
124: } else if (IndirectWrapperHelper.requiresIndirectWrapper(origVal)) {
125: final Descriptor fieldDescriptor = getFieldDescriptor(f, originalClass, configuration.getDescriptor());
126: // Collection or Map
127: clonedValue = getInstanceBuilder(origVal).buildClone(clone, f, origVal,
128: new CloneConfiguration(fieldDescriptor, configuration.getPostRegister()));
129: } else {
130: // Otherwise we have a relationship and we need to clone its target as well
131: if (isOriginalInUoW(origVal)) {
132: // If the reference is already managed
133: clonedValue = uow.getCloneForOriginal(origVal);
134: } else {
135: if (isTypeManaged(origValueClass)) {
136: final Descriptor fieldDescriptor =
137: getFieldDescriptor(f, originalClass, configuration.getDescriptor());
138: clonedValue = getVisitedEntity(configuration.getDescriptor(), origVal);
139: if (clonedValue == null) {
140: clonedValue = uow.registerExistingObject(origVal, fieldDescriptor,
141: configuration.getPostRegister());
142: }
143: } else {
144: clonedValue = buildClone(origVal, configuration);
145: }
146: }
147: }
148: EntityPropertiesUtils.setFieldValue(f, clone, clonedValue);
149: }
150: }
151:
152: private static void cloneIdentifier(Object original, Object clone, EntityType<?> et) {
153: final Identifier<?, ?> identifier = et.getIdentifier();
154: final Object idValue = EntityPropertiesUtils.getFieldValue(identifier.getJavaField(), original);
155: EntityPropertiesUtils.setFieldValue(identifier.getJavaField(), clone, idValue);
156: }
157:
158: private Descriptor getFieldDescriptor(Field field, Class<?> entityClass, Descriptor entityDescriptor) {
159: final EntityType<?> et = getMetamodel().entity(entityClass);
160: final FieldSpecification<?, ?> fieldSpec = et.getFieldSpecification(field.getName());
161: return entityDescriptor.getAttributeDescriptor(fieldSpec);
162: }
163:
164: /**
165: * Check if the given class is an immutable type.
166: * <p>
167: * Objects of immutable types do not have to be cloned, because they cannot be modified.
168: * <p>
169: * Note that this method does not do any sophisticated verification, it just checks if the specified class
170: * corresponds to a small set of predefined conditions, e.g. primitive class, enum, String.
171: *
172: * @param cls the class to check
173: * @return Whether the class represents immutable objects
174: */
175: static boolean isImmutable(Class<?> cls) {
176: return cls.isPrimitive() || cls.isEnum() || IMMUTABLE_TYPES.contains(cls);
177: }
178:
179: /**
180: * Checks if the specified object is immutable.
181: * <p>
182: * {@code null} is considered immutable, otherwise, this method just calls {@link #isImmutable(Class)}.
183: *
184: * @param object The instance to check
185: * @return immutability status
186: */
187: static boolean isImmutable(Object object) {
188: return object == null || isImmutable(object.getClass());
189: }
190:
191: @Override
192: public void mergeChanges(ObjectChangeSet changeSet) {
193: final Object original = changeSet.getChangedObject();
194: try {
195: for (ChangeRecord change : changeSet.getChanges()) {
196: Field f = change.getAttribute().getJavaField();
197: if (isImmutable(f.getType())) {
198: EntityPropertiesUtils.setFieldValue(f, original, change.getNewValue());
199: continue;
200: }
201: Object origVal = EntityPropertiesUtils.getFieldValue(f, original);
202: Object newVal = change.getNewValue();
203: if (newVal == null) {
204: EntityPropertiesUtils.setFieldValue(f, original, null);
205: continue;
206: }
207: getInstanceBuilder(newVal).mergeChanges(f, original, origVal, newVal);
208: }
209: } catch (SecurityException e) {
210: throw new OWLPersistenceException(e);
211: }
212: }
213:
214: private Object getVisitedEntity(Descriptor descriptor, Object original) {
215: assert descriptor != null;
216: assert original != null;
217: return visitedEntities.get(descriptor, original);
218: }
219:
220: private void putVisitedEntity(Descriptor descriptor, Object original, Object clone) {
221: assert descriptor != null;
222: visitedEntities.add(descriptor, original, clone);
223: }
224:
225: AbstractInstanceBuilder getInstanceBuilder(Object toClone) {
226: return builders.getBuilder(toClone);
227: }
228:
229: boolean isTypeManaged(Class<?> cls) {
230: return uow.isEntityType(cls);
231: }
232:
233: boolean isOriginalInUoW(Object original) {
234: return uow.containsOriginal(original);
235: }
236:
237: Object getOriginal(Object clone) {
238: return uow.getOriginal(clone);
239: }
240:
241: Metamodel getMetamodel() {
242: return uow.getMetamodel();
243: }
244:
245: @Override
246: public void reset() {
247: visitedEntities.clear();
248: }
249:
250: @Override
251: public void removeVisited(Object instance, Descriptor descriptor) {
252: visitedEntities.remove(descriptor, instance);
253: }
254:
255: /**
256: * Gets basic object info for logging.
257: * <p>
258: * This works around using {@link Object#toString()} for entities, which could inadvertently trigger lazy field
259: * fetching.
260: *
261: * @param object Object to stringify
262: * @return String info about the specified object
263: */
264: private String stringify(Object object) {
265: assert object != null;
266: return isTypeManaged(object.getClass()) ?
267: object.getClass().getSimpleName() + "<" + EntityPropertiesUtils.getIdentifier(object, getMetamodel()) +
268: ">" : object.toString();
269: }
270:
271: private static Set<Class<?>> getImmutableTypes() {
272: HashSet<Class<?>> ret = new HashSet<>();
273: ret.add(Boolean.class);
274: ret.add(Character.class);
275: ret.add(Byte.class);
276: ret.add(Short.class);
277: ret.add(Integer.class);
278: ret.add(Long.class);
279: ret.add(Float.class);
280: ret.add(Double.class);
281: ret.add(Void.class);
282: ret.add(String.class);
283: ret.add(URI.class);
284: ret.add(URL.class);
285: ret.add(LocalDate.class);
286: ret.add(LocalDateTime.class);
287: ret.add(ZonedDateTime.class);
288: ret.add(Instant.class);
289: return ret;
290: }
291:
292: private final class Builders {
293: private final AbstractInstanceBuilder defaultBuilder;
294: private final AbstractInstanceBuilder dateBuilder;
295: private final AbstractInstanceBuilder multilingualStringBuilder;
296: // Lists and Sets
297: private AbstractInstanceBuilder collectionBuilder;
298: private AbstractInstanceBuilder mapBuilder;
299:
300: private Builders() {
301: this.defaultBuilder = new DefaultInstanceBuilder(CloneBuilderImpl.this, uow);
302: this.dateBuilder = new DateInstanceBuilder(CloneBuilderImpl.this, uow);
303: this.multilingualStringBuilder = new MultilingualStringInstanceBuilder(CloneBuilderImpl.this, uow);
304: }
305:
306: private AbstractInstanceBuilder getBuilder(Object toClone) {
307:• if (toClone instanceof Date) {
308: return dateBuilder;
309: }
310:• if (toClone instanceof MultilingualString) {
311: return multilingualStringBuilder;
312: }
313:• if (toClone instanceof Map) {
314:• if (mapBuilder == null) {
315: this.mapBuilder = new MapInstanceBuilder(CloneBuilderImpl.this, uow);
316: }
317: return mapBuilder;
318:• } else if (toClone instanceof Collection) {
319:• if (collectionBuilder == null) {
320: this.collectionBuilder = new CollectionInstanceBuilder(CloneBuilderImpl.this, uow);
321: }
322: return collectionBuilder;
323: } else {
324: return defaultBuilder;
325: }
326: }
327: }
328: }