Skip to content

Method: CloneBuilderImpl.Builders(CloneBuilderImpl)

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