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