Package: CloneBuilderImpl
CloneBuilderImpl
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
CloneBuilderImpl(UnitOfWorkImpl) |
|
|
|
|
|
||||||||||||||||||||
buildClone(Object, CloneConfiguration) |
|
|
|
|
|
||||||||||||||||||||
buildClone(Object, Field, Object, Descriptor) |
|
|
|
|
|
||||||||||||||||||||
buildCloneImpl(Object, Field, Object, CloneConfiguration) |
|
|
|
|
|
||||||||||||||||||||
cloneIdentifier(Object, Object, EntityType) |
|
|
|
|
|
||||||||||||||||||||
createIndirectCollection(Object, Object, Field) |
|
|
|
|
|
||||||||||||||||||||
getFieldDescriptor(Field, Class, Descriptor) |
|
|
|
|
|
||||||||||||||||||||
getImmutableTypes() |
|
|
|
|
|
||||||||||||||||||||
getInstanceBuilder(Object) |
|
|
|
|
|
||||||||||||||||||||
getMetamodel() |
|
|
|
|
|
||||||||||||||||||||
getOriginal(Object) |
|
|
|
|
|
||||||||||||||||||||
getVisitedEntity(Descriptor, Object) |
|
|
|
|
|
||||||||||||||||||||
isImmutable(Class) |
|
|
|
|
|
||||||||||||||||||||
isImmutable(Object) |
|
|
|
|
|
||||||||||||||||||||
isOriginalInUoW(Object) |
|
|
|
|
|
||||||||||||||||||||
isTypeManaged(Class) |
|
|
|
|
|
||||||||||||||||||||
mergeChanges(ObjectChangeSet) |
|
|
|
|
|
||||||||||||||||||||
populateAttributes(Object, Object, CloneConfiguration) |
|
|
|
|
|
||||||||||||||||||||
putVisitedEntity(Descriptor, Object, Object) |
|
|
|
|
|
||||||||||||||||||||
removeVisited(Object, Descriptor) |
|
|
|
|
|
||||||||||||||||||||
reset() |
|
|
|
|
|
||||||||||||||||||||
static {...} |
|
|
|
|
|
||||||||||||||||||||
stringify(Object) |
|
|
|
|
|
Coverage
1: /**
2: * Copyright (C) 2019 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.adapters.IndirectCollection;
16: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
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 (origVal instanceof Collection || origVal instanceof Map) {
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: IndirectCollection<?> createIndirectCollection(Object c, Object owner, Field f) {
256: return uow.createIndirectCollection(c, owner, f);
257: }
258:
259: /**
260: * Gets basic object info for logging.
261: * <p>
262: * This works around using {@link Object#toString()} for entities, which could inadvertently trigger lazy field
263: * fetching.
264: *
265: * @param object Object to stringify
266: * @return String info about the specified object
267: */
268: private String stringify(Object object) {
269:• assert object != null;
270:• return isTypeManaged(object.getClass()) ?
271: object.getClass().getSimpleName() + "<" + EntityPropertiesUtils.getIdentifier(object, getMetamodel()) +
272: ">" : object.toString();
273: }
274:
275: private static Set<Class<?>> getImmutableTypes() {
276: HashSet<Class<?>> ret = new HashSet<>();
277: ret.add(Boolean.class);
278: ret.add(Character.class);
279: ret.add(Byte.class);
280: ret.add(Short.class);
281: ret.add(Integer.class);
282: ret.add(Long.class);
283: ret.add(Float.class);
284: ret.add(Double.class);
285: ret.add(Void.class);
286: ret.add(String.class);
287: ret.add(URI.class);
288: ret.add(URL.class);
289: ret.add(LocalDate.class);
290: ret.add(LocalDateTime.class);
291: ret.add(ZonedDateTime.class);
292: ret.add(Instant.class);
293: return ret;
294: }
295:
296: private final class Builders {
297: private AbstractInstanceBuilder defaultBuilder;
298: private AbstractInstanceBuilder dateBuilder;
299: // Lists and Sets
300: private AbstractInstanceBuilder collectionBuilder;
301: private AbstractInstanceBuilder mapBuilder;
302:
303: private Builders() {
304: this.defaultBuilder = new DefaultInstanceBuilder(CloneBuilderImpl.this, uow);
305: this.dateBuilder = new DateInstanceBuilder(CloneBuilderImpl.this, uow);
306: }
307:
308: private AbstractInstanceBuilder getBuilder(Object toClone) {
309: if (toClone instanceof Date) {
310: return dateBuilder;
311: }
312: if (toClone instanceof Map) {
313: if (mapBuilder == null) {
314: this.mapBuilder = new MapInstanceBuilder(CloneBuilderImpl.this, uow);
315: }
316: return mapBuilder;
317: } else if (toClone instanceof Collection) {
318: if (collectionBuilder == null) {
319: this.collectionBuilder = new CollectionInstanceBuilder(CloneBuilderImpl.this, uow);
320: }
321: return collectionBuilder;
322: } else {
323: return defaultBuilder;
324: }
325: }
326: }
327: }