/**
* <copyright>
*
* Copyright (c) 2002, 2009 IBM Corporation and others.
* 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:
* IBM - Initial API and implementation
*
* </copyright>
*
* $Id: AdapterFactoryContentProvider.java,v 1.12 2008/05/07 19:08:40 emerks Exp $
*/
package net.enilink.komma.edit.ui.provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.IPropertySourceProvider;
import net.enilink.komma.common.adapter.IAdapterFactory;
import net.enilink.komma.common.notify.INotificationListener;
import net.enilink.komma.common.notify.INotifier;
import net.enilink.komma.common.notify.NotificationFilter;
import net.enilink.komma.core.IReference;
import net.enilink.komma.edit.domain.AdapterFactoryEditingDomain;
import net.enilink.komma.edit.domain.IEditingDomain;
import net.enilink.komma.edit.provider.IItemPropertySource;
import net.enilink.komma.edit.provider.IStructuredItemContentProvider;
import net.enilink.komma.edit.provider.ITreeItemContentProvider;
import net.enilink.komma.edit.provider.IViewerNotification;
import net.enilink.komma.edit.provider.ViewerNotification;
import net.enilink.komma.model.IModelAware;
/**
* This content provider wraps an AdapterFactory and it delegates its JFace
* provider interfaces to corresponding adapter-implemented item provider
* interfaces. All method calls to the various structured content provider
* interfaces are delegated to interfaces implemented by the adapters generated
* by the AdapterFactory.
* {@link org.eclipse.jface.viewers.IStructuredContentProvider} is delegated to
* {@link IStructuredItemContentProvider}; {@link ITreeContentProvider} is
* delegated to {@link ITreeItemContentProvider}; and
* {@link IPropertySourceProvider} to {@link IItemPropertySource}.
*/
public class AdapterFactoryContentProvider implements ITreeContentProvider,
IPropertySourceProvider, INotificationListener<IViewerNotification> {
/**
* This keeps track of the one factory we are using. Use a
* {@link org.eclipse.emf.edit.provider.ComposedAdapterFactory} if adapters
* from more the one factory are involved in the model.
*/
protected IAdapterFactory adapterFactory;
/**
* This keeps track of the one viewer using this content provider.
*/
protected Viewer viewer;
/**
* This is used to queue viewer notifications and refresh viewers based on
* them.
*
*/
protected ViewerRefresh viewerRefresh;
private static final Class<?> IStructuredItemContentProviderClass = IStructuredItemContentProvider.class;
private static final Class<?> ITreeItemContentProviderClass = ITreeItemContentProvider.class;
private static final Class<?> IItemPropertySourceClass = IItemPropertySource.class;
/**
* This constructs an instance that wraps this factory. The factory should
* yield adapters that implement the various IItemContentProvider
* interfaces. If the adapter factory is an {@link IChangeNotifier}, a
* listener is added to it, so it's important to call {@link #dispose()}.
*/
@SuppressWarnings("unchecked")
public AdapterFactoryContentProvider(IAdapterFactory adapterFactory) {
this.adapterFactory = adapterFactory;
if (adapterFactory instanceof INotifier) {
((INotifier<IViewerNotification>) adapterFactory).addListener(this);
}
}
/**
* This sets the wrapped factory. If the adapter factory is an
* {@link IChangeNotifier}, a listener is added to it, so it's important to
* call {@link #dispose()}.
*/
@SuppressWarnings("unchecked")
public void setAdapterFactory(IAdapterFactory adapterFactory) {
if (this.adapterFactory instanceof INotifier) {
((INotifier<IViewerNotification>) this.adapterFactory)
.removeListener(this);
}
if (adapterFactory instanceof INotifier) {
((INotifier<IViewerNotification>) adapterFactory).addListener(this);
}
this.adapterFactory = adapterFactory;
}
/**
* This returns the wrapped factory.
*/
public IAdapterFactory getAdapterFactory() {
return adapterFactory;
}
/**
* The given Viewer will start (oldInput == null) or stop (newInput == null)
* listening for domain events.
*/
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
// If there was no old input, then we must be providing content for this
// part for the first time...
this.viewer = viewer;
}
/**
* This implements
* {@link org.eclipse.jface.viewers.IStructuredContentProvider}.getElements
* to forward the call to an object that implements
* {@link org.eclipse.emf.edit.provider.IStructuredItemContentProvider#getElements
* IStructuredItemContentProvider.getElements}.
*/
public Object[] getElements(Object object) {
if (object instanceof Object[]) {
return (Object[]) object;
}
// Get the adapter from the factory.
IStructuredItemContentProvider structuredItemContentProvider = (IStructuredItemContentProvider) adapterFactory
.adapt(object, IStructuredItemContentProviderClass);
// Either delegate the call or return nothing.
return (structuredItemContentProvider != null ? structuredItemContentProvider
.getElements(object) : Collections.EMPTY_LIST).toArray();
}
/**
* This implements {@link org.eclipse.jface.viewers.ITreeContentProvider}
* .getChildren to forward the call to an object that implements
* {@link org.eclipse.emf.edit.provider.ITreeItemContentProvider#getChildren
* ITreeItemContentProvider.getChildren}.
*/
public Object[] getChildren(Object object) {
// Get the adapter from the factory.
ITreeItemContentProvider treeItemContentProvider = (ITreeItemContentProvider) adapterFactory
.adapt(object, ITreeItemContentProviderClass);
// Either delegate the call or return nothing.
return (treeItemContentProvider != null ? treeItemContentProvider
.getChildren(object) : Collections.EMPTY_LIST).toArray();
}
/**
* This implements {@link org.eclipse.jface.viewers.ITreeContentProvider}
* .hasChildren to forward the call to an object that implements
* {@link org.eclipse.emf.edit.provider.ITreeItemContentProvider#hasChildren
* ITreeItemContentProvider.hasChildren}.
*/
public boolean hasChildren(Object object) {
// Get the adapter from the factory.
ITreeItemContentProvider treeItemContentProvider = (ITreeItemContentProvider) adapterFactory
.adapt(object, ITreeItemContentProviderClass);
// Either delegate the call or return nothing.
return treeItemContentProvider != null
&& treeItemContentProvider.hasChildren(object);
}
/**
* This implements {@link org.eclipse.jface.viewers.ITreeContentProvider}
* .getParent to forward the call to an object that implements
* {@link org.eclipse.emf.edit.provider.ITreeItemContentProvider#getParent
* ITreeItemContentProvider.getParent}.
*/
public Object getParent(Object object) {
// Get the adapter from the factory.
ITreeItemContentProvider treeItemContentProvider = (ITreeItemContentProvider) adapterFactory
.adapt(object, ITreeItemContentProviderClass);
// Either delegate the call or return nothing.
return treeItemContentProvider != null ? treeItemContentProvider
.getParent(object) : null;
}
/**
* This discards the content provider and removes this as a listener to the
* {@link #adapterFactory}.
*/
@SuppressWarnings("unchecked")
public void dispose() {
if (adapterFactory instanceof INotifier) {
((INotifier<IViewerNotification>) adapterFactory)
.removeListener(this);
}
viewer = null;
}
/**
* This implements
* {@link org.eclipse.ui.views.properties.IPropertySourceProvider}
* .getPropertySource to forward the call to an object that implements
* {@link org.eclipse.emf.edit.provider.IItemPropertySource}.
*/
public IPropertySource getPropertySource(Object object) {
if (object instanceof IPropertySource) {
return (IPropertySource) object;
} else {
IItemPropertySource itemPropertySource = (IItemPropertySource) adapterFactory
.adapt(object, IItemPropertySourceClass);
return itemPropertySource != null ? createPropertySource(object,
itemPropertySource) : null;
}
}
protected IPropertySource createPropertySource(Object object,
IItemPropertySource itemPropertySource) {
return new PropertySource(object, adapterFactory, itemPropertySource);
}
@Override
public void notifyChanged(
Collection<? extends IViewerNotification> notifications) {
if (viewer != null && viewer.getControl() != null
&& !viewer.getControl().isDisposed()) {
// If the notification is an IViewerNotification, it specifies how
// ViewerRefresh should behave. Otherwise fall
// back to NotifyChangedToViewerRefresh, which determines how to
// refresh the viewer directly from the model
// notification.
boolean executeRefresh = false;
for (IViewerNotification notification : notifications) {
if (viewerRefresh == null) {
viewerRefresh = new ViewerRefresh(viewer);
}
executeRefresh |= viewerRefresh
.addNotification((IViewerNotification) notification);
}
if (executeRefresh) {
viewer.getControl().getDisplay().asyncExec(viewerRefresh);
}
}
}
/**
* A runnable class that efficiently updates a
* {@link org.eclipse.jface.viewers.Viewer} via standard APIs, based on
* queued {@link org.eclipse.emf.edit.provider.IViewerNotification}s from
* the model's item providers.
*/
public static class ViewerRefresh implements Runnable {
Viewer viewer;
List<IViewerNotification> notifications;
public ViewerRefresh(Viewer viewer) {
this.viewer = viewer;
}
/**
* Adds a viewer notification to the queue that will be processed by
* this <code>ViewerRefresh</code>. Duplicative notifications will not
* be queued.
*
* @param notification
* the notification to add to the queue
* @return whether the queue has been made non-empty, which would
* indicate that the <code>ViewerRefresh</code> needs to be
* {@link Display#asyncExec scheduled} on the event queue
*/
public synchronized boolean addNotification(
IViewerNotification notification) {
if (notifications == null) {
notifications = new ArrayList<IViewerNotification>();
}
if (notifications.isEmpty()) {
notifications.add(notification);
return true;
}
if (viewer instanceof StructuredViewer) {
for (Iterator<IViewerNotification> i = notifications.iterator(); i
.hasNext() && notification != null;) {
IViewerNotification old = i.next();
IViewerNotification merged = merge(old, notification);
if (merged == old) {
notification = null;
} else if (merged != null) {
notification = merged;
i.remove();
}
}
if (notification != null) {
notifications.add(notification);
}
}
return false;
}
/**
* Compares two notifications and, if duplicative, returns a single
* notification that does the work of both. Note: this gives priority to
* a content refresh on the whole viewer over a content refresh or label
* update on a specific element; however, it doesn't use parent-child
* relationships to determine if refreshes on non-equal elements are
* duplicative.
*
* @return a single notification that is equivalent to the two
* parameters, or null if they are non-duplicative
*/
protected IViewerNotification merge(IViewerNotification n1,
IViewerNotification n2) {
// This implements the following order of preference:
// 1. full refresh and update
// 2. full refresh (add update if necessary)
// 3. refresh element with update
// 4. refresh element (if necessary)
// 5. update element
//
if (n1.getElement() == null && n1.isLabelUpdate()) {
return n1;
} else if (n2.getElement() == null && n2.isLabelUpdate()) {
return n2;
} else if (n1.getElement() == null) {
if (n2.isLabelUpdate()) {
n1 = new ViewerNotification();
}
return n1;
} else if (n2.getElement() == null) {
if (n1.isLabelUpdate()) {
n2 = new ViewerNotification();
}
return n2;
} else if (n1.getElement().equals(n2.getElement())) {
if (n1.isContentRefresh() && n1.isLabelUpdate()) {
return n1;
} else if (n2.isContentRefresh() && n2.isLabelUpdate()) {
return n2;
} else if (n1.isContentRefresh()) {
if (n2.isLabelUpdate()) {
n1 = new ViewerNotification(n1.getElement(), true, true);
}
return n1;
} else if (n2.isContentRefresh()) {
if (n1.isLabelUpdate()) {
n2 = new ViewerNotification(n2.getElement(), true, true);
}
return n2;
} else if (n1.isLabelUpdate()) {
return n1;
} else // n2.isLabelUpdate()
{
return n2;
}
}
return null;
}
public void run() {
if (viewer != null && viewer.getControl() != null
&& !viewer.getControl().isDisposed()) {
List<IViewerNotification> current;
synchronized (this) {
current = notifications;
notifications = null;
}
if (current != null) {
for (IViewerNotification viewerNotification : current) {
refresh(viewerNotification);
}
}
}
}
protected void refresh(IViewerNotification notification) {
Object element = notification.getElement();
if (viewer instanceof StructuredViewer) {
StructuredViewer structuredViewer = (StructuredViewer) viewer;
ISelection selection = structuredViewer.getSelection();
boolean isStaleSelection = AdapterFactoryEditingDomain
.isStale(selection);
if (isStaleSelection) {
viewer.setSelection(StructuredSelection.EMPTY);
}
if (element != null) {
// the following test may also be done with an
// IElementComparer on the viewer
Widget widget = structuredViewer.testFindItem(element);
if (widget != null) {
// <FIX> for
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=389482
if (viewer instanceof TreeViewer
&& (viewer.getControl().getStyle() & SWT.VIRTUAL) != 0) {
if (widget != null && !widget.isDisposed()) {
// force widget to be refreshed
((TreeItem) widget).getChecked();
}
}
// </FIX>
Object oldElement = widget.getData();
// ensure that old and new element are contained within
// the same model or in no model at all
if (element instanceof IModelAware
&& oldElement instanceof IModelAware
&& !((IModelAware) element).getModel().equals(
((IModelAware) oldElement).getModel())) {
if (element instanceof IReference) {
element = ((IModelAware) oldElement).getModel()
.resolve((IReference) element);
} else {
element = oldElement;
}
}
if (notification.isContentRefresh()) {
structuredViewer.refresh(element,
notification.isLabelUpdate());
} else if (notification.isLabelUpdate()) {
structuredViewer.update(element, null);
}
}
} else {
structuredViewer.refresh(notification.isLabelUpdate());
}
if (isStaleSelection) {
Object object = structuredViewer.getInput();
IEditingDomain editingDomain = AdapterFactoryEditingDomain
.getEditingDomainFor(object);
if (editingDomain == null) {
for (Object child : ((IStructuredContentProvider) structuredViewer
.getContentProvider()).getElements(object)) {
editingDomain = AdapterFactoryEditingDomain
.getEditingDomainFor(child);
if (editingDomain != null) {
break;
}
}
}
// TODO handle stale selections
// if (editingDomain instanceof AdapterFactoryEditingDomain)
// {
// structuredViewer
// .setSelection(
// new StructuredSelection(
// ((AdapterFactoryEditingDomain) editingDomain)
// .resolve(((IStructuredSelection) selection)
// .toList())),
// true);
// }
}
} else {
viewer.refresh();
}
}
}
@Override
public NotificationFilter<IViewerNotification> getFilter() {
return null;
}
}