/******************************************************************************* * Copyright (c) 2010-2015 Henshin developers. 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: * TU Berlin, University of Luxembourg, SES S.A. *******************************************************************************/ package de.tub.tfs.muvitor.ui.utils; import java.util.HashMap; import org.eclipse.core.runtime.ListenerList; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EObject; import de.tub.tfs.muvitor.gef.editparts.AdapterConnectionEditPart; import de.tub.tfs.muvitor.gef.editparts.AdapterGraphicalEditPart; import de.tub.tfs.muvitor.gef.editparts.AdapterTreeEditPart; import de.tub.tfs.muvitor.ui.IDUtil; import de.tub.tfs.muvitor.ui.MuvitorTreeEditor; /** * In MuvitorKit editors, this purely static service can be used to listen to * and process EMF notifications fired by almost all EObject instances even * without being registered to these models. Usually, listeners of this service * perform checks for correctness conditions and generate problem markers on * specific model changes. * * <p> * This service is hooked into MuvitorKit editors via the adapter edit parts, * which by default pass notifications of their models to this service by * calling {@link #notifyListeners(Notification)}. * * <p> * As the default behavior that is this services's main purpose, if * MuvitorNotifierService notices that an EObject has been deleted from the * model it will take care of closing all MuvitorKit views showing this * particular EObject or one of its children. * * <p> * There are several interfaces for listeners serving different purposes. The * most general is {@link IEMFChangeListener}, which will be notified about all * notifications announced via {@link #notifyListeners(Notification)}. * * <p> * Listeners should use this service carefully with respect to performance * issues by reacting on specific notifications only (e.g. by filtering * notifications by their featureID) and restricting listeners to their editor * contexts if possible (see * {@link #addListener(IMuvitorListener, MuvitorTreeEditor)}). * * @author Tony Modica */ public class MuvitorNotifierService { /** * Listeners implementing this interface will be notified about all * occurring notifications. */ public static interface IEMFChangeListener extends IMuvitorListener { /** * @param model * the notifier causing the notification * @param notification * the notification */ public void notifyChange(EObject model, Notification notification); } /** * This interface is equivalent to {@link IEMFChangeListener} unless listening * jobs will be notified after all other {@link IMuvitorListener} so that * possible reactions to a notification have completed before. */ public static interface IEMFChangeListenerJob extends IEMFChangeListener { } /** * This is the general interface for objects listening to the * MuvitorNotifierService. It is used internally, only the interfaces * extending this one are meant to be implemented. */ private static interface IMuvitorListener { } static private final HashMap<MuvitorTreeEditor, ListenerList> editor2Listeners = new HashMap<MuvitorTreeEditor, ListenerList>(); static private final ListenerList globalListeners = new ListenerList(); /** * The last notification is kept for comparison in * {@link #notifyListeners(Notification)} to avoid multiple notification * distribution calls of several objects listening to the same EObject and * its changes. */ static private Notification lastNotification; /** * Adds a listener to the {@link MuvitorNotifierService}. * * @param listener * the listener to be added * @param editor * the editor in whose context notifications will be forwarded to * the listener, or <code>null</code> if it should receive all * notifications globally */ final static public void addListener(final IMuvitorListener listener, final MuvitorTreeEditor editor) { if (editor == null) { globalListeners.add(listener); } else { getListeners(editor).add(listener); } } /** * Unregisters all listeners from the {@link MuvitorNotifierService} of a * specific editor context. */ final static public void clear(final MuvitorTreeEditor editor) { final ListenerList listeners = editor2Listeners.remove(editor); if (listeners != null) { listeners.clear(); } } /** * This method passes a notification to all listeners registered to the * GlobalNotifierService. This will be done according to the interfaces the * listeners implement. * * <p> * In MuvitorKit editors all edit parts (should) extend the adapter edit * parts that by default call this method if their models fire some * notification. * * <p> * If the notification announces that an EObject has been deleted from the * model all MuvitorKit views showing this particular EObject or one of its * children will be closed. * * @param notification * the notification to distribute to all registered listeners * * @see IMuvitorListener * @see IEMFChangeListener * @see IChangeListenerJob * * @see AdapterTreeEditPart * @see AdapterGraphicalEditPart * @see AdapterConnectionEditPart */ final static public void notifyListeners(final Notification notification) { /* * little optimization to handle each notification only once even if * passed by several sources consecutively. Removing an adapter and set/ * unset with the same result are not model changes and not important as * well. And don't care if a model has is already deleted. */ final EObject notifier = (EObject) notification.getNotifier(); if(notifier == null){ return; } if (lastNotification == notification || notification.isTouch() || notifier.eResource() == null) { return; } lastNotification = notification; // handle deletion of an element by hiding the view showing it or its // children if (notification.getEventType() == Notification.REMOVE && notification.getOldValue() instanceof EObject && ((EObject) notification.getOldValue()).eContainer() == null) { // a child has been removed from one of its parent's containment // lists and has not just been moved into another container final EObject deletedChild = (EObject) notification.getOldValue(); MuvitorTreeEditor.closeViewShowing(deletedChild); for (final TreeIterator<EObject> it = deletedChild.eAllContents(); it.hasNext();) { MuvitorTreeEditor.closeViewShowing(it.next()); } } final Object[] listeners = getListeners(IDUtil.getHostEditor(notifier)).getListeners(); // handle all other model changes // handle normal listeners before starting jobs for (final Object listener : listeners) { if (listener instanceof IEMFChangeListener) { ((IEMFChangeListener) listener).notifyChange(notifier, notification); } } for (final Object listener : globalListeners.getListeners()) { if (listener instanceof IEMFChangeListener) { ((IEMFChangeListener) listener).notifyChange(notifier, notification); } } for (final Object listener : listeners) { if (listener instanceof IEMFChangeListenerJob) { ((IEMFChangeListenerJob) listener).notifyChange(notifier, notification); } } for (final Object listener : globalListeners.getListeners()) { if (listener instanceof IEMFChangeListenerJob) { ((IEMFChangeListenerJob) listener).notifyChange(notifier, notification); } } } /** * Removes a listener from the {@link MuvitorNotifierService}. * * @param listener * the listener to be removed */ final static public void removeListener(final IMuvitorListener listener) { globalListeners.remove(listener); for (final ListenerList listeners : editor2Listeners.values()) { listeners.remove(listener); } } final static private ListenerList getListeners(final MuvitorTreeEditor editor) { ListenerList listeners = editor2Listeners.get(editor); if (listeners == null) { listeners = new ListenerList(); editor2Listeners.put(editor, listeners); } return listeners; } /** * This class is purely static. */ private MuvitorNotifierService() { } }