/** * Copyright (c) 2002-2006 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 */ package org.eclipse.emf.edit.ui.provider; import java.util.ArrayList; 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.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.ui.views.properties.IPropertySource; import org.eclipse.ui.views.properties.IPropertySourceProvider; import org.eclipse.swt.widgets.Display; import org.eclipse.emf.common.notify.AdapterFactory; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain; import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.emf.edit.provider.IChangeNotifier; import org.eclipse.emf.edit.provider.IItemPropertySource; import org.eclipse.emf.edit.provider.INotifyChangedListener; import org.eclipse.emf.edit.provider.IStructuredItemContentProvider; import org.eclipse.emf.edit.provider.ITreeItemContentProvider; import org.eclipse.emf.edit.provider.IViewerNotification; import org.eclipse.emf.edit.provider.ViewerNotification; /** * 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, INotifyChangedListener { private static final long serialVersionUID = 1L; /** * 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 AdapterFactory 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. * @since 2.2.0 */ 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()}. */ public AdapterFactoryContentProvider(AdapterFactory adapterFactory) { this.adapterFactory = adapterFactory; if (adapterFactory instanceof IChangeNotifier) { ((IChangeNotifier)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()}. */ public void setAdapterFactory(AdapterFactory adapterFactory) { if (this.adapterFactory instanceof IChangeNotifier) { ((IChangeNotifier)this.adapterFactory).removeListener(this); } if (adapterFactory instanceof IChangeNotifier) { ((IChangeNotifier)adapterFactory).addListener(this); } this.adapterFactory = adapterFactory; } /** * This returns the wrapped factory. */ public AdapterFactory 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) { // 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}. */ public void dispose() { if (adapterFactory instanceof IChangeNotifier) { ((IChangeNotifier)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) (object instanceof EObject && ((EObject)object).eClass() == null ? null : adapterFactory.adapt(object, IItemPropertySourceClass)); return itemPropertySource != null ? createPropertySource(object, itemPropertySource) : null; } } protected IPropertySource createPropertySource(Object object, IItemPropertySource itemPropertySource) { return new PropertySource(object, itemPropertySource); } public void notifyChanged(Notification notification) { 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. // if (notification instanceof IViewerNotification) { if (viewerRefresh == null) { viewerRefresh = new ViewerRefresh(viewer); } if (viewerRefresh.addNotification((IViewerNotification)notification)) { viewer.getControl().getDisplay().asyncExec(viewerRefresh); } } else { NotifyChangedToViewerRefresh.handleNotifyChanged( viewer, notification.getNotifier(), notification.getEventType(), notification.getFeature(), notification.getOldValue(), notification.getNewValue(), notification.getPosition()); } } } /** * 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; boolean compatibility; /** * @since 2.2.0 */ public ViewerRefresh(Viewer viewer) { this.viewer = viewer; } /** * @deprecated in 2.2.0 */ @Deprecated public ViewerRefresh(Viewer viewer, IViewerNotification notification) { this.viewer = viewer; addNotification(notification); compatibility = true; } /** * 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 * @since 2.2.0 */ 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 * @since 2.2.0 */ 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(n1); } return n1; } else if (n2.getElement() == null) { if (n1.isLabelUpdate()) { n2 = new ViewerNotification(n2); } return n2; } else if (n1.getElement() == 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, n1.getElement(), true, true); } return n1; } else if (n2.isContentRefresh()) { if (n1.isLabelUpdate()) { n2 = new ViewerNotification(n2, 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); } } } } /** * @since 2.2.0 */ protected void refresh(IViewerNotification notification) { // Previously, we never updated the viewer on a resolve. Now we post and merge it as appropriate. // if (compatibility && notification.getEventType() == Notification.RESOLVE) return; 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) { 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(); EditingDomain editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(object); if (editingDomain == null) { for (Object child : ((IStructuredContentProvider)structuredViewer.getContentProvider()).getElements(object)) { editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(child); if (editingDomain != null) { break; } } } if (editingDomain instanceof AdapterFactoryEditingDomain) { structuredViewer.setSelection (new StructuredSelection(((AdapterFactoryEditingDomain)editingDomain).resolve(((IStructuredSelection)selection).toList())), true); } } } else { viewer.refresh(); } } } }