Skip to contentMethod: CollectionInstanceBuilder(CloneBuilderImpl, UnitOfWork)
1: /**
2: * Copyright (C) 2016 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.adapters.IndirectCollection;
18: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
19: import cz.cvut.kbss.jopa.model.annotations.Types;
20: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
21: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
22:
23: import java.lang.reflect.Constructor;
24: import java.lang.reflect.Field;
25: import java.lang.reflect.InvocationTargetException;
26: import java.net.URI;
27: import java.security.AccessController;
28: import java.security.PrivilegedActionException;
29: import java.util.*;
30:
31: /**
32: * Special class for cloning collections. Introduced because some Java collection have no no-argument constructor and
33: * thus they must be cloned specially. NOTE: This class may be removed in case a better cloning mechanisms (namely
34: * database mappings and copy policies) is introduced.
35: *
36: * @author kidney
37: */
38: class CollectionInstanceBuilder extends AbstractInstanceBuilder {
39:
40: private static final Class<?> singletonListClass = Collections.singletonList(null).getClass();
41: private static final Class<?> singletonSetClass = Collections.singleton(null).getClass();
42: private static final Class<?> arrayAsListClass = Arrays.asList(new Object()).getClass();
43:
44: CollectionInstanceBuilder(CloneBuilderImpl builder, UnitOfWork uow) {
45: super(builder, uow);
46: this.populates = true;
47: }
48:
49: /**
50: * This method is the entry point for cloning the Java collections. It clones standard collections as well as
51: * immutable collections and singleton collections. </p>
52: * <p>
53: * Currently supported are List and Set.
54: *
55: * @param collection The collection to clone
56: * @return A deep clone of the specified collection
57: */
58: @Override
59: Object buildClone(Object cloneOwner, Field field, Object collection, Descriptor repository)
60: throws OWLPersistenceException {
61: assert (collection instanceof Collection);
62: Collection<?> container = (Collection<?>) collection;
63: if (container instanceof IndirectCollection<?>) {
64: container = (Collection<?>) ((IndirectCollection<?>) container)
65: .getReferencedCollection();
66: }
67: Collection<?> clone = cloneUsingDefaultConstructor(cloneOwner, field, container, repository);
68: if (clone == null) {
69: if (Collections.EMPTY_LIST == container) {
70: return Collections.EMPTY_LIST;
71: }
72: if (Collections.EMPTY_SET == container) {
73: return Collections.EMPTY_SET;
74: }
75: Constructor<?> c;
76: Object element = container.iterator().next();
77: Object[] params = new Object[1];
78: if (!CloneBuilderImpl.isImmutable(element.getClass())) {
79: element = builder.buildClone(element, repository);
80: if (element instanceof Collection || element instanceof Map) {
81: element = builder.createIndirectCollection(element, cloneOwner, field);
82: }
83: }
84: params[0] = element;
85: if (singletonListClass.isInstance(container)) {
86: c = getFirstDeclaredConstructorFor(singletonListClass);
87: } else if (singletonSetClass.isInstance(container)) {
88: c = getFirstDeclaredConstructorFor(singletonSetClass);
89: } else if (arrayAsListClass.isInstance(container)) {
90: final List arrayList = new ArrayList<>(container.size());
91: cloneCollectionContent(cloneOwner, field, container, arrayList, repository);
92: c = getFirstDeclaredConstructorFor(ArrayList.class);
93: params[0] = arrayList;
94: } else {
95: throw new OWLPersistenceException("Encountered unsupported type of collection: "
96: + container.getClass());
97: }
98: try {
99: if (!c.isAccessible()) {
100: c.setAccessible(true);
101: }
102: clone = (Collection<?>) c.newInstance(params);
103: } catch (InstantiationException | IllegalArgumentException | InvocationTargetException e) {
104: throw new OWLPersistenceException(e);
105: } catch (IllegalAccessException e) {
106: logConstructorAccessException(c, e);
107: try {
108: clone = (Collection<?>) AccessController
109: .doPrivileged(new PrivilegedInstanceCreator(c));
110: } catch (PrivilegedActionException ex) {
111: throw new OWLPersistenceException(ex);
112: }
113: }
114: }
115: clone = (Collection<?>) builder.createIndirectCollection(clone, cloneOwner, field);
116: return clone;
117: }
118:
119: /**
120: * Clones the specified collection using its default zero argument constructor. If the specified collection has none
121: * (e. g. like SingletonList), this method returns null.
122: *
123: * @param container The collection to clone.
124: * @return cloned collection
125: */
126: private Collection<?> cloneUsingDefaultConstructor(Object cloneOwner, Field field,
127: Collection<?> container, Descriptor repository) {
128: Class<?> javaClass = container.getClass();
129: Collection<?> result = createNewInstance(javaClass, container.size());
130: if (result != null) {
131: // Makes shallow copy
132: cloneCollectionContent(cloneOwner, field, container, result, repository);
133: }
134: return result;
135: }
136:
137: private Collection<?> createNewInstance(Class<?> type, int size) {
138: Object[] params = null;
139: Class<?>[] types = {int.class};
140: // Look for constructor taking initial size as parameter
141: Constructor<?> ctor = getDeclaredConstructorFor(type, types);
142: if (ctor != null) {
143: params = new Object[1];
144: params[0] = size;
145: } else {
146: ctor = DefaultInstanceBuilder.getDeclaredConstructorFor(type, null);
147: }
148: if (ctor == null) {
149: return null;
150: }
151: Collection<?> result = null;
152: try {
153: result = (Collection<?>) ctor.newInstance(params);
154: } catch (InstantiationException | InvocationTargetException | IllegalArgumentException e) {
155: throw new OWLPersistenceException(e);
156: } catch (IllegalAccessException e) {
157: logConstructorAccessException(ctor, e);
158: try {
159: result = (Collection<?>) AccessController
160: .doPrivileged(new PrivilegedInstanceCreator(ctor));
161: } catch (PrivilegedActionException ex) {
162: logPrivilegedConstructorAccessException(ctor, ex);
163: // Do nothing
164: }
165: }
166: return result;
167: }
168:
169: /**
170: * Clone all the elements in the collection. This will make sure that the cloning process creates a deep copy.
171: *
172: * @param source The collection to clone.
173: */
174: private void cloneCollectionContent(Object cloneOwner, Field field, Collection<?> source,
175: Collection<?> target, Descriptor descriptor) {
176: if (source.isEmpty()) {
177: return;
178: }
179: Collection<Object> tg = (Collection<Object>) target;
180: for (Object obj : source) {
181: if (obj == null) {
182: tg.add(null);
183: continue;
184: }
185: if (CloneBuilderImpl.isImmutable(obj.getClass())) {
186: tg.addAll(source);
187: break;
188: }
189: Object clone;
190: if (builder.isTypeManaged(obj.getClass())) {
191: clone = uow.registerExistingObject(obj, descriptor);
192: } else {
193: clone = builder.buildClone(cloneOwner, field, obj, descriptor);
194: }
195: tg.add(clone);
196: }
197: }
198:
199: @Override
200: void mergeChanges(Field field, Object target, Object originalValue, Object cloneValue) {
201: assert (originalValue == null || originalValue instanceof Collection);
202: assert cloneValue instanceof Collection;
203:
204: Collection<Object> orig = (Collection<Object>) originalValue;
205: Collection<Object> clone = (Collection<Object>) cloneValue;
206: if (clone instanceof IndirectCollection) {
207: clone = ((IndirectCollection<Collection<Object>>) clone).getReferencedCollection();
208: }
209: if (originalValue == null) {
210: orig = (Collection<Object>) createNewInstance(clone.getClass(), clone.size());
211: if (orig == null) {
212: orig = createDefaultCollection(clone.getClass());
213: }
214: EntityPropertiesUtils.setFieldValue(field, target, orig);
215: }
216: orig.clear();
217: if (clone.isEmpty()) {
218: return;
219: }
220: for (Object cl : clone) {
221: orig.add(uow.contains(cl) ? builder.getOriginal(cl) : cl);
222: }
223: final Types types = field.getAnnotation(Types.class);
224: if (types != null) {
225: checkForNewTypes(orig);
226: }
227: }
228:
229: private Collection<Object> createDefaultCollection(Class<?> cls) {
230: if (Set.class.isAssignableFrom(cls)) {
231: return new HashSet<>();
232: } else if (List.class.isAssignableFrom(cls)) {
233: return new ArrayList<>();
234: } else {
235: throw new IllegalArgumentException("Unsupported type of collection: " + cls);
236: }
237: }
238:
239: /**
240: * Checks if new types were added to the specified collection. </p>
241: * <p>
242: * If so, they are added to the module extraction signature managed by Metamodel.
243: *
244: * @param collection The collection to check
245: * @see Types
246: */
247: private void checkForNewTypes(Collection<?> collection) {
248: assert collection != null;
249: if (collection.isEmpty()) {
250: return;
251: }
252: final Set<URI> signature = builder.getMetamodel().getModuleExtractionExtraSignature();
253: for (Object elem : collection) {
254: final URI u = EntityPropertiesUtils.getValueAsURI(elem);
255: if (!signature.contains(u)) {
256: builder.getMetamodel().addUriToModuleExtractionSignature(u);
257: }
258: }
259: }
260: }