blob: f73f3ed6a3862f79bb31c6b4a30223611905ab11 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2008-2010 See4sys 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:
* See4sys - Initial API and implementation
*
* </copyright>
*/
package org.eclipse.sphinx.emf.edit;
import java.util.List;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.transaction.NotificationFilter;
import org.eclipse.emf.transaction.ResourceSetChangeEvent;
import org.eclipse.emf.transaction.ResourceSetListenerImpl;
import org.eclipse.sphinx.emf.util.EObjectUtil;
/**
* Detects {@link EObject model object}s that have been removed from their
* {@link org.eclipse.emf.ecore.EObject#eResource() containing resource} and/or their {@link EObject#eContainer
* containing object} and turns them as well as all their directly and indirectly contained objects into proxies. This
* offers the following benefits:
* <ul>
* <li>After removal of an {@link EObject model object} from its container, all other {@link EObject model object}s that
* are still referencing the removed {@link EObject model object} will know that the latter is no longer available but
* can still figure out its type and {@link URI}.</li>
* <li>If a new {@link EObject model object} with the same type as the removed one is added to the same container again
* later on, the proxies which the other {@link EObject model object}s are still referencing will be resolved as usual
* and therefore get automatically replaced by the newly added {@link EObject model object}.</li>
* <li>In big models, this approach can yield significant advantages in terms of performance because it helps avoiding
* full deletions of {@link EObject model object}s involving expensive searches for their cross-references and those of
* all their directly and indirectly contained objects. It does all the same not lead to references pointing at
* "floating" {@link EObject model object}s, i.e., {@link EObject model object}s that are not directly or indirectly
* contained in a resource.</li>
* </ul>
*/
public class LocalProxyChangeListener extends ResourceSetListenerImpl {
/**
* Default constructor.
*/
public LocalProxyChangeListener() {
super(NotificationFilter.createEventTypeFilter(Notification.ADD_MANY).or(
NotificationFilter.createEventTypeFilter(Notification.REMOVE_MANY).or(
NotificationFilter.createEventTypeFilter(Notification.ADD).or(
NotificationFilter.createEventTypeFilter(Notification.REMOVE).or(
NotificationFilter.createEventTypeFilter(Notification.SET).or(
NotificationFilter.createEventTypeFilter(Notification.UNSET)))))));
}
@Override
public void resourceSetChanged(ResourceSetChangeEvent event) {
for (Notification notification : event.getNotifications()) {
if (notification.getNotifier() instanceof EObject) {
EObject object = (EObject) notification.getNotifier();
if (notification.getFeature() instanceof EReference) {
EReference reference = (EReference) notification.getFeature();
switch (notification.getEventType()) {
case Notification.UNSET:
// Convert removed object plus its directly and indirectly contained children into
// proxies
if (reference.isContainer()) {
Object value = object.eGet(reference);
EObject oldValue = (EObject) notification.getOldValue();
if (oldValue != null && value == null) {
if (!object.eIsProxy()) {
EObjectUtil.proxify(oldValue, reference, object);
}
}
}
break;
// Object(s) being removed from model (i.e. from its owner)?
/*
* !! Important Note !! Be sure to consider only removed objects and ignore all objects that have
* just been moved. While the former have been effectively removed from their old containers and are
* floating in memory, the latter have also been removed from their old containers but got assigned
* to a new one right after. For obvious reasons such moved objects and their directly and
* indirectly contained children must not be converted into proxies.
*/
/*
* !! Important Note !! Don't use notification.getNewValue() == null to test if object has been
* removed from the model. The reason is that plenty of successive modifications and notifications
* for the same object and feature might have happened before we get called here. Therefore the new
* value of an arbitrary notification which we are dealing with here is the new value which was
* applicable by the time the notification was created but does not necessarily correspond to the
* current object's state.
*/
case Notification.REMOVE:
// Convert removed object plus its directly and indirectly contained children into
// proxies
if (reference.isContainment()) {
if (!reference.isMany()) {
Object value = object.eGet(reference);
EObject oldValue = (EObject) notification.getOldValue();
if (oldValue.eResource() == null && value != oldValue) {
EObjectUtil.proxify(object, reference, oldValue);
}
} else {
@SuppressWarnings("unchecked")
List<Object> values = (List<Object>) object.eGet(reference);
EObject oldValue = (EObject) notification.getOldValue();
if (oldValue.eResource() == null && !values.contains(oldValue)) {
EObjectUtil.proxify(object, reference, oldValue);
}
}
}
break;
case Notification.REMOVE_MANY:
// Convert removed objects plus its directly and indirectly contained children into
// proxies
if (reference.isContainment()) {
@SuppressWarnings("unchecked")
List<Object> values = (List<Object>) object.eGet(reference);
@SuppressWarnings("unchecked")
List<EObject> oldValues = (List<EObject>) notification.getOldValue();
for (EObject oldValue : oldValues) {
if (oldValue.eResource() == null && !values.contains(oldValue)) {
EObjectUtil.proxify(object, reference, oldValue);
}
}
}
break;
case Notification.SET:
// Convert added object plus its directly and indirectly contained children
// back into regular EObjects
if (reference.isContainer()) {
Object value = object.eGet(reference);
if (value != null) {
if (object.eIsProxy()) {
EObjectUtil.deproxify(object);
}
}
}
break;
// Object(s) being added to model (i.e. to some owner)?
/*
* !! Important Note !! Don't use notification.getOldValue() == null to test if object has been
* added to the model. The reason is that plenty of successive modifications and notifications for
* the same object and feature might have happened before we get called here. Therefore the old
* value of an arbitrary notification which we are dealing with here is the old value which was
* applicable by the time the notification was created but does not necessarily correspond to the
* current object's state.
*/
case Notification.ADD:
// Convert added object plus its directly and indirectly contained children
// back into regular EObjects
if (reference.isContainment()) {
if (!reference.isMany()) {
Object value = object.eGet(reference);
EObject newValue = (EObject) notification.getNewValue();
if (value == newValue) {
EObjectUtil.deproxify(newValue);
}
} else {
@SuppressWarnings("unchecked")
List<Object> values = (List<Object>) object.eGet(reference);
EObject newValue = (EObject) notification.getNewValue();
if (values.contains(newValue)) {
EObjectUtil.deproxify(newValue);
}
}
}
break;
case Notification.ADD_MANY:
// Convert added objects plus its directly and indirectly contained children
// back into regular EObjects
if (reference.isContainment()) {
@SuppressWarnings("unchecked")
List<Object> values = (List<Object>) object.eGet(reference);
@SuppressWarnings("unchecked")
List<EObject> newValues = (List<EObject>) notification.getNewValue();
for (EObject newValue : newValues) {
if (values.contains(newValue)) {
EObjectUtil.deproxify(newValue);
}
}
}
break;
}
}
}
}
}
@Override
public boolean isPostcommitOnly() {
return true;
}
}