/******************************************************************************* | |
* Copyright (c) 2007, 2015 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.common.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.ListIterator; | |
import org.eclipse.jpt.common.utility.internal.StringBuilderTools; | |
import org.eclipse.jpt.common.utility.internal.collection.ListTools; | |
import org.eclipse.jpt.common.utility.internal.iterator.ReadOnlyListIterator; | |
import org.eclipse.jpt.common.utility.internal.reference.SimpleIntReference; | |
import org.eclipse.jpt.common.utility.model.Model; | |
import org.eclipse.jpt.common.utility.model.event.ListAddEvent; | |
import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; | |
import org.eclipse.jpt.common.utility.model.event.ListClearEvent; | |
import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; | |
import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; | |
import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; | |
import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; | |
import org.eclipse.jpt.common.utility.model.value.ListValueModel; | |
/** | |
* Abstract list value model that provides behavior for wrapping a {@link ListValueModel} | |
* (or {@link CollectionValueModel}) and listening for changes to aspects of the | |
* <em>items</em> 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 list or collection's external appearance. | |
* | |
* Subclasses need to override two methods:<ul> | |
* <li>{@link #engageItem_(Model)}<p> | |
* begin listening to the appropriate aspect of the specified item and call | |
* {@link #itemAspectChanged(EventObject)} whenever the aspect changes | |
* <li>{@link #disengageItem_(Model)}<p> | |
* stop listening to the appropriate aspect of the specified item | |
* </ul> | |
*/ | |
public abstract class ItemAspectListValueModelAdapter<E> | |
extends ListValueModelWrapper<E> | |
implements ListValueModel<E> | |
{ | |
/** | |
* Maintain a counter for each of the items in the | |
* wrapped list holder we are listening to. | |
*/ | |
protected final IdentityHashMap<E, SimpleIntReference> counters; | |
// ********** constructors ********** | |
/** | |
* Constructor - the list holder is required. | |
*/ | |
protected ItemAspectListValueModelAdapter(ListValueModel<? extends E> listHolder) { | |
super(listHolder); | |
this.counters = new IdentityHashMap<E, SimpleIntReference>(); | |
} | |
/** | |
* Constructor - the collection holder is required. | |
*/ | |
protected ItemAspectListValueModelAdapter(CollectionValueModel<? extends E> collectionHolder) { | |
this(new CollectionListValueModelAdapter<E>(collectionHolder)); | |
} | |
// ********** ListValueModel implementation ********** | |
public Iterator<E> iterator() { | |
return this.listIterator(); | |
} | |
public ListIterator<E> listIterator() { | |
return new ReadOnlyListIterator<E>(this.listModel.listIterator()); | |
} | |
public E get(int index) { | |
return this.listModel.get(index); | |
} | |
public int size() { | |
return this.listModel.size(); | |
} | |
public Object[] toArray() { | |
return this.listModel.toArray(); | |
} | |
// ********** 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(this.listModel); | |
} | |
protected void engageItems(Iterable<? extends E> items) { | |
for (E item : items) { | |
this.engageItem(item); | |
} | |
} | |
protected void engageItem(E item) { | |
// listen to each item only once | |
SimpleIntReference counter = this.counters.get(item); | |
if (counter == null) { | |
counter = new SimpleIntReference(); | |
this.counters.put(item, counter); | |
this.engageItem_((Model) item); | |
} | |
counter.increment(); | |
} | |
/** | |
* Start listening to the specified item. | |
*/ | |
protected abstract void engageItem_(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(this.listModel); | |
} | |
protected void disengageItems(Iterable<? extends E> items) { | |
for (E item : items) { | |
this.disengageItem(item); | |
} | |
} | |
protected void disengageItem(E item) { | |
// stop listening to each item only once | |
SimpleIntReference counter = this.counters.get(item); | |
if (counter == null) { | |
// something is wrong if this happens... ~bjv | |
throw new IllegalStateException("missing counter: " + item); //$NON-NLS-1$ | |
} | |
if (counter.decrement() == 0) { | |
this.counters.remove(item); | |
this.disengageItem_((Model) item); | |
} | |
} | |
/** | |
* Stop listening to the specified item. | |
*/ | |
protected abstract void disengageItem_(Model item); | |
@Override | |
public void toString(StringBuilder sb) { | |
StringBuilderTools.append(sb, this); | |
} | |
// ********** 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(ListAddEvent event) { | |
// re-fire event with the wrapper as the source | |
this.fireItemsAdded(event.clone(this, LIST_VALUES)); | |
this.engageItems(this.getItems(event)); | |
} | |
/** | |
* Items were removed from the wrapped list holder. | |
* Stop listening to the removed items and forward the event. | |
*/ | |
@Override | |
protected void itemsRemoved(ListRemoveEvent event) { | |
this.disengageItems(this.getItems(event)); | |
// re-fire event with the wrapper as the source | |
this.fireItemsRemoved(event.clone(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(ListReplaceEvent event) { | |
this.disengageItems(this.getOldItems(event)); | |
// re-fire event with the wrapper as the source | |
this.fireItemsReplaced(event.clone(this, LIST_VALUES)); | |
this.engageItems(this.getNewItems(event)); | |
} | |
/** | |
* Items were moved in the wrapped list holder. | |
* No need to change any listeners; just forward the event. | |
*/ | |
@Override | |
protected void itemsMoved(ListMoveEvent event) { | |
// re-fire event with the wrapper as the source | |
this.fireItemsMoved(event.clone(this, LIST_VALUES)); | |
} | |
/** | |
* The wrapped list holder was cleared. | |
* Stop listening to the removed items and forward the event. | |
*/ | |
@Override | |
protected void listCleared(ListClearEvent event) { | |
// we should only need to disengage each item once... | |
// make a copy to prevent a ConcurrentModificationException | |
Collection<E> keys = new ArrayList<E>(this.counters.keySet()); | |
this.disengageItems(keys); | |
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 event) { | |
// we should only need to disengage each item once... | |
// make a copy to prevent a ConcurrentModificationException | |
Collection<E> keys = new ArrayList<E>(this.counters.keySet()); | |
this.disengageItems(keys); | |
this.counters.clear(); | |
// re-fire event with the wrapper as the source | |
this.fireListChanged(event.clone(this)); | |
this.engageAllItems(); | |
} | |
// ********** item change support ********** | |
/** | |
* The specified item has a bound property that has changed. | |
* Notify listeners of the change. The listeners will have to determine | |
* whether the item aspect change is significant. | |
*/ | |
protected void itemAspectChanged(@SuppressWarnings("unused") EventObject event) { | |
this.fireListChanged(LIST_VALUES, ListTools.arrayList(this.listModel, this.listModel.size())); | |
} | |
} |