org.eclipse.persistence.internal.oxm.ReferenceResolver Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// Marcel Valovy - major speed up, major refurbishing.
package org.eclipse.persistence.internal.oxm;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.Callable;
import org.eclipse.persistence.core.descriptors.CoreDescriptor;
import org.eclipse.persistence.core.descriptors.CoreInheritancePolicy;
import org.eclipse.persistence.core.mappings.CoreAttributeAccessor;
import org.eclipse.persistence.core.mappings.CoreMapping;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.core.queries.CoreContainerPolicy;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.oxm.mappings.CollectionReferenceMapping;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.mappings.InverseReferenceMapping;
import org.eclipse.persistence.internal.oxm.mappings.Mapping;
import org.eclipse.persistence.internal.oxm.mappings.ObjectReferenceMapping;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* This class is leveraged by reference mappings. It plays 3 roles:
*
* - Stores objects with an ID
* - Stores key based relationships
* - Resolves key based relationships based on the objects stored by ID
*
*/
public final class ReferenceResolver {
/**
* The default initial capacity of map - must be a power of two.
*/
private static final int MAP_INITIAL_CAPACITY = 1 << 6; // aka 64
/**
* The default initial capacity of list - must be a power of two.
*/
private static final int LIST_INITIAL_CAPACITY = 1 << 3; // aka 8
/**
* This field is in fact used locally only. We're just saving resources by reusing one instance of the object.
*/
private final ReferenceKey refKey;
/**
* Stores References.
*/
private LinkedHashMap referencesMap;
/**
* Stores References that have been kicked out of the {@link #referencesMap} because a Reference with same key
* was added to it.
*/
private List unluckyReferences;
/**
* Stores positions of References that have been kicked out of the {@link #referencesMap}. The position is
* the same this Reference would have if this code would run on a list instead of map.
*/
private List unluckyRefPositions;
/**
* Speed-up cache that was introduced in 2.5 instead of the previous speed-up mechanisms using session cache.
*/
private Map> cache;
/**
* The default constructor initializes the list of References.
*/
public ReferenceResolver() {
referencesMap = new LinkedHashMap<>(MAP_INITIAL_CAPACITY);
unluckyReferences = new ArrayList<>(MAP_INITIAL_CAPACITY);
unluckyRefPositions = new ArrayList<>(MAP_INITIAL_CAPACITY);
cache = new HashMap<>(MAP_INITIAL_CAPACITY);
refKey = new ReferenceKey();
}
/* Shows why the containers are not final. Keep this private method up here. */
/**
* Resets the references containers.
*
* PERF:
* Allocating a new object may be faster than clearing old objects, especially in this case.
*
* As 'Stephen C' points out: ,,There are locality and cross-generational issues that could affect performance. When
* you repeatedly recycle an ArrayList, the object and its backing array are likely to be tenured. That means that:
*
* - The list objects and the objects representing list elements are likely to be in different areas of the
* heap, potentially increasing TLB misses and page traffic, especially at GC time.
* - Assignment of (young generation) references into the (tenured) list's backing array are likely to incur
* write barrier overheads ... depending on the GC implementation."
*
* from Stack Overflow.
*
* Taking last size can give approximate prediction of the next size.
* Halving provides convergence and increases efficiency. Since the list will be empty after, it's efficient.
*/
private void resetContainers() {
referencesMap = new LinkedHashMap<>(Math.max(referencesMap.size() / 2, MAP_INITIAL_CAPACITY));
unluckyReferences = new ArrayList<>(Math.max(unluckyReferences.size() / 2, LIST_INITIAL_CAPACITY));
unluckyRefPositions = new ArrayList<>(unluckyReferences.size());
cache = new HashMap<>(Math.max(cache.size() / 2, MAP_INITIAL_CAPACITY));
}
/**
* Add a Reference object to the list - these References will
* be resolved after unmarshalling is complete.
*
* #############################
* # Strategy - Hash Collision #
* #############################
* Suppose that hashing function is h(k) = k % 9;
* __________Input 9 entries_________________________
* Key k | 0, 8, 7, 5, 5, 5, 1, 14, 3 |
* Position# p | 0, 1, 2, 3, 4, 5, 6, 7, 8 |
* Value = p * 3 | 0, 3, 6, 9, 12, 15, 18, 21, 24 |
* --------------------------------------------------
* e.g. eighth entry is Entry#7{ key = 14, value = 21 }
*
* ####################################
* # Insert element - O(1) guaranteed #
* ####################################
* Processing the 9th key:
*
* 1. Attempt to insert Entry#7 with key '14' into map of references.
* > h(14) = 5;
* HashMap buckets:
* position | 0 1 2 3 4 5 6 7 8
* entry(key) | 0 1 3 5 7 8
* ^
* > Bucket 5 is taken.
*
* 2. Store the entry in a separate list.
* List for unlucky references:
* position | 0 1 2
* entry(key) | 5 5
* ^
*
* position | 0 1 2
* entry(key) | 5 5 14
* ^
* 3. Store the position # p of this element, i.e. what spot it would have
* taken if all entries were stored in a position list, counting from zero.
*
* List storing position # of unlucky references:
* position | 0 1 2
* entry # (p) | 4 5
* ^
*
* position | 0 1 2
* entry # (p) | 4 5 7
* ^
*
* #####################################################
* # Retrieve element - O(1) expected, O(n) worst case #
* #####################################################
* Retrieve entry with key '14'
* 1. Attempt to retrieve it from map
* > h(14) = 5;
* HashMap buckets:
* position | 0 1 2 3 4 5 6 7 8
* entry(key) | 0 1 3 5 7 8
* ^
* Hash function points to bucket # 5. Stored key is 5.
* > key 5 != 14.
*
* 2. Iterate through list of unluckyReferences, comparing
* key to all keys in the list.
*
* position | 0 1 2
* entry(key) | 5 5 14
* ^
* > key 5 != 14
*
* position | 0 1 2
* entry(key) | 5 5 14
* ^
*
* > key 5 != 14
*
* position | 0 1 2
* entry(key) | 5 5 14
* ^
*
* > key 14 = 14, retrieve entry.
*
* ##################################################
* # Iterate through all elements - O(n) guaranteed #
* ##################################################
*
* 1. Create boolean array of size n that keeps track
* of unlucky positions:
* > boolean[] a = new boolean[lastPosition + 1];
*
* 2. Set a[p] = true for elements that did not fit into
* hash map, p = position # of element.
*
* > for (Integer p : unluckyRefPositions) {
* > a[p] = true;
* > }
*
* 3. Iterate through LinkedMap and List as if they were one joined collection
* of size s = map.size() + list.size(), ordered by p = position # of element:
* > for (p = 0; p < s; p ++) {
* > if a[p] = false, take next element from linked map iterator,
* > if a[p] = true, take next element from list iterator,
* > }
*
*/
public final void addReference(final Reference ref) {
final ReferenceKey key = new ReferenceKey(ref);
/* If an entry with equal key is already present in map, we preserve the original entry and
* note the position of the new entry into the list of positions and put the new misfit value
* into the list of unluckyReferences. */
final Reference previous = referencesMap.get(key);
if (previous != null || ref.getSourceObject() instanceof Collection) {
unluckyReferences.add(ref);
// The input integer represents the position (starting from 0) of the new element that didn't fit into the map.
unluckyRefPositions.add(referencesMap.size() + unluckyReferences.size() - 1);
} else {
referencesMap.put(key, ref);
}
}
/**
* Retrieve the reference for a given mapping instance. If more keys match, returns the first occurrence.
*/
public final Reference getReference(final ObjectReferenceMapping mapping, final Object sourceObject) {
refKey.setMapping(mapping);
refKey.setSourceObject(sourceObject);
final Reference reference = referencesMap.get(refKey);
if (reference != null) {
return reference;
}
// Search for unlucky references that were kicked out of hashMap by entries with equal key.
for (Reference reference1 : unluckyReferences) {
if (reference1.getMapping() == mapping && reference1.getSourceObject() == sourceObject) {
return reference1;
}
}
return null;
}
/**
* Return a reference for the given mapping and source object, that doesn't already
* contain an entry for the provided field.
*/
public final Reference getReference(final ObjectReferenceMapping mapping, final Object sourceObject,
final Field xmlField) {
final Field targetField = (Field) mapping.getSourceToTargetKeyFieldAssociations().get(xmlField);
String tgtXpath = null;
if (!(mapping.getReferenceClass() == null || mapping.getReferenceClass() == Object.class)) {
if (targetField != null) {
tgtXpath = targetField.getXPath();
}
}
final ReferenceKey key = new ReferenceKey(sourceObject, mapping);
Reference reference = referencesMap.get(key);
if (reference != null && reference.getPrimaryKeyMap().get(tgtXpath) == null) {
return reference;
}
// Search for unlucky references that were kicked out of hashMap by entries with equal key.
for (Reference reference1 : unluckyReferences) {
if (reference1.getMapping() == mapping && reference1.getSourceObject() == sourceObject) {
if (reference1.getPrimaryKeyMap().get(tgtXpath) == null) {
return reference1;
}
}
}
return null;
}
/**
* Store an instance by key based on a mapped class. These values will be
* used when it comes time to resolve the references.
*
* @since EclipseLink 2.5.0
*/
public final void putValue(final Class clazz, final Object key, final Object object) {
Map