blob: cf96b57faa6690792787cbbb86eb4125b812555a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2013 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0, which accompanies this distribution
* and is available at https://www.eclipse.org/legal/epl-2.0/.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.common.ui.internal.jface;
import java.util.ConcurrentModificationException;
import org.eclipse.jpt.common.ui.internal.swt.listeners.SWTListenerTools;
import org.eclipse.jpt.common.ui.jface.ItemContentProvider;
import org.eclipse.jpt.common.utility.internal.ArrayTools;
import org.eclipse.jpt.common.utility.internal.ObjectTools;
import org.eclipse.jpt.common.utility.internal.iterable.EmptyIterable;
import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent;
import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent;
import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent;
import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent;
import org.eclipse.jpt.common.utility.model.listener.CollectionChangeAdapter;
import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener;
import org.eclipse.jpt.common.utility.model.value.CollectionValueModel;
/**
* This abstract class provides the behavior common to item structure and tree
* content providers (i.e. maintaining a list of an item's elements/children).
*
* @param <M> the type of the provider's manager
*/
abstract class ModelItemContentProvider<M extends ItemContentProvider.Manager>
implements ItemContentProvider
{
/* private-protected */ final Object item;
private final CollectionValueModel<?> childrenModel;
private final CollectionChangeListener childrenListener;
/**
* The children must be accessed on the manager's UI thread.
* This is <code>null</code> when the provider is disposed.
*/
/* private-protected */ Object[] children;
/* private-protected */ final M manager;
/* private-protected */ static final Iterable<?> EMPTY_ITERABLE = EmptyIterable.instance();
ModelItemContentProvider(Object item, CollectionValueModel<?> childrenModel, M manager) {
super();
if (item == null) {
throw new NullPointerException();
}
this.item = item;
if (childrenModel == null) {
throw new NullPointerException();
}
this.childrenModel = childrenModel;
if (manager == null) {
throw new NullPointerException();
}
this.manager = manager;
this.childrenListener = this.buildChildrenListener();
this.childrenModel.addCollectionChangeListener(CollectionValueModel.VALUES, this.childrenListener);
this.children = this.buildChildren();
}
// ********** children **********
/* private-protected */ Object[] getChildren() {
return this.children;
}
private CollectionChangeListener buildChildrenListener() {
return SWTListenerTools.wrap(this.buildChildrenListener_(), this.manager.getViewer());
}
private CollectionChangeListener buildChildrenListener_() {
return new ChildrenListener();
}
/* CU private */ class ChildrenListener
extends CollectionChangeAdapter
{
@Override
public void itemsAdded(CollectionAddEvent event) {
ModelItemContentProvider.this.childrenAdded(event.getItems());
}
@Override
public void itemsRemoved(CollectionRemoveEvent event) {
ModelItemContentProvider.this.childrenRemoved(event.getItems());
}
@Override
public void collectionChanged(CollectionChangeEvent event) {
ModelItemContentProvider.this.childrenChanged();
}
@Override
public void collectionCleared(CollectionClearEvent event) {
ModelItemContentProvider.this.childrenCleared();
}
}
/* CU private */ void childrenAdded(Iterable<?> addedChildren) {
if (this.isAlive()) {
this.childrenAdded_(addedChildren);
}
}
private void childrenAdded_(Iterable<?> addedChildren) {
this.children = ArrayTools.addAll(this.children, addedChildren);
this.notifyManager(addedChildren, EMPTY_ITERABLE);
}
/* CU private */ void childrenRemoved(Iterable<?> removedChildren) {
if (this.isAlive()) {
this.childrenRemoved_(removedChildren);
}
}
private void childrenRemoved_(Iterable<?> removedChildren) {
this.children = ArrayTools.removeAll(this.children, removedChildren);
this.notifyManager(EMPTY_ITERABLE, removedChildren);
}
/* CU private */ void childrenChanged() {
if (this.isAlive()) {
this.childrenChanged_();
}
}
private void childrenChanged_() {
Object[] old = this.children;
this.children = this.buildChildren();
Iterable<?> addedChildren = ArrayTools.iterable(ArrayTools.removeAll(this.children, old));
Iterable<?> removedChildren = ArrayTools.iterable(ArrayTools.removeAll(old, this.children));
this.notifyManager(addedChildren, removedChildren);
}
/* CU private */ void childrenCleared() {
if (this.isAlive()) {
this.childrenCleared_();
}
}
private void childrenCleared_() {
Object[] old = this.children;
this.children = ObjectTools.EMPTY_OBJECT_ARRAY;
if (old.length != 0) {
this.notifyManager(EMPTY_ITERABLE, ArrayTools.iterable(old));
}
}
/**
* Notify the item content provider's manager the item's list of children
* has changed.
*/
/* private-protected */ abstract void notifyManager(Iterable<?> addedChildren, Iterable<?> removedChildren);
private Object[] buildChildren() {
while (true) {
try {
return ArrayTools.array(this.childrenModel);
} catch (ConcurrentModificationException ex) {
// try again - hack: need to make value models thread-safe... TODO bjv
}
}
}
// ********** dispose **********
public void dispose() {
this.childrenModel.removeCollectionChangeListener(CollectionValueModel.VALUES, this.childrenListener);
this.children = null; // NB: disposed!
}
// ********** misc **********
/**
* Check whether the provider was disposed between the time an event was
* fired and the time the event is handled on the UI thread.
*/
private boolean isAlive() {
return this.children != null;
}
@Override
public String toString() {
return ObjectTools.toString(this, this.item);
}
}