All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.indirection.IndirectMap Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * Copyright (c) 1998, 2021 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
package org.eclipse.persistence.indirection;

import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.eclipse.persistence.descriptors.changetracking.CollectionChangeEvent;
import org.eclipse.persistence.descriptors.changetracking.CollectionChangeTracker;
import org.eclipse.persistence.descriptors.changetracking.MapChangeEvent;

/**
 * IndirectMap allows a domain class to take advantage of TopLink indirection
 * without having to declare its instance variable as a ValueHolderInterface.
 * 

To use an IndirectMap:

    *
  • Declare the appropriate instance variable with type Map or Hashtable *
  • Send the message #useTransparentMap(String) to the appropriate * CollectionMapping. *
* EclipseLink will place an * IndirectMap in the instance variable when the containing domain object is read from * the database. With the first message sent to the IndirectMap, the contents * are fetched from the database and normal Hashtable/Map behavior is resumed. * * @param the type of keys maintained by this map * @param the type of mapped values * @see org.eclipse.persistence.mappings.CollectionMapping * @see org.eclipse.persistence.indirection.IndirectList * @author Big Country * @since TOPLink/Java 2.5 */ public class IndirectMap extends Hashtable implements CollectionChangeTracker, IndirectCollection, Map> { /** Reduce type casting */ protected volatile Hashtable delegate; /** Delegate indirection behavior to a value holder */ protected volatile ValueHolderInterface> valueHolder; /** Change tracking listener. */ private transient PropertyChangeListener changeListener; /** The mapping attribute name, used to raise change events. */ private transient String attributeName; /** Store initial size for lazy init. */ protected int initialCapacity = 11; /** Store load factor for lazy init. */ protected float loadFactor = 0.75f; /** * PUBLIC: * Construct a new, empty IndirectMap with a default * capacity and load factor. */ public IndirectMap() { this(11); } /** * PUBLIC: * Construct a new, empty IndirectMap with the specified initial capacity * and default load factor. * * @param initialCapacity the initial capacity of the hashtable */ public IndirectMap(int initialCapacity) { this(initialCapacity, 0.75f); } /** * PUBLIC: * Construct a new, empty IndirectMap with the specified initial * capacity and load factor. * * @param initialCapacity the initial capacity of the hashtable * @param loadFactor a number between 0.0 and 1.0 * @exception IllegalArgumentException if the initial capacity is less * than or equal to zero, or if the load factor is less than * or equal to zero */ public IndirectMap(int initialCapacity, float loadFactor) { super(0); this.initialize(initialCapacity, loadFactor); } /** * PUBLIC: * Construct a new IndirectMap with the same mappings as the given Map. * The IndirectMap is created with a capacity of twice the number of entries * in the given Map or 11 (whichever is greater), and a default load factor, which is 0.75. * @param m a map containing the mappings to use */ public IndirectMap(Map m) { super(0); this.initialize(m); } /** * Return the freshly-built delegate. */ protected Hashtable buildDelegate() { Hashtable value = (Hashtable)getValueHolder().getValue(); if (value == null) { value = new Hashtable<>(this.initialCapacity, this.loadFactor); } return value; } /** * @see java.util.Hashtable#clear() */ @Override public synchronized void clear() { if (hasTrackedPropertyChangeListener()) { Iterator objects = this.keySet().iterator(); while (objects.hasNext()) { K o = objects.next(); objects.remove(); } } else { this.getDelegate().clear(); } } /** * INTERNAL: * clear any changes that have been deferred to instantiation. * Indirect collections with change tracking avoid instantiation on add/remove. */ @Override public void clearDeferredChanges(){ } /** * @see java.util.Hashtable#clone() * This will result in a database query if necessary. */ /* There are 3 situations when clone() is called: 1. The developer actually wants to clone the collection (typically to modify one of the 2 resulting collections). In which case the contents must be read from the database. 2. A UnitOfWork needs a clone (or backup clone) of the collection. But the UnitOfWork checks "instantiation" before cloning collections ("un-instantiated" collections are not cloned). 3. A MergeManager needs an extra copy of the collection (because the "backup" and "target" are the same object?). But the MergeManager checks "instantiation" before merging collections (again, "un-instantiated" collections are not merged). */ @Override public synchronized Object clone() { IndirectMap result = (IndirectMap)super.clone(); result.delegate = (Hashtable)this.getDelegate().clone(); result.valueHolder = new ValueHolder<>(result.delegate); result.attributeName = null; result.changeListener = null; return result; } /** * @see java.util.Hashtable#contains(java.lang.Object) */ @Override public synchronized boolean contains(Object value) { return this.getDelegate().contains(value); } /** * @see java.util.Hashtable#containsKey(java.lang.Object) */ @Override public synchronized boolean containsKey(Object key) { return this.getDelegate().containsKey(key); } /** * @see java.util.Hashtable#containsValue(java.lang.Object) */ @Override public boolean containsValue(Object value) { return this.getDelegate().containsValue(value); } /** * @see java.util.Hashtable#elements() */ @Override public synchronized Enumeration elements() { return this.getDelegate().elements(); } /** * @see java.util.Hashtable#entrySet() */ @Override public Set> entrySet() { return new Set> (){ Set> delegateSet = IndirectMap.this.getDelegate().entrySet(); @Override public int size(){ return this.delegateSet.size(); } @Override public boolean isEmpty(){ return this.delegateSet.isEmpty(); } @Override public boolean contains(Object o){ return this.delegateSet.contains(o); } @Override public Iterator> iterator(){ return new Iterator>() { Iterator> delegateIterator = delegateSet.iterator(); Map.Entry currentObject; @Override public boolean hasNext() { return this.delegateIterator.hasNext(); } @Override public Map.Entry next() { this.currentObject = this.delegateIterator.next(); return this.currentObject; } @Override public void remove() { this.delegateIterator.remove(); if (currentObject != null) { raiseRemoveChangeEvent(currentObject.getKey(), currentObject.getValue()); } } @Override public void forEachRemaining(Consumer> action) { this.delegateIterator.forEachRemaining(action); } }; } @Override public Object[] toArray(){ return this.delegateSet.toArray(); } @Override public T[] toArray(T[] a){ return this.delegateSet.toArray(a); } @Override public boolean add(Map.Entry o){ return this.delegateSet.add(o); } @Override public boolean remove(Object o){ if (!(o instanceof Map.Entry)) { return false; } return (IndirectMap.this.remove(((Map.Entry)o).getKey()) != null); } @Override public boolean containsAll(Collection c){ return this.delegateSet.containsAll(c); } @Override public boolean addAll(Collection> c){ return this.delegateSet.addAll(c); } @Override public boolean retainAll(Collection c){ boolean result = false; Iterator> objects = delegateSet.iterator(); while (objects.hasNext()) { Map.Entry object = objects.next(); if (!c.contains(object)) { objects.remove(); raiseRemoveChangeEvent(object.getKey(), object.getValue()); result = true; } } return result; } @Override public boolean removeAll(Collection c){ boolean result = false; for (Object object : c) { if ( ! (object instanceof Map.Entry)){ continue; } Object removed = IndirectMap.this.remove(((Map.Entry)object).getKey()); if (removed != null){ result = true; } } return result; } @Override public void clear(){ IndirectMap.this.clear(); } @Override public boolean equals(Object o){ return this.delegateSet.equals(o); } @Override public int hashCode(){ return this.delegateSet.hashCode(); } @Override public boolean removeIf(Predicate> filter) { boolean hasChanged = false; Iterator> objects = iterator(); while (objects.hasNext()) { if (filter.test(objects.next())) { objects.remove(); hasChanged |= true; } } return hasChanged; } @Override public Stream> stream() { return this.delegateSet.stream(); } @Override public Stream> parallelStream() { return this.delegateSet.parallelStream(); } @Override public void forEach(Consumer> action) { this.delegateSet.forEach(action); } @Override public Spliterator> spliterator() { return this.delegateSet.spliterator(); } }; } /** * @see java.util.Hashtable#equals(java.lang.Object) */ @Override public synchronized boolean equals(Object o) { return this.getDelegate().equals(o); } /** * @see java.util.Hashtable#get(java.lang.Object) */ @Override public synchronized V get(Object key) { return this.getDelegate().get(key); } /** * INTERNAL: * Check whether the contents have been read from the database. * If they have not, read them and set the delegate. * This method used to be synchronized, which caused deadlock. */ protected Hashtable getDelegate() { Hashtable newDelegate = this.delegate; if (newDelegate == null) { synchronized(this){ newDelegate = this.delegate; if (newDelegate == null) { this.delegate = newDelegate = this.buildDelegate(); } } } return newDelegate; } /** * INTERNAL: * Return the real collection object. * This will force instantiation. */ @Override public Map getDelegateObject() { return getDelegate(); } /** * INTERNAL: * Return the mapping attribute name, used to raise change events. */ @Override public String getTrackedAttributeName() { return attributeName; } /** * Return the property change listener for change tracking. */ @Override public PropertyChangeListener _persistence_getPropertyChangeListener() { return changeListener; } /** * PUBLIC: * Return the valueHolder. * This method used to be synchronized, which caused deadlock. */ @Override public ValueHolderInterface> getValueHolder() { ValueHolderInterface> vh = this.valueHolder; // PERF: lazy initialize value holder and vector as are normally set after creation. if (vh == null) { synchronized(this){ vh = this.valueHolder; if (vh == null) { this.valueHolder = vh = new ValueHolder<>(new Hashtable<>(initialCapacity, loadFactor)); } } } return vh; } /** * @see java.util.Hashtable#hashCode() */ @Override public synchronized int hashCode() { return this.getDelegate().hashCode(); } /** * INTERNAL: * Return if the collection has a property change listener for change tracking. */ public boolean hasTrackedPropertyChangeListener() { return this.changeListener != null; } /** * Initialize the instance. */ protected void initialize(int initialCapacity, float loadFactor) { this.delegate = null; this.loadFactor = loadFactor; this.initialCapacity = initialCapacity; this.valueHolder = null; } /** * Initialize the instance. */ protected void initialize(Map m) { this.delegate = null; Hashtable temp = new Hashtable<>(m); this.valueHolder = new ValueHolder<>(temp); } /** * @see java.util.Hashtable#isEmpty() */ @Override public boolean isEmpty() { return this.getDelegate().isEmpty(); } /** * PUBLIC: * Return whether the contents have been read from the database. */ @Override public boolean isInstantiated() { return this.getValueHolder().isInstantiated(); } /** * @see java.util.Hashtable#keys() */ @Override public synchronized Enumeration keys() { return this.getDelegate().keys(); } /** * @see java.util.Hashtable#keySet() */ @Override public Set keySet() { return new Set (){ Set delegateSet = IndirectMap.this.getDelegate().keySet(); @Override public int size(){ return this.delegateSet.size(); } @Override public boolean isEmpty(){ return this.delegateSet.isEmpty(); } @Override public boolean contains(Object o){ return this.delegateSet.contains(o); } @Override public Iterator iterator(){ return new Iterator() { Iterator delegateIterator = delegateSet.iterator(); K currentObject; @Override public boolean hasNext() { return this.delegateIterator.hasNext(); } @Override public K next() { this.currentObject = this.delegateIterator.next(); return this.currentObject; } @Override public void remove() { IndirectMap.this.raiseRemoveChangeEvent(currentObject, IndirectMap.this.getDelegate().get(currentObject)); this.delegateIterator.remove(); } @Override public void forEachRemaining(Consumer action) { this.delegateIterator.forEachRemaining(action); } }; } @Override public Object[] toArray(){ return this.delegateSet.toArray(); } @Override public Object[] toArray(Object a[]){ return this.delegateSet.toArray(a); } @Override public boolean add(K o){ return this.delegateSet.add(o); } @Override public boolean remove(Object o){ return (IndirectMap.this.remove(o) != null); } @Override public boolean containsAll(Collection c){ return this.delegateSet.containsAll(c); } @Override public boolean addAll(Collection c){ return this.delegateSet.addAll(c); } @Override public boolean retainAll(Collection c){ boolean result = false; Iterator objects = delegateSet.iterator(); while (objects.hasNext()) { Object object = objects.next(); if (!c.contains(object)) { objects.remove(); IndirectMap.this.raiseRemoveChangeEvent(object, IndirectMap.this.getDelegate().get(object)); result = true; } } return result; } @Override public boolean removeAll(Collection c){ boolean result = false; for (Iterator cs = c.iterator(); cs.hasNext(); ){ if (IndirectMap.this.remove(cs.next()) != null ) { result = true; } } return result; } @Override public void clear(){ IndirectMap.this.clear(); } @Override public boolean equals(Object o){ return this.delegateSet.equals(o); } @Override public int hashCode(){ return this.delegateSet.hashCode(); } @Override public boolean removeIf(Predicate filter) { boolean hasChanged = false; Iterator objects = iterator(); while (objects.hasNext()) { if (filter.test(objects.next())) { objects.remove(); hasChanged |= true; } } return hasChanged; } @Override public Stream stream() { return this.delegateSet.stream(); } @Override public Stream parallelStream() { return this.delegateSet.parallelStream(); } @Override public void forEach(Consumer action) { this.delegateSet.forEach(action); } @Override public Spliterator spliterator() { return this.delegateSet.spliterator(); } }; } /** * @see java.util.Hashtable#put(java.lang.Object, java.lang.Object) */ @Override public synchronized V put(K key, V value) { V oldValue = this.getDelegate().put(key, value); if (oldValue != null){ raiseRemoveChangeEvent(key, oldValue); } raiseAddChangeEvent(key, value); return oldValue; } /** * @see java.util.Hashtable#putAll(java.util.Map) */ @Override public synchronized void putAll(Map t) { // Must trigger add events if tracked or uow. if (hasTrackedPropertyChangeListener()) { t.entrySet().stream().forEach((newEntry) -> { this.put(newEntry.getKey(), newEntry.getValue()); }); }else{ this.getDelegate().putAll(t); } } @Override public synchronized V compute(K key, BiFunction remappingFunction) { // Must trigger add events if tracked or uow. if (hasTrackedPropertyChangeListener()) { V oldValue = get(key); V newValue = remappingFunction.apply(key, oldValue); if (oldValue != null ) { if (newValue != null) { put(key, newValue); return newValue; } remove(key); } else { if (newValue != null) { put(key, newValue); return newValue; } } return null; } return getDelegate().compute(key, remappingFunction); } @Override public synchronized V computeIfAbsent(K key, Function mappingFunction) { // Must trigger add events if tracked or uow. if (hasTrackedPropertyChangeListener()) { V oldValue = get(key); if (oldValue == null) { V newValue = mappingFunction.apply(key); if (newValue != null) { put(key, newValue); } return newValue; } return oldValue; } return getDelegate().computeIfAbsent(key, mappingFunction); } @Override public synchronized V computeIfPresent(K key, BiFunction remappingFunction) { // Must trigger add events if tracked or uow. if (hasTrackedPropertyChangeListener()) { if (get(key) != null) { V oldValue = get(key); V newValue = remappingFunction.apply(key, oldValue); if (newValue != null) { put(key, newValue); return newValue; } remove(key); } return null; } return getDelegate().computeIfPresent(key, remappingFunction); } @Override public synchronized void forEach(BiConsumer action) { getDelegate().forEach(action); } @Override public synchronized V getOrDefault(Object key, V defaultValue) { return getDelegate().getOrDefault(key, defaultValue); } @Override public synchronized V merge(K key, V value, BiFunction remappingFunction) { // Must trigger add events if tracked or uow. if (hasTrackedPropertyChangeListener()) { V oldValue = get(key); V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value); if (newValue == null) { remove(key); } else { put(key, newValue); } return newValue; } return getDelegate().merge(key, value, remappingFunction); } @Override public synchronized V putIfAbsent(K key, V value) { // Must trigger add events if tracked or uow. if (hasTrackedPropertyChangeListener()) { V current = getDelegate().get(key); if (current == null) { V v = getDelegate().put(key, value); raiseAddChangeEvent(key, value); return v; } return current; } return getDelegate().putIfAbsent(key, value); } @Override public synchronized boolean remove(Object key, Object value) { // Must trigger add events if tracked or uow. if (hasTrackedPropertyChangeListener()) { Map del = getDelegate(); if (del.containsKey(key) && Objects.equals(del.get(key), value)) { del.remove(key); raiseRemoveChangeEvent(key, value); return true; } return false; } return getDelegate().remove(key, value); } @Override public synchronized V replace(K key, V value) { // Must trigger add events if tracked or uow. if (hasTrackedPropertyChangeListener()) { Map del = getDelegate(); if (del.containsKey(key)) { return put(key, value); } return null; } return getDelegate().replace(key, value); } @Override public synchronized boolean replace(K key, V oldValue, V newValue) { // Must trigger add events if tracked or uow. if (hasTrackedPropertyChangeListener()) { Map del = getDelegate(); if (del.containsKey(key) && Objects.equals(del.get(key), oldValue)) { put(key, newValue); return true; } return false; } return getDelegate().replace(key, oldValue, newValue); } @Override public synchronized void replaceAll(BiFunction function) { // Must trigger add events if tracked or uow. if (hasTrackedPropertyChangeListener()) { getDelegate().entrySet().stream().forEach((entry) -> { K key = entry.getKey(); V oldValue = entry.getValue(); entry.setValue(function.apply(key, entry.getValue())); raiseRemoveChangeEvent(key, oldValue); raiseAddChangeEvent(key, entry.getValue()); }); return; } getDelegate().replaceAll(function); } /** * @see java.util.Hashtable#rehash() */ @Override protected void rehash() { throw new InternalError("unsupported"); } /** * Raise the add change event and relationship maintainence. */ protected void raiseAddChangeEvent(Object key, Object value) { if (hasTrackedPropertyChangeListener()) { _persistence_getPropertyChangeListener().propertyChange(new MapChangeEvent(this, getTrackedAttributeName(), this, key, value, CollectionChangeEvent.ADD, true)); } // this is where relationship maintenance would go } /** * Raise the remove change event. */ protected void raiseRemoveChangeEvent(Object key, Object value) { if (hasTrackedPropertyChangeListener()) { _persistence_getPropertyChangeListener().propertyChange(new MapChangeEvent(this, getTrackedAttributeName(), this, key, value, CollectionChangeEvent.REMOVE, true)); } // this is where relationship maintenance would go } /** * @see java.util.Hashtable#remove(java.lang.Object) */ @Override public synchronized V remove(Object key) { V value = this.getDelegate().remove(key); if (value != null){ raiseRemoveChangeEvent(key, value); } return value; } /** * INTERNAL: * Set the mapping attribute name, used to raise change events. * This is required if the change listener is set. */ @Override public void setTrackedAttributeName(String attributeName) { this.attributeName = attributeName; } /** * INTERNAL: * Set the property change listener for change tracking. */ @Override public void _persistence_setPropertyChangeListener(PropertyChangeListener changeListener) { this.changeListener = changeListener; } /** * INTERNAL: * Set the value holder. */ @Override public void setValueHolder(ValueHolderInterface> valueHolder) { this.delegate = null; this.valueHolder = valueHolder; } /** * @see java.util.Hashtable#size() */ @Override public int size() { return this.getDelegate().size(); } /** * INTERNAL * Set whether this collection should attempt do deal with adds and removes without retrieving the * collection from the dB */ @Override public void setUseLazyInstantiation(boolean useLazyInstantiation){ } /** * INTERNAL: * Return the elements that have been removed before instantiation. */ @Override public Collection> getRemovedElements() { return null; } /** * INTERNAL: * Return the elements that have been added before instantiation. */ @Override public Collection> getAddedElements() { return null; } /** * INTERNAL: * Return if any elements that have been added or removed before instantiation. */ @Override public boolean hasDeferredChanges() { return false; } /** * PUBLIC: * Use the Hashtable.toString(); but wrap it with braces to indicate * there is a bit of indirection. * Don't allow this method to trigger a database read. * @see java.util.Hashtable#toString() */ @Override public String toString() { if (ValueHolderInterface.shouldToStringInstantiate) { return this.getDelegate().toString(); } if (this.isInstantiated()) { return "{" + this.getDelegate().toString() + "}"; } else { return "{" + org.eclipse.persistence.internal.helper.Helper.getShortClassName(this.getClass()) + ": not instantiated}"; } } /** * @see java.util.Hashtable#values() */ @Override public Collection values() { return new Collection() { protected Collection delegateCollection = IndirectMap.this.getDelegate().values(); @Override public int size(){ return delegateCollection.size(); } @Override public boolean isEmpty(){ return delegateCollection.isEmpty(); } @Override public boolean contains(Object o){ return delegateCollection.contains(o); } @Override public Iterator iterator() { return new Iterator() { Iterator delegateIterator = delegateCollection.iterator(); V currentObject; @Override public boolean hasNext() { return this.delegateIterator.hasNext(); } @Override public V next() { this.currentObject = this.delegateIterator.next(); return this.currentObject; } @Override public void remove() { for (Map.Entry entry : IndirectMap.this.getDelegate().entrySet()) { if (entry.getValue().equals(currentObject)){ IndirectMap.this.raiseRemoveChangeEvent(entry.getKey(), entry.getValue()); } } this.delegateIterator.remove(); } @Override public void forEachRemaining(Consumer action) { this.delegateIterator.forEachRemaining(action); } }; } @Override public Object[] toArray(){ return this.delegateCollection.toArray(); } @Override public T[] toArray(T[] a){ return this.delegateCollection.toArray(a); } @Override public boolean add(V o){ return this.delegateCollection.add(o); } @Override public boolean remove(Object o){ for (Iterator> entryIt = IndirectMap.this.getDelegate().entrySet().iterator(); entryIt.hasNext();) { Map.Entry entry = entryIt.next(); if (entry.getValue().equals(o)){ IndirectMap.this.raiseRemoveChangeEvent(entry.getKey(), entry.getValue()); entryIt.remove(); return true; } } return false; } @Override public boolean containsAll(Collection c){ return this.delegateCollection.containsAll(c); } @Override public boolean addAll(Collection c){ return this.delegateCollection.addAll(c); } @Override public boolean removeAll(Collection c){ boolean result = false; for (Iterator iterator = c.iterator(); iterator.hasNext();){ if (remove(iterator.next()) ){ result = true; } } return result; } @Override public boolean retainAll(Collection c){ boolean result = false; for (Iterator> iterator = IndirectMap.this.entrySet().iterator(); iterator.hasNext();){ Map.Entry entry = iterator.next(); if (! c.contains(entry.getValue()) ) { iterator.remove(); result = true; } } return result; } @Override public void clear(){ IndirectMap.this.clear(); } @Override public boolean equals(Object o){ return this.delegateCollection.equals(o); } @Override public int hashCode(){ return this.delegateCollection.hashCode(); } @Override public void forEach(Consumer action) { this.delegateCollection.forEach(action); } @Override public boolean removeIf(Predicate filter) { boolean hasChanged = false; Iterator objects = iterator(); while (objects.hasNext()) { if (filter.test(objects.next())) { objects.remove(); hasChanged |= true; } } return hasChanged; } @Override public Spliterator spliterator() { return this.delegateCollection.spliterator(); } @Override public Stream stream() { return this.delegateCollection.stream(); } @Override public Stream parallelStream() { return this.delegateCollection.parallelStream(); } }; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy