blob: bfdaaac2f643e605456ee0da4255e41f751eaa73 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* 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.Set;
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 datatabase. 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 {
/** Reduce type casting */
protected volatile Hashtable<K, V> delegate;
/** Delegate indirection behavior to a value holder */
protected 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<? 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();
this.raiseRemoveChangeEvent(o, this.get(o));
}
} 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() {
raiseRemoveChangeEvent(currentObject.getKey(), currentObject.getValue());
this.delegateIterator.remove();
}
};
}
@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 objects = delegateSet.iterator();
while (objects.hasNext()) {
Map.Entry object = (Map.Entry)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 (Iterator cs = c.iterator(); cs.hasNext(); ){
Object object = cs.next();
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();
}
};
}
/**
* @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() {
if (delegate == null) {
synchronized(this){
if (delegate == null) {
delegate = this.buildDelegate();
}
}
}
return delegate;
}
/**
* INTERNAL:
* Return the real collection object.
* This will force instantiation.
*/
@Override
public Object 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() {
// PERF: lazy initialize value holder and vector as are normally set after creation.
if (valueHolder == null) {
synchronized(this){
if (valueHolder == null) {
valueHolder = new ValueHolder(new Hashtable<>(initialCapacity, loadFactor));
}
}
}
return valueHolder;
}
/**
* @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 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 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();
}
};
}
/**
* @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()) {
Iterator<? extends K> objects = t.keySet().iterator();
while (objects.hasNext()) {
K key = objects.next();
this.put(key, t.get(key));
}
}else{
this.getDelegate().putAll(t);
}
}
/**
* @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<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() {
Iterator iterator = IndirectMap.this.getDelegate().entrySet().iterator();
while (iterator.hasNext()){
Map.Entry entry = (Map.Entry)iterator.next();
if (entry.getValue().equals(currentObject)){
IndirectMap.this.raiseRemoveChangeEvent(entry.getKey(), entry.getValue());
}
}
this.delegateIterator.remove();
}
};
}
@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){
Iterator iterator = IndirectMap.this.getDelegate().entrySet().iterator();
while (iterator.hasNext()){
Map.Entry entry = (Map.Entry)iterator.next();
if (entry.getValue().equals(o)){
IndirectMap.this.raiseRemoveChangeEvent(entry.getKey(), entry.getValue());
}
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 iterator = IndirectMap.this.entrySet().iterator(); iterator.hasNext();){
Map.Entry entry = (Map.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();
}
};
}
}