| /* |
| * 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. |
| * <p>To use an IndirectMap:<ul> |
| * <li> Declare the appropriate instance variable with type Map or Hashtable |
| * <li> Send the message #useTransparentMap(String) to the appropriate |
| * CollectionMapping. |
| * </ul> |
| * 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 <K> the type of keys maintained by this map |
| * @param <V> 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<K, V> extends Hashtable<K, V> implements CollectionChangeTracker, IndirectCollection<Map.Entry<K, V>, Map<K, V>> { |
| |
| /** Reduce type casting */ |
| protected volatile Hashtable<K, V> delegate; |
| |
| /** Delegate indirection behavior to a value holder */ |
| protected volatile ValueHolderInterface<Map<K, V>> 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<? extends K, ? extends V> m) { |
| super(0); |
| this.initialize(m); |
| } |
| |
| /** |
| * Return the freshly-built delegate. |
| */ |
| protected Hashtable<K, V> buildDelegate() { |
| Hashtable<K, V> value = (Hashtable<K, V>)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<K> 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<K, V> result = (IndirectMap<K, V>)super.clone(); |
| result.delegate = (Hashtable<K, V>)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<V> elements() { |
| return this.getDelegate().elements(); |
| } |
| |
| /** |
| * @see java.util.Hashtable#entrySet() |
| */ |
| @Override |
| public Set<Map.Entry<K,V>> entrySet() { |
| return new Set<Map.Entry<K,V>> (){ |
| Set<Map.Entry<K,V>> 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<Map.Entry<K,V>> iterator(){ |
| return new Iterator<Map.Entry<K,V>>() { |
| Iterator<Map.Entry<K, V>> delegateIterator = delegateSet.iterator(); |
| Map.Entry<K, V> currentObject; |
| |
| @Override |
| public boolean hasNext() { |
| return this.delegateIterator.hasNext(); |
| } |
| |
| @Override |
| public Map.Entry<K, V> 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<? super java.util.Map.Entry<K, V>> action) { |
| this.delegateIterator.forEachRemaining(action); |
| } |
| }; |
| } |
| |
| @Override |
| public Object[] toArray(){ |
| return this.delegateSet.toArray(); |
| } |
| |
| @Override |
| public <T> T[] toArray(T[] a){ |
| return this.delegateSet.toArray(a); |
| } |
| |
| @Override |
| public boolean add(Map.Entry<K, V> 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<? extends Map.Entry<K, V>> c){ |
| return this.delegateSet.addAll(c); |
| } |
| |
| @Override |
| public boolean retainAll(Collection<?> c){ |
| boolean result = false; |
| Iterator<Map.Entry<K, V>> objects = delegateSet.iterator(); |
| while (objects.hasNext()) { |
| Map.Entry<K, V> 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<? super Map.Entry<K, V>> filter) { |
| boolean hasChanged = false; |
| Iterator<Map.Entry<K, V>> objects = iterator(); |
| while (objects.hasNext()) { |
| if (filter.test(objects.next())) { |
| objects.remove(); |
| hasChanged |= true; |
| } |
| } |
| return hasChanged; |
| } |
| |
| @Override |
| public Stream<Map.Entry<K, V>> stream() { |
| return this.delegateSet.stream(); |
| } |
| |
| @Override |
| public Stream<java.util.Map.Entry<K, V>> parallelStream() { |
| return this.delegateSet.parallelStream(); |
| } |
| |
| @Override |
| public void forEach(Consumer<? super java.util.Map.Entry<K, V>> action) { |
| this.delegateSet.forEach(action); |
| } |
| |
| @Override |
| public Spliterator<java.util.Map.Entry<K, V>> 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<K, V> getDelegate() { |
| Hashtable<K, V> 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<K, V> 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<Map<K, V>> getValueHolder() { |
| ValueHolderInterface<Map<K, V>> 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<? extends K, ? extends V> m) { |
| this.delegate = null; |
| Hashtable<K, V> 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<K> keys() { |
| return this.getDelegate().keys(); |
| } |
| |
| /** |
| * @see java.util.Hashtable#keySet() |
| */ |
| @Override |
| public Set<K> keySet() { |
| |
| return new Set<K> (){ |
| Set<K> 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<K> iterator(){ |
| return new Iterator<K>() { |
| Iterator<K> 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<? super K> 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<? extends K> c){ |
| return this.delegateSet.addAll(c); |
| } |
| |
| @Override |
| public boolean retainAll(Collection<?> c){ |
| boolean result = false; |
| Iterator<K> 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<? super K> filter) { |
| boolean hasChanged = false; |
| Iterator<K> objects = iterator(); |
| while (objects.hasNext()) { |
| if (filter.test(objects.next())) { |
| objects.remove(); |
| hasChanged |= true; |
| } |
| } |
| return hasChanged; |
| } |
| |
| @Override |
| public Stream<K> stream() { |
| return this.delegateSet.stream(); |
| } |
| |
| @Override |
| public Stream<K> parallelStream() { |
| return this.delegateSet.parallelStream(); |
| } |
| |
| @Override |
| public void forEach(Consumer<? super K> action) { |
| this.delegateSet.forEach(action); |
| } |
| |
| @Override |
| public Spliterator<K> 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<? extends K,? extends V> 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<? super K,? super V,? extends V> 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<? super K,? extends V> 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<? super K,? super V,? extends V> 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<? super K,? super V> 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<? super V,? super V,? extends V> 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<K, V> 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<K, V> 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<K, V> 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<? super K,? super V,? extends V> 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<Map<K, V>> 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<Map.Entry<K, V>> getRemovedElements() { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the elements that have been added before instantiation. |
| */ |
| @Override |
| public Collection<Map.Entry<K, V>> 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<V> values() { |
| return new Collection<V>() { |
| protected Collection<V> 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<V> iterator() { |
| return new Iterator<V>() { |
| Iterator<V> 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<? super V> action) { |
| this.delegateIterator.forEachRemaining(action); |
| } |
| }; |
| } |
| |
| @Override |
| public Object[] toArray(){ |
| return this.delegateCollection.toArray(); |
| } |
| |
| @Override |
| public <T> 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<Map.Entry<K, V>> entryIt = IndirectMap.this.getDelegate().entrySet().iterator(); entryIt.hasNext();) { |
| Map.Entry<K, V> 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<? extends V> 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<Map.Entry<K, V>> iterator = IndirectMap.this.entrySet().iterator(); iterator.hasNext();){ |
| Map.Entry<K, V> 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<? super V> action) { |
| this.delegateCollection.forEach(action); |
| } |
| |
| @Override |
| public boolean removeIf(Predicate<? super V> filter) { |
| boolean hasChanged = false; |
| Iterator<V> objects = iterator(); |
| while (objects.hasNext()) { |
| if (filter.test(objects.next())) { |
| objects.remove(); |
| hasChanged |= true; |
| } |
| } |
| return hasChanged; |
| } |
| |
| @Override |
| public Spliterator<V> spliterator() { |
| return this.delegateCollection.spliterator(); |
| } |
| |
| @Override |
| public Stream<V> stream() { |
| return this.delegateCollection.stream(); |
| } |
| |
| @Override |
| public Stream<V> parallelStream() { |
| return this.delegateCollection.parallelStream(); |
| } |
| }; |
| } |
| } |