blob: cd3169c7ac82dea52e9669d6c0c179d1c7a509b1 [file] [log] [blame]
/*******************************************************************************
* Copyright 2011 Chair for Applied Software Engineering,
* Technische Universitaet Muenchen.
* All rights reserved. This program and the accompanying materials
* are made available under 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.Stack;
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.impl.NotifiableIdEObjectCollectionImpl;
/**
* Notifies a about changes in its containment hierarchy.
*
* @author koegel
* @author emueller
*/
public class EObjectChangeNotifier extends EContentAdapter {
private final NotifiableIdEObjectCollectionImpl collection;
private boolean isInitializing;
private Stack<Notification> currentNotifications;
private Stack<EObject> removedModelElements;
private int reentrantCallToAddAdapterCounter;
private boolean notificationDisabled;
/**
* Constructor. Attaches an Adapter to the given {@link Notifier} and forwards notifications to the given
* NotifiableIdEObjectCollection, that reacts appropriately.
*
* @param notifiableCollection
* a NotifiableIdEObjectCollection
* @param notifier
* the {@link Notifier} to listen to
*/
public EObjectChangeNotifier(NotifiableIdEObjectCollectionImpl notifiableCollection, Notifier notifier) {
this.collection = notifiableCollection;
isInitializing = true;
currentNotifications = new Stack<Notification>();
removedModelElements = new Stack<EObject>();
notifier.eAdapters().add(this);
isInitializing = false;
reentrantCallToAddAdapterCounter = 0;
notificationDisabled = false;
}
/**
* {@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.size() > 0) {
removedModelElements.pop();
removedModelElements.push(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);
// push null to ensure that stack has same size as call stack
// will be replaced with an actual removed element by removeAdapter method if there is any
removedModelElements.push(null);
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);
// detect if the notification is about a reference to an object outside of the collection => notify
// project
if (feature instanceof EReference) {
Object newValue = notification.getNewValue();
if (newValue instanceof EObject) {
EObject newEObject = (EObject) newValue;
if (!collection.containsInstance(newEObject)) {
collection.addCutElement(newEObject);
}
}
}
currentNotifications.pop();
if (!notification.isTouch()
&& notifier instanceof EObject
&& (collection.getModelElementId((EObject) notifier) != null || collection
.getDeletedModelElementId((EObject) notifier) != null)) {
collection.notify(notification, collection, (EObject) notifier);
}
EObject removedElement = removedModelElements.pop();
if (removedElement != null) {
collection.modelElementRemoved(collection, removedElement);
}
}
/**
* @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;
}
}