/******************************************************************************* * Copyright (c) 2008-2011 Chair for Applied Software Engineering, * Technische Universitaet Muenchen. * 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: ******************************************************************************/ package org.eclipse.emf.emfstore.common.model.util; import java.util.HashSet; import java.util.Set; import java.util.Stack; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.util.EContentAdapter; import org.eclipse.emf.emfstore.common.model.NotifiableIdEObjectCollection; /** * Notifies a about changes in its containment hierarchy. * * @author koegel * @author emueller */ public class EObjectChangeNotifier extends EContentAdapter { private final NotifiableIdEObjectCollection collection; private boolean isInitializing; private Set<EObject> removedModelElements; private Stack<Notification> currentNotifications; private int reentrantCallToAddAdapterCounter; private boolean notificationDisabled; /** * Constructor. Attaches an {@link Adapter} to the given {@link Notifier} and forwards notifications to the given * {@link NotifiableIdEObjectCollection}, that reacts appropriately. * * @param notifiableCollection * a {@link NotifiableIdEObjectCollection} * @param notifier * the {@link Notifier} to listen to */ public EObjectChangeNotifier(NotifiableIdEObjectCollection notifiableCollection, Notifier notifier) { this.collection = notifiableCollection; isInitializing = true; currentNotifications = new Stack<Notification>(); notifier.eAdapters().add(this); isInitializing = false; reentrantCallToAddAdapterCounter = 0; notificationDisabled = false; removedModelElements = new HashSet<EObject>(); } /** * {@inheritDoc} * * @see org.eclipse.emf.ecore.util.EContentAdapter#addAdapter(org.eclipse.emf.common.notify.Notifier) */ @Override protected void addAdapter(Notifier notifier) { try { reentrantCallToAddAdapterCounter += 1; if (!notifier.eAdapters().contains(this)) { super.addAdapter(notifier); } } finally { reentrantCallToAddAdapterCounter -= 1; } if (reentrantCallToAddAdapterCounter > 0 || currentNotifications.isEmpty()) { // any other than the first call in re-entrant calls to addAdapter // are going to call the project return; } Notification currentNotification = currentNotifications.peek(); if (currentNotification != null && !currentNotification.isTouch() && !isInitializing && notifier instanceof EObject && !ModelUtil.isIgnoredDatatype((EObject) notifier)) { EObject modelElement = (EObject) notifier; if (!collection.containsInstance(modelElement) && isInCollection(modelElement)) { collection.modelElementAdded(collection, modelElement); } } } /** * {@inheritDoc} * * @see org.eclipse.emf.ecore.util.EContentAdapter#removeAdapter(org.eclipse.emf.common.notify.Notifier) */ @Override protected void removeAdapter(Notifier notifier) { if (isInitializing || currentNotifications.isEmpty()) { return; } Notification currentNotification = currentNotifications.peek(); if (currentNotification != null && currentNotification.isTouch()) { return; } if (currentNotification != null && currentNotification.getFeature() instanceof EReference) { EReference eReference = (EReference) currentNotification.getFeature(); if (eReference.isContainment() && eReference.getEOpposite() != null && !eReference.getEOpposite().isTransient()) { return; } } if (notifier instanceof EObject) { EObject modelElement = (EObject) notifier; if (!isInCollection(modelElement) && (collection.containsInstance(modelElement) || collection.getDeletedModelElementId(modelElement) != null)) { removedModelElements.add(modelElement); } } } /** * Checks whether the given {@link EObject} is within the collection. * * @param modelElement * the {@link EObject} whose containment should be checked * @return true, if the {@link EObject} is contained in the collection, * false otherwise */ private boolean isInCollection(EObject modelElement) { EObject parent = modelElement.eContainer(); if (parent == null) { return false; } if (parent == collection) { return true; } if (collection.containsInstance(parent)) { return true; } return isInCollection(parent); } /** * {@inheritDoc} * * @see org.eclipse.emf.ecore.util.EContentAdapter#notifyChanged(org.eclipse.emf.common.notify.Notification) */ @Override public void notifyChanged(Notification notification) { if (notificationDisabled) { return; } currentNotifications.push(notification); Object feature = notification.getFeature(); Object notifier = notification.getNotifier(); if (feature instanceof EReference) { EReference eReference = (EReference) feature; // Do not create notifications for transient features if (eReference.isTransient()) { return; } if (eReference.isContainer()) { handleContainer(notification, eReference); } } super.notifyChanged(notification); currentNotifications.pop(); // collection itself is not a valid model element if (!notification.isTouch() && notifier instanceof EObject) { collection.notify(notification, collection, (EObject) notifier); } for (EObject removedModelElement : removedModelElements) { collection.modelElementRemoved(collection, removedModelElement); } removedModelElements.clear(); } /** * @param notification */ private void handleContainer(Notification notification, EReference eReference) { if (notification.getEventType() == Notification.SET) { Object newValue = notification.getNewValue(); Object oldValue = notification.getOldValue(); if (newValue == null && oldValue != null) { removeAdapter((Notifier) notification.getNotifier()); } } } /** * @param notificationDisabled * the notificationDisabled to set */ public void disableNotifications(boolean notificationDisabled) { this.notificationDisabled = notificationDisabled; } }