Skip to content

Method: cloneObject(Object, Field, Object, CloneConfiguration)

1: /*
2: * JOPA
3: * Copyright (C) 2024 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.proxy.change.ChangeTrackingIndirectCollection;
22: import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectMap;
23: import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration;
24: import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor;
25: import cz.cvut.kbss.jopa.utils.CollectionFactory;
26: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
27:
28: import java.lang.reflect.Constructor;
29: import java.lang.reflect.Field;
30: import java.lang.reflect.InvocationTargetException;
31: import java.util.Collection;
32: import java.util.Collections;
33: import java.util.HashMap;
34: import java.util.Map;
35: import java.util.Map.Entry;
36: import java.util.Optional;
37:
38: class MapInstanceBuilder extends AbstractInstanceBuilder {
39:
40: private static final Class<?> singletonMapClass = Collections.singletonMap(null, null).getClass();
41: private static final Class<?> map1Class = Map.of(new Object(), new Object()).getClass();
42:
43: MapInstanceBuilder(CloneBuilder builder, UnitOfWork uow) {
44: super(builder, uow);
45: }
46:
47: @Override
48: Object buildClone(Object cloneOwner, Field field, Object original, CloneConfiguration configuration) {
49: Map<?, ?> orig = (Map<?, ?>) original;
50: if (original instanceof ChangeTrackingIndirectCollection) {
51: orig = ((ChangeTrackingIndirectCollection<Map<?, ?>>) original).unwrap();
52: }
53: if (orig == Collections.emptyMap()) {
54: return orig;
55: }
56: final Class<?> origCls = orig.getClass();
57: Map<?, ?> clone;
58: clone = cloneUsingDefaultConstructor(cloneOwner, field, origCls, orig, configuration);
59: if (clone == null) {
60: if (singletonMapClass.isInstance(orig) || map1Class.isInstance(orig)) {
61: clone = buildSingletonClone(cloneOwner, field, orig, configuration);
62: } else if (Collections.emptyMap().equals(orig)) {
63: clone = orig;
64: } else {
65: clone = CollectionFactory.createDefaultMap();
66: cloneMapContent(cloneOwner, field, orig, clone, configuration);
67: }
68: }
69: clone = new ChangeTrackingIndirectMap<>(cloneOwner, field, uow, clone);
70: return clone;
71:
72: }
73:
74: private Map<?, ?> cloneUsingDefaultConstructor(Object cloneOwner, Field field, Class<?> origCls, Map<?, ?> original,
75: CloneConfiguration configuration) {
76: Optional<Map<?, ?>> result = createNewInstance(origCls, original.size());
77: result.ifPresent(r -> cloneMapContent(cloneOwner, field, original, r, configuration));
78: return result.orElse(null);
79: }
80:
81: private static Optional<Map<?, ?>> createNewInstance(Class<?> type, int size) {
82: Map<?, ?> result = null;
83: final Class<?>[] types = {int.class};
84: Object[] params;
85: Constructor<?> c = getDeclaredConstructorFor(type, types);
86: if (c != null) {
87: params = new Object[1];
88: params[0] = size;
89: } else {
90: c = getDeclaredConstructorFor(type, null);
91: params = null;
92: }
93: if (c == null) {
94: return Optional.empty();
95: }
96: try {
97: result = (Map<?, ?>) c.newInstance(params);
98: } catch (InstantiationException | IllegalArgumentException | InvocationTargetException e) {
99: throw new OWLPersistenceException(e);
100: } catch (IllegalAccessException e) {
101: logConstructorAccessException(c, e);
102: // Do nothing
103: }
104: return Optional.ofNullable(result);
105: }
106:
107: private Map<?, ?> buildSingletonClone(Object cloneOwner, Field field, Map<?, ?> orig,
108: CloneConfiguration configuration) {
109: Entry<?, ?> e = orig.entrySet().iterator().next();
110: Object key = CloneBuilder.isImmutable(e.getKey()) ? e.getKey() :
111: cloneObject(cloneOwner, field, e.getKey(), configuration);
112: Object value = CloneBuilder.isImmutable(e.getValue()) ? e.getValue() :
113: cloneObject(cloneOwner, field, e.getValue(), configuration);
114: if ((value instanceof Collection || value instanceof Map) && !(value instanceof ChangeTrackingIndirectCollection)) {
115: value = uow.createIndirectCollection(value, cloneOwner, field);
116: }
117: return Collections.singletonMap(key, value);
118: }
119:
120: private void cloneMapContent(Object cloneOwner, Field field, Map<?, ?> source,
121: Map<?, ?> target, CloneConfiguration configuration) {
122: if (source.isEmpty()) {
123: return;
124: }
125: final Map<Object, Object> m = (Map<Object, Object>) target;
126: final Entry<?, ?> tmp = source.entrySet().iterator().next();
127: // Note: If we encounter null -> null mapping first, the whole map will be treated as immutable type map, which can be incorrect
128: final boolean keyPrimitive = CloneBuilder.isImmutable(tmp.getKey());
129: final boolean valuePrimitive = CloneBuilder.isImmutable(tmp.getValue());
130: for (Entry<?, ?> e : source.entrySet()) {
131: Object key;
132: Object value;
133: if (keyPrimitive) {
134: if (valuePrimitive) {
135: m.putAll(source);
136: break;
137: }
138: key = e.getKey();
139: value = cloneObject(cloneOwner, field, e.getValue(), configuration);
140: } else {
141: key = cloneObject(cloneOwner, field, e.getKey(), configuration);
142: value = valuePrimitive ? e.getValue() : cloneObject(cloneOwner, field, e.getValue(), configuration);
143: }
144: m.put(key, value);
145: }
146: }
147:
148: private Object cloneObject(Object owner, Field field, Object obj, CloneConfiguration configuration) {
149: Object clone;
150:• if (obj == null) {
151: clone = null;
152:• } else if (builder.isTypeManaged(obj.getClass())) {
153: clone = uow.registerExistingObject(obj, new CloneRegistrationDescriptor(configuration.getDescriptor()).postCloneHandlers(configuration.getPostRegister()));
154: } else {
155: clone = builder.buildClone(owner, field, obj, configuration.getDescriptor());
156: }
157: return clone;
158: }
159:
160: @Override
161: void mergeChanges(Field field, Object target, Object originalValue, Object cloneValue) {
162: assert (originalValue == null) || (originalValue instanceof Map);
163: assert cloneValue instanceof Map;
164:
165: Map<Object, Object> orig = (Map<Object, Object>) originalValue;
166: final Map<Object, Object> clone = cloneValue instanceof ChangeTrackingIndirectCollection ?
167: ((ChangeTrackingIndirectCollection<Map<Object, Object>>) cloneValue).unwrap() : (Map<Object, Object>) cloneValue;
168: if (orig == null) {
169: orig = (Map<Object, Object>) createNewInstance(clone.getClass(), clone.size()).orElseGet(() -> createDefaultMap(clone.size()));
170: EntityPropertiesUtils.setFieldValue(field, target, orig);
171: }
172: orig.clear();
173: if (clone.isEmpty()) {
174: return;
175: }
176: for (Entry<?, ?> e : clone.entrySet()) {
177: final Object key = e.getKey();
178: final Object value = e.getValue();
179: final Object keyToPut = uow.contains(key) ? builder.getOriginal(key) : key;
180: final Object valueToPut = uow.contains(value) ? builder.getOriginal(value) : value;
181: orig.put(keyToPut, valueToPut);
182: }
183: }
184:
185: private static Map<Object, Object> createDefaultMap(int size) {
186: return new HashMap<>(size > 1 ? size : 16);
187: }
188:
189: @Override
190: boolean populatesAttributes() {
191: return true;
192: }
193: }