Skip to content

Method: buildClone(Object, CloneConfiguration)

1: /**
2: * Copyright (C) 2016 Czech Technical University in Prague
3: * <p>
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: * <p>
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.adapters.IndirectCollection;
18: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
19: import cz.cvut.kbss.jopa.model.annotations.Inferred;
20: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
21: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
22: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
23: import cz.cvut.kbss.jopa.model.metamodel.Identifier;
24: import cz.cvut.kbss.jopa.model.metamodel.Metamodel;
25: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
26: import org.slf4j.Logger;
27: import org.slf4j.LoggerFactory;
28:
29: import java.lang.reflect.Field;
30: import java.net.URI;
31: import java.net.URL;
32: import java.util.*;
33:
34: public class CloneBuilderImpl implements CloneBuilder {
35:
36: private static final Logger LOG = LoggerFactory.getLogger(CloneBuilderImpl.class);
37:
38: private static final Set<Class<?>> IMMUTABLE_TYPES = getImmutableTypes();
39:
40: // Contains entities that are already cloned, so that we don't clone them again
41: private final RepositoryMap visitedEntities;
42:
43: private final Builders builders;
44:
45: private final UnitOfWorkImpl uow;
46:
47: public CloneBuilderImpl(UnitOfWorkImpl uow) {
48: this.uow = uow;
49: this.visitedEntities = new RepositoryMap();
50: this.builders = new Builders();
51: }
52:
53: @Override
54: public Object buildClone(Object original, CloneConfiguration cloneConfiguration) {
55: Objects.requireNonNull(original);
56: Objects.requireNonNull(cloneConfiguration);
57: LOG.trace("Cloning object {}.", original);
58: return buildCloneImpl(null, null, original, cloneConfiguration);
59: }
60:
61: @Override
62: public Object buildClone(Object cloneOwner, Field clonedField, Object original, Descriptor descriptor) {
63: if (cloneOwner == null || original == null || descriptor == null) {
64: throw new NullPointerException();
65: }
66: LOG.trace("Cloning object {} with owner {}", original, cloneOwner);
67: return buildCloneImpl(cloneOwner, clonedField, original, new CloneConfiguration(descriptor));
68: }
69:
70: private Object buildCloneImpl(Object cloneOwner, Field clonedField, Object original,
71: CloneConfiguration cloneConfiguration) {
72: if (isOriginalInUoW(original)) {
73: return uow.getCloneForOriginal(original);
74: }
75: final Class<?> cls = original.getClass();
76: final boolean managed = isTypeManaged(cls);
77: final Descriptor descriptor = cloneConfiguration.getDescriptor();
78: if (managed) {
79: final Object visitedClone = getVisitedEntity(descriptor, original);
80: if (visitedClone != null) {
81: return visitedClone;
82: }
83: }
84: final AbstractInstanceBuilder builder = getInstanceBuilder(original);
85: Object clone = builder.buildClone(cloneOwner, clonedField, original, cloneConfiguration);
86: if (managed) {
87: // Register visited object before populating attributes to prevent endless cloning cycles
88: putVisitedEntity(descriptor, original, clone);
89: }
90: if (!builder.populatesAttributes() && !isImmutable(original.getClass())) {
91: populateAttributes(original, clone, cloneConfiguration);
92: }
93: return clone;
94: }
95:
96: /**
97: * Clone all the attributes of the original and set the clone values. This also means cloning any relationships and
98: * their targets.
99: */
100: private void populateAttributes(Object original, Object clone, CloneConfiguration configuration) {
101: final Class<?> originalClass = original.getClass();
102: final EntityType<?> et = getMetamodel().entity(originalClass);
103: // Ensure the identifier is cloned before any other attributes
104: // This prevents problems where circular references between entities lead to clones being registered with null identifier
105: cloneIdentifier(original, clone, et);
106: for (FieldSpecification<?, ?> fs : et.getFieldSpecifications()) {
107: if (fs == et.getIdentifier()) {
108: continue; // Already cloned
109: }
110: final Field f = fs.getJavaField();
111: final Object origVal = EntityPropertiesUtils.getFieldValue(f, original);
112: if (origVal == null) {
113: continue;
114: }
115: final Class<?> origValueClass = origVal.getClass();
116: Object clonedValue;
117: if (isImmutable(origValueClass)) {
118: // The field is an immutable type
119: clonedValue = origVal;
120: } else if (origVal instanceof Collection || origVal instanceof Map) {
121: final Descriptor fieldDescriptor = getFieldDescriptor(f, originalClass, configuration.getDescriptor());
122: // Collection or Map
123: clonedValue = getInstanceBuilder(origVal).buildClone(clone, f, origVal,
124: new CloneConfiguration(fieldDescriptor, configuration.getPostRegister()));
125: } else {
126: // Otherwise we have a relationship and we need to clone its target as well
127: if (isOriginalInUoW(origVal)) {
128: // If the reference is already managed
129: clonedValue = uow.getCloneForOriginal(origVal);
130: } else {
131: if (isTypeManaged(origValueClass)) {
132: final Descriptor fieldDescriptor =
133: getFieldDescriptor(f, originalClass, configuration.getDescriptor());
134: clonedValue = getVisitedEntity(configuration.getDescriptor(), origVal);
135: if (clonedValue == null) {
136: clonedValue = uow.registerExistingObject(origVal, fieldDescriptor,
137: configuration.getPostRegister());
138: }
139: } else {
140: clonedValue = buildClone(origVal, configuration);
141: }
142: }
143: }
144: EntityPropertiesUtils.setFieldValue(f, clone, clonedValue);
145: }
146: }
147:
148: private void cloneIdentifier(Object original, Object clone, EntityType<?> et) {
149: final Identifier identifier = et.getIdentifier();
150: final Object idValue = EntityPropertiesUtils.getFieldValue(identifier.getJavaField(), original);
151: EntityPropertiesUtils.setFieldValue(identifier.getJavaField(), clone, idValue);
152: }
153:
154: private Descriptor getFieldDescriptor(Field field, Class<?> entityClass, Descriptor entityDescriptor) {
155: final EntityType<?> et = getMetamodel().entity(entityClass);
156: final FieldSpecification<?, ?> fieldSpec = et.getFieldSpecification(field.getName());
157: return entityDescriptor.getAttributeDescriptor(fieldSpec);
158: }
159:
160: /**
161: * Check if the given class is an immutable type. This is used by the {@link
162: * #populateAttributes(Object, Object, CloneConfiguration)} method. If this returns true, the populateAttributes can simply
163: * assign the value.
164: *
165: * @param cls the class to check
166: * @return Whether the class represents immutable objects
167: */
168: static boolean isImmutable(final Class<?> cls) {
169: return cls.isPrimitive() || cls.isEnum() || IMMUTABLE_TYPES.contains(cls);
170: }
171:
172: @Override
173: public void mergeChanges(ObjectChangeSet changeSet) {
174: final Object original = changeSet.getChangedObject();
175: try {
176: for (ChangeRecord change : changeSet.getChanges()) {
177: Field f = change.getAttribute().getJavaField();
178: if (isImmutable(f.getType())) {
179: EntityPropertiesUtils.setFieldValue(f, original, change.getNewValue());
180: continue;
181: }
182: Object origVal = EntityPropertiesUtils.getFieldValue(f, original);
183: Object newVal = change.getNewValue();
184: if (newVal == null) {
185: EntityPropertiesUtils.setFieldValue(f, original, null);
186: continue;
187: }
188: getInstanceBuilder(newVal).mergeChanges(f, original, origVal, newVal);
189: }
190: } catch (SecurityException e) {
191: throw new OWLPersistenceException(e);
192: }
193: }
194:
195: private Object getVisitedEntity(Descriptor descriptor, Object original) {
196: assert descriptor != null;
197: assert original != null;
198: return visitedEntities.get(descriptor, original);
199: }
200:
201: private void putVisitedEntity(Descriptor descriptor, Object original, Object clone) {
202: assert descriptor != null;
203: visitedEntities.add(descriptor, original, clone);
204: }
205:
206: AbstractInstanceBuilder getInstanceBuilder(Object toClone) {
207: return builders.getBuilder(toClone);
208: }
209:
210: boolean isTypeManaged(Class<?> cls) {
211: return uow.isEntityType(cls);
212: }
213:
214: boolean isOriginalInUoW(Object original) {
215: return uow.containsOriginal(original);
216: }
217:
218: Object getOriginal(Object clone) {
219: return uow.getOriginal(clone);
220: }
221:
222: Metamodel getMetamodel() {
223: return uow.getMetamodel();
224: }
225:
226: @Override
227: public void reset() {
228: visitedEntities.clear();
229: }
230:
231: @Override
232: public void removeVisited(Object instance, Descriptor descriptor) {
233: visitedEntities.remove(descriptor, instance);
234: }
235:
236: IndirectCollection<?> createIndirectCollection(Object c, Object owner, Field f) {
237: return uow.createIndirectCollection(c, owner, f);
238: }
239:
240: public static synchronized boolean isFieldInferred(final Field f) {
241: return f.getAnnotation(Inferred.class) != null;
242: }
243:
244: private static Set<Class<?>> getImmutableTypes() {
245: HashSet<Class<?>> ret = new HashSet<>();
246: ret.add(Boolean.class);
247: ret.add(Character.class);
248: ret.add(Byte.class);
249: ret.add(Short.class);
250: ret.add(Integer.class);
251: ret.add(Long.class);
252: ret.add(Float.class);
253: ret.add(Double.class);
254: ret.add(Void.class);
255: ret.add(String.class);
256: ret.add(URI.class);
257: ret.add(URL.class);
258: return ret;
259: }
260:
261: private final class Builders {
262: private AbstractInstanceBuilder defaultBuilder;
263: private AbstractInstanceBuilder dateBuilder;
264: // Lists and Sets
265: private AbstractInstanceBuilder collectionBuilder;
266: private AbstractInstanceBuilder mapBuilder;
267:
268: private Builders() {
269: this.defaultBuilder = new DefaultInstanceBuilder(CloneBuilderImpl.this, uow);
270: this.dateBuilder = new DateInstanceBuilder(CloneBuilderImpl.this, uow);
271: }
272:
273: private AbstractInstanceBuilder getBuilder(Object toClone) {
274: if (toClone instanceof Date) {
275: return dateBuilder;
276: }
277: if (toClone instanceof Map) {
278: if (mapBuilder == null) {
279: this.mapBuilder = new MapInstanceBuilder(CloneBuilderImpl.this, uow);
280: }
281: return mapBuilder;
282: } else if (toClone instanceof Collection) {
283: if (collectionBuilder == null) {
284: this.collectionBuilder = new CollectionInstanceBuilder(CloneBuilderImpl.this, uow);
285: }
286: return collectionBuilder;
287: } else {
288: return defaultBuilder;
289: }
290: }
291: }
292: }