/** * Copyright (c) 2007 Borland Software Corporation * * 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: * bblajer - initial API and implementation */ package org.eclipse.gmf.runtime.lite.edit.parts.update; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.command.CompoundCommand; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.emf.transaction.ResourceSetChangeEvent; import org.eclipse.emf.transaction.ResourceSetListenerImpl; import org.eclipse.emf.transaction.RollbackException; import org.eclipse.emf.transaction.Transaction; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.emf.transaction.impl.FilterManager; import org.eclipse.gef.EditPart; import org.eclipse.gef.EditPartViewer; import org.eclipse.gmf.runtime.lite.edit.parts.update.canonical.INotationModelRefresher; import org.eclipse.gmf.runtime.notation.CanonicalStyle; import org.eclipse.gmf.runtime.notation.NotationPackage; import org.eclipse.gmf.runtime.notation.View; /** * Manages update for the diagram by requesting refresh of affected edit parts at the end of each transaction. * Also manages notation model refreshers which are allowed to contribute their trigger commands before the transaction is committed. */ public class TransactionalUpdateManager extends ResourceSetListenerImpl { private final EditPartViewer myEditPartViewer; private final HashMap<EObject, Collection<IUpdatableEditPart>> myRegisteredListeners = new HashMap<EObject, Collection<IUpdatableEditPart>>(); private final NotationModelRefresherHolder myNotationModelRefreshers = new NotationModelRefresherHolder(); private TransactionalEditingDomain myEditingDomain; /** * Creates an instance of <code>TransactionalUpdateManager</code>. */ public TransactionalUpdateManager(EditPartViewer editPartViewer) { myEditPartViewer = editPartViewer; } /** * Installs the update manager as a listener to the given editing domain. The receiver will start listening and dispatching notification to * registered refreshers. */ public void install(TransactionalEditingDomain editingDomain) { if (myEditingDomain != null) { throw new IllegalStateException("Already listening to an editing domain"); } myEditingDomain = editingDomain; myEditingDomain.addResourceSetListener(this); } /** * Tells whether the update manager is installed. Only installed update managers dispatch notifications. */ public boolean isInstalled() { return myEditingDomain != null; } /** * Uninstalls the update manager. It will stop receiving and dispatching notifications to any listeners. */ public void uninstall() { if (isInstalled()) { myEditingDomain.removeResourceSetListener(this); myEditingDomain = null; } } /** * Returns the command that, if applied, synchronizes the notational model with the underlying domain model. */ public Command buildRefreshNotationModelCommand() { return myNotationModelRefreshers.buildNotationRefreshCommand(); } public void addNotationModelRefresher(INotationModelRefresher refresher) { myNotationModelRefreshers.addNotationModelRefresher(refresher); } public void removeNotationModelRefresher(INotationModelRefresher refresher) { myNotationModelRefreshers.removeNotationModelRefresher(refresher); } public boolean isNotationModelRefresherInstalled(INotationModelRefresher refresher) { return myNotationModelRefreshers.isNotationModelRefresherInstalled(refresher); } /** * Registers the given updatable edit part as a listener to the given <code>EObject</code>. */ public void addUpdatableEditPart(EObject source, IUpdatableEditPart listener) { if (source == null || listener == null) { return; } Collection<IUpdatableEditPart> listeners = myRegisteredListeners.get(source); if (listeners == null) { listeners = new LinkedList<IUpdatableEditPart>(); myRegisteredListeners.put(source, listeners); } listeners.add(listener); } /** * Unregisters the given updatable edit part as a listener to the given <code>EObject</code>. */ public void removeUpdatableEditPart(EObject source, IUpdatableEditPart listener) { if (source == null || listener == null) { return; } Collection<IUpdatableEditPart> listeners = myRegisteredListeners.get(source); if (listeners == null) { return; } listeners.remove(listener); if (listeners.isEmpty()) { myRegisteredListeners.remove(source); } } public void dispose() { myRegisteredListeners.clear(); myNotationModelRefreshers.dispose(); } protected final Collection<IUpdatableEditPart> getRegisteredListeners(EObject source) { return myRegisteredListeners.get(source); } protected final Collection<IUpdatableEditPart> getRegisteredListeners(Notification msg) { Object notifier = msg.getNotifier(); if (notifier instanceof EObject) { Collection<IUpdatableEditPart> result = getRegisteredListeners((EObject) notifier); if (result != null) { return result; } } return Collections.emptyList(); } @Override public Command transactionAboutToCommit(ResourceSetChangeEvent event) throws RollbackException { myNotationModelRefreshers.startRecording(); try { executeRefreshers(event); return myNotationModelRefreshers.buildNotationRefreshCommand(event); } finally { myNotationModelRefreshers.stopRecording(); } } @SuppressWarnings("unchecked") private void executeRefreshers(ResourceSetChangeEvent event) { ComposeableRefresherImpl affectedRefreshers = new ComposeableRefresherImpl(); for (Notification msg : (Collection<Notification>) event.getNotifications()) { Collection<IUpdatableEditPart> affectedEditParts = findAffectedParts(msg); for(IUpdatableEditPart next : affectedEditParts) { processRefreshers(affectedRefreshers, next, msg); } Collection<IUpdatableEditPart> registeredListeners = getRegisteredListeners(msg); for(IUpdatableEditPart next : registeredListeners) { processRefreshers(affectedRefreshers, next, msg); } if (isCanonicalStyleEvent(msg)) { myNotationModelRefreshers.processCanonicalStyleEvent(msg); } } affectedRefreshers.refresh(); } @Override public void resourceSetChanged(ResourceSetChangeEvent event) { if (event.getTransaction() == null) { //Although the API doc explicitly states that this should never happen, transaction is in fact <code>null</code> while broadcasting //unbatched notifications. However, it seems that this only occurs inside read-only transactions, and thus it is not necessary to do something //about such situations. return; } if (Boolean.TRUE.equals(event.getTransaction().getOptions().get(Transaction.OPTION_NO_TRIGGERS))) { //the transaction did not contain triggers, e.g., because it was an undo/redo transaction. We should execute the //associated refreshers, but should not build the notation refresh command, since it is not requested. executeRefreshers(event); } for (Iterator<EObject> it = myRegisteredListeners.keySet().iterator(); it.hasNext(); ) { EObject next = it.next(); if (next.eResource() == null) { it.remove(); } } myNotationModelRefreshers.removeStaleRefreshers(); } public boolean isCanonicalStyleEvent(org.eclipse.emf.common.notify.Notification msg) { if (NotationPackage.eINSTANCE.getCanonicalStyle_Canonical() == msg.getFeature()) { return msg.getNewBooleanValue(); } if (NotationPackage.eINSTANCE.getView_Styles() == msg.getFeature()) { if (UpdaterUtil.affects(msg, NotationPackage.eINSTANCE.getCanonicalStyle())) { CanonicalStyle style = (CanonicalStyle) ((View) msg.getNotifier()).getStyle(NotationPackage.eINSTANCE.getCanonicalStyle()); if (style == null) { return true; } return style.isCanonical(); } } return false; } protected void processRefreshers(ComposeableRefresherImpl affectedRefreshers, IUpdatableEditPart next, Notification msg) { if (next instanceof IExternallyUpdatableEditPart) { for (IExternallyUpdatableEditPart.ExternalRefresher nextExternalRefresher : ((IExternallyUpdatableEditPart) next).getExternalRefreshers()) { if (nextExternalRefresher.isAffectingEvent(msg)) { affectedRefreshers.addRefresher(nextExternalRefresher); } } } IUpdatableEditPart.Refresher refresher = next.getRefresher((EStructuralFeature)msg.getFeature(), msg); affectedRefreshers.addRefresher(refresher); } protected final EditPartViewer getEditPartViewer() { return myEditPartViewer; } protected Collection<IUpdatableEditPart> findAffectedParts(Notification msg) { Object notifier = msg.getNotifier(); if (notifier instanceof EObject) { View view = getView((EObject) notifier); if (view != null) { EditPart affectedEditPart = (EditPart) myEditPartViewer.getEditPartRegistry().get(view); if (affectedEditPart instanceof IUpdatableEditPart) { if (shouldNotifyParent(msg) && affectedEditPart.getModel() == view && affectedEditPart.getParent() instanceof IUpdatableEditPart) { return Arrays.asList((IUpdatableEditPart) affectedEditPart, (IUpdatableEditPart) affectedEditPart.getParent()); } return Collections.singleton((IUpdatableEditPart) affectedEditPart); } return Collections.emptyList(); } } return Collections.emptyList(); } protected boolean shouldNotifyParent(Notification msg) { if (msg.getFeature() == NotationPackage.eINSTANCE.getView_Visible()) { return true; } if (msg.getFeature() == EcorePackage.eINSTANCE.getEModelElement_EAnnotations()) { return msg.getNotifier() instanceof View; } return false; } private View getView(EObject offspring) { while (offspring != null && (offspring instanceof View == false || isFiltered((View) offspring))) { offspring = offspring.eContainer(); } return (View) offspring; } protected boolean isFiltered(View view) { return false == myEditPartViewer.getEditPartRegistry().get(view) instanceof IUpdatableEditPart; } private static class NotationModelRefresherHolder { private final HashMap<View, Collection<INotationModelRefresher>> myListeners = new HashMap<View, Collection<INotationModelRefresher>>(); private Set<INotationModelRefresher> myJustAddedListeners; void addNotationModelRefresher(INotationModelRefresher refresher) { if (refresher == null) { return; } View view = refresher.getView(); if (view == null || view.eResource() == null) { return; } Collection<INotationModelRefresher> listeners = myListeners.get(view); if (listeners == null) { listeners = new LinkedList<INotationModelRefresher>(); myListeners.put(view, listeners); } if (!listeners.contains(refresher)) { listeners.add(refresher); } if (myJustAddedListeners != null) { myJustAddedListeners.add(refresher); } } boolean isNotationModelRefresherInstalled(INotationModelRefresher refresher) { if (myJustAddedListeners != null && myJustAddedListeners.contains(refresher)) { return true; } if (refresher == null) { return false; } View view = refresher.getView(); if (view == null) { return false; } Collection<INotationModelRefresher> listeners = myListeners.get(view); if (listeners == null) { return false; } return listeners.contains(refresher); } void processCanonicalStyleEvent(Notification msg) { assert myJustAddedListeners != null; Collection<INotationModelRefresher> affectedRefreshers = myListeners.get(msg.getNotifier()); if (affectedRefreshers != null) { myJustAddedListeners.addAll(affectedRefreshers); } } Command buildNotationRefreshCommand() { CompoundCommand result = new CompoundCommand(); for(INotationModelRefresher next : getAllNotationModelRefreshers()) { Command refreshNotationModelCommand = next.buildRefreshNotationModelCommand(); result.appendIfCanExecute(refreshNotationModelCommand); } return result.isEmpty() ? null : result; } Command buildNotationRefreshCommand(ResourceSetChangeEvent event) throws RollbackException { CompoundCommand result = new CompoundCommand(); ArrayList<Notification> cache = new ArrayList<Notification>(event.getNotifications().size()); for(INotationModelRefresher next : getAllNotationModelRefreshers()) { ResourceSetChangeEvent nextEvent; if (myJustAddedListeners.contains(next)) { nextEvent = event; } else { List<?> filtered = FilterManager.getInstance().select(event.getNotifications(), next.getFilter(), cache); if (filtered.isEmpty()) { continue; } nextEvent = new ResourceSetChangeEvent(event.getEditingDomain(), event.getTransaction(), event.getNotifications()); } if (nextEvent != null) { Command nextCommand = next.transactionAboutToCommit(nextEvent); if (nextCommand != null) { result.append(nextCommand); } } } return result.isEmpty() ? null : result; } void removeNotationModelRefresher(INotationModelRefresher refresher) { if (refresher == null) { return; } View view = refresher.getView(); if (view == null) { return; } Collection<INotationModelRefresher> listeners = myListeners.get(view); if (listeners != null) { listeners.remove(refresher); } if (myJustAddedListeners != null) { myJustAddedListeners.remove(refresher); } } private Collection<INotationModelRefresher> getAllNotationModelRefreshers() { Collection<INotationModelRefresher> result = new HashSet<INotationModelRefresher>(); for(Collection<INotationModelRefresher> next : myListeners.values()) { if (next != null) { result.addAll(next); } } return result; } void startRecording() { myJustAddedListeners = new HashSet<INotationModelRefresher>(); } void stopRecording() { myJustAddedListeners.clear(); myJustAddedListeners = null; } void removeStaleRefreshers() { for (Iterator<Map.Entry<View, Collection<INotationModelRefresher>>> it = myListeners.entrySet().iterator(); it.hasNext(); ) { Map.Entry<View, Collection<INotationModelRefresher>> next = it.next(); if (next.getKey().eResource() == null) { it.remove(); } } } void dispose() { myListeners.clear(); } } }