blob: 359cd6c7e3fce12f1e421ec865d34f8650ac7e59 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.utility.internal.model.value;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.jpt.utility.internal.Counter;
import org.eclipse.jpt.utility.internal.model.Model;
import org.eclipse.jpt.utility.internal.model.event.ListChangeEvent;
/**
* Abstract list value model that provides behavior for wrapping a list value
* model (or collection value model) and listening for changes to aspects of the
* *items* held by the list (or collection). Changes to the actual list
* (or collection) are also monitored.
*
* This is useful if you have a collection of items that can be modified by adding
* or removing items or the items themselves might change in a fashion that
* might change the collection's external appearance.
*
* Subclasses need to override two methods:
*
* listenToItem(Model)
* begin listening to the appropriate aspect of the specified item and call
* #itemAspectChanged(Object) whenever the aspect changes
*
* stopListeningToItem(Model)
* stop listening to the appropriate aspect of the specified item
*/
public abstract class ItemAspectListValueModelAdapter
extends ListValueModelWrapper
{
/**
* Maintain a counter for each of the items in the
* wrapped list holder we are listening to.
*/
protected final IdentityHashMap counters;
// ********** constructors **********
/**
* Constructor - the list holder is required.
*/
protected ItemAspectListValueModelAdapter(ListValueModel listHolder) {
super(listHolder);
this.counters = new IdentityHashMap();
}
/**
* Constructor - the collection holder is required.
*/
protected ItemAspectListValueModelAdapter(CollectionValueModel collectionHolder) {
this(new CollectionListValueModelAdapter(collectionHolder));
}
// ********** ListValueModel implementation **********
public ListIterator values() {
return this.listHolder.values();
}
public void add(int index, Object item) {
this.listHolder.add(index, item);
}
public void addAll(int index, List items) {
this.listHolder.addAll(index, items);
}
public Object remove(int index) {
return this.listHolder.remove(index);
}
public List remove(int index, int length) {
return this.listHolder.remove(index, length);
}
public Object replace(int index, Object item) {
return this.listHolder.replace(index, item);
}
public List replaceAll(int index, List items) {
return this.listHolder.replaceAll(index, items);
}
public Object get(int index) {
return this.listHolder.get(index);
}
public int size() {
return this.listHolder.size();
}
// ********** behavior **********
/**
* Start listening to the list holder and the items in the list.
*/
@Override
protected void engageModel() {
super.engageModel();
this.engageAllItems();
}
protected void engageAllItems() {
this.engageItems((ListIterator) this.listHolder.values());
}
protected void engageItems(Iterator stream) {
while (stream.hasNext()) {
this.engageItem(stream.next());
}
}
protected void engageItem(Object item) {
// listen to an item only once
Counter counter = (Counter) this.counters.get(item);
if (counter == null) {
counter = new Counter();
this.counters.put(item, counter);
this.startListeningToItem((Model) item);
}
counter.increment();
}
/**
* Start listening to the specified item.
*/
protected abstract void startListeningToItem(Model item);
/**
* Stop listening to the list holder and the items in the list.
*/
@Override
protected void disengageModel() {
this.disengageAllItems();
super.disengageModel();
}
protected void disengageAllItems() {
this.disengageItems((ListIterator) this.listHolder.values());
}
protected void disengageItems(Iterator stream) {
while (stream.hasNext()) {
this.disengageItem(stream.next());
}
}
protected void disengageItem(Object item) {
// stop listening to an item only once
Counter counter = (Counter) this.counters.get(item);
if (counter == null) {
// something is wrong if this happens... ~bjv
throw new IllegalStateException("missing counter: " + item);
}
if (counter.decrement() == 0) {
this.counters.remove(item);
this.stopListeningToItem((Model) item);
}
}
/**
* Stop listening to the specified item.
*/
protected abstract void stopListeningToItem(Model item);
// ********** list change support **********
/**
* Items were added to the wrapped list holder.
* Forward the event and begin listening to the added items.
*/
@Override
protected void itemsAdded(ListChangeEvent e) {
// re-fire event with the wrapper as the source
this.fireItemsAdded(e.cloneWithSource(this, LIST_VALUES));
this.engageItems(e.items());
}
/**
* Items were removed from the wrapped list holder.
* Stop listening to the removed items and forward the event.
*/
@Override
protected void itemsRemoved(ListChangeEvent e) {
this.disengageItems(e.items());
// re-fire event with the wrapper as the source
this.fireItemsRemoved(e.cloneWithSource(this, LIST_VALUES));
}
/**
* Items were replaced in the wrapped list holder.
* Stop listening to the removed items, forward the event,
* and begin listening to the added items.
*/
@Override
protected void itemsReplaced(ListChangeEvent e) {
this.disengageItems(e.replacedItems());
// re-fire event with the wrapper as the source
this.fireItemsReplaced(e.cloneWithSource(this, LIST_VALUES));
this.engageItems(e.items());
}
/**
* Items were moved in the wrapped list holder.
* No need to change any listeners; just forward the event.
*/
@Override
protected void itemsMoved(ListChangeEvent e) {
// re-fire event with the wrapper as the source
this.fireItemsMoved(e.cloneWithSource(this, LIST_VALUES));
}
/**
* The wrapped list holder was cleared.
* Stop listening to the removed items and forward the event.
*/
@Override
protected void listCleared(ListChangeEvent e) {
// we should only need to disengage each item once...
// make a copy to prevent a ConcurrentModificationException
Collection keys = new ArrayList(this.counters.keySet());
this.disengageItems(keys.iterator());
this.counters.clear();
// re-fire event with the wrapper as the source
this.fireListCleared(LIST_VALUES);
}
/**
* The wrapped list holder has changed in some dramatic fashion.
* Reconfigure our listeners and forward the event.
*/
@Override
protected void listChanged(ListChangeEvent e) {
// we should only need to disengage each item once...
// make a copy to prevent a ConcurrentModificationException
Collection keys = new ArrayList(this.counters.keySet());
this.disengageItems(keys.iterator());
this.counters.clear();
// re-fire event with the wrapper as the source
this.fireListChanged(LIST_VALUES);
this.engageAllItems();
}
// ********** item change support **********
/**
* The specified item has a bound property that has changed.
* Notify listeners of the change.
*/
protected void itemAspectChanged(EventObject e) {
Object item = e.getSource();
int index = this.lastIdentityIndexOf(item);
while (index != -1) {
this.itemAspectChanged(index, item);
index = this.lastIdentityIndexOf(item, index);
}
}
/**
* The specified item has a bound property that has changed.
* Notify listeners of the change.
*/
protected void itemAspectChanged(int index, Object item) {
this.fireItemReplaced(LIST_VALUES, index, item, item); // hmmm...
}
/**
* Return the last index of the specified item, using object
* identity instead of equality.
*/
protected int lastIdentityIndexOf(Object o) {
return this.lastIdentityIndexOf(o, this.listHolder.size());
}
/**
* Return the last index of the specified item, starting just before the
* the specified endpoint, and using object identity instead of equality.
*/
protected int lastIdentityIndexOf(Object o, int end) {
for (int i = end; i-- > 0; ) {
if (this.listHolder.get(i) == o) {
return i;
}
}
return -1;
}
}