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