/*
* Copyright (c) 2011-2013, 2015, 2016 Eike Stepper (Berlin, Germany) 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:
* Simon McDuff - initial API and implementation
* Eike Stepper - maintenance
* Christian W. Damus (CEA) - bug 376620: notifications for primitive-valued attributes
*/
package org.eclipse.emf.internal.cdo.object;
import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.model.CDOModelUtil;
import org.eclipse.emf.cdo.common.model.CDOType;
import org.eclipse.emf.cdo.common.revision.CDORevisionFactory;
import org.eclipse.emf.cdo.common.revision.delta.CDOAddFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOClearFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOContainerFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOMoveFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta;
import org.eclipse.emf.cdo.spi.common.revision.CDOFeatureDeltaVisitorImpl;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.net4j.util.ReflectUtil;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.notify.impl.NotificationChainImpl;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.spi.cdo.InternalCDOObject;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
/**
* @author Simon McDuff
* @since 2.0
*/
public class CDONotificationBuilder extends CDOFeatureDeltaVisitorImpl
{
private CDOView view;
private InternalEObject object;
private CDORevisionDelta revisionDelta;
private NotificationChainImpl notification;
private Set<CDOObject> detachedObjects;
private InternalCDORevision oldRevision;
/**
* @since 3.0
*/
public CDONotificationBuilder(CDOView view)
{
this.view = view;
}
/**
* @since 3.0
*/
public CDOView getView()
{
return view;
}
/**
* @since 3.0
*/
public synchronized NotificationChain buildNotification(InternalEObject object, InternalCDORevision oldRevision, CDORevisionDelta revisionDelta,
Set<CDOObject> detachedObjects)
{
notification = new NotificationChainImpl();
this.object = object;
this.revisionDelta = revisionDelta;
this.detachedObjects = detachedObjects;
this.oldRevision = oldRevision;
revisionDelta.accept(this);
return notification;
}
public synchronized NotificationChain buildNotification(InternalCDOObject object, InternalCDORevision newRevision)
{
InternalCDORevision oldRevision = (InternalCDORevision)CDORevisionFactory.DEFAULT.createRevision(object.eClass());
CDORevisionDelta revisionDelta = newRevision.compare(oldRevision);
return buildNotification(object, oldRevision, revisionDelta, null);
}
@Override
public void visit(CDOMoveFeatureDelta delta)
{
EStructuralFeature feature = delta.getFeature();
int oldPosition = delta.getOldPosition();
int newPosition = delta.getNewPosition();
Object oldValue = delta.getValue();
if (oldValue instanceof CDOID)
{
CDOID oldID = (CDOID)oldValue;
CDOObject object = findObjectByID(oldID);
if (object != null)
{
oldValue = object;
}
}
add(new CDODeltaNotificationImpl(object, Notification.MOVE, feature, Integer.valueOf(oldPosition), oldValue, newPosition));
}
@Override
public void visit(CDOAddFeatureDelta delta)
{
EStructuralFeature feature = delta.getFeature();
add(new CDODeltaNotificationImpl(object, Notification.ADD, feature, getOldValue(feature), delta.getValue(), delta.getIndex()));
}
@Override
public void visit(CDORemoveFeatureDelta delta)
{
EStructuralFeature feature = delta.getFeature();
int index = delta.getIndex();
Object oldValue = delta.getValue();
if (oldValue instanceof CDOID)
{
CDOID oldID = (CDOID)oldValue;
CDOObject object = findObjectByID(oldID);
if (object != null)
{
oldValue = object;
}
}
add(new CDODeltaNotificationImpl(object, Notification.REMOVE, feature, oldValue, null, index));
}
@Override
public void visit(CDOSetFeatureDelta delta)
{
EStructuralFeature feature = delta.getFeature();
Object oldValue = delta.getOldValue();
if (oldValue == null)
{
oldValue = getOldValue(feature);
}
if (oldValue instanceof CDOID)
{
CDOID oldID = (CDOID)oldValue;
CDOObject object = findObjectByID(oldID);
if (object != null)
{
oldValue = object;
}
}
Object newValue = delta.getValue();
add(createNotification(Notification.SET, feature, oldValue, newValue, delta.getIndex()));
}
@Override
public void visit(CDOUnsetFeatureDelta delta)
{
EStructuralFeature feature = delta.getFeature();
Object oldValue = getOldValue(feature);
if (oldValue instanceof CDOID)
{
CDOID oldID = (CDOID)oldValue;
CDOObject object = findObjectByID(oldID);
if (object != null)
{
oldValue = object;
}
}
add(createNotification(Notification.UNSET, feature, oldValue, null, Notification.NO_INDEX));
}
@Override
public void visit(CDOClearFeatureDelta delta)
{
EStructuralFeature feature = delta.getFeature();
Object oldValue = getOldValue(feature);
if (oldValue instanceof List<?>)
{
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>)oldValue;
if (!list.isEmpty())
{
list = new ArrayList<Object>(list); // Copy the list so that it.set() does not change the frozen oldRevision
boolean changed = false;
for (ListIterator<Object> it = list.listIterator(); it.hasNext();)
{
Object element = it.next();
if (element instanceof CDOID)
{
CDOID id = (CDOID)element;
CDOObject oldObject = findObjectByID(id);
if (oldObject != null)
{
it.set(oldObject);
changed = true;
}
}
}
if (changed)
{
oldValue = list;
}
}
}
add(new CDODeltaNotificationImpl(object, Notification.REMOVE_MANY, feature, oldValue, null));
}
@Override
public void visit(CDOContainerFeatureDelta delta)
{
Object oldValue = null;
if (oldRevision != null)
{
oldValue = oldRevision.getContainerID();
if (oldValue instanceof CDOID)
{
CDOID oldID = (CDOID)oldValue;
CDOObject object = findObjectByID(oldID);
if (object != null)
{
oldValue = object;
}
}
}
add(new CDODeltaNotificationImpl(object, Notification.SET, EcorePackage.eINSTANCE.eContainmentFeature(), oldValue, delta.getContainerID()));
}
private CDOObject findObjectByID(CDOID id)
{
CDOObject object = view.getObject(id, false);
if (object == null)
{
object = findDetachedObjectByID(id);
}
return object;
}
private CDOObject findDetachedObjectByID(CDOID id)
{
if (detachedObjects != null)
{
for (CDOObject object : detachedObjects)
{
if (id == object.cdoID())
{
return object;
}
}
}
return null;
}
private Object getOldValue(EStructuralFeature feature)
{
if (oldRevision == null)
{
return null;
}
return oldRevision.getValue(feature);
}
private CDODeltaNotificationImpl createNotification(int eventType, EStructuralFeature feature, Object oldValue, Object newValue, int position)
{
EClassifier eType = feature.getEType();
if (oldValue != null || newValue != null)
{
CDOType type = CDOModelUtil.getType(eType);
if (type != null)
{
if (oldValue != null)
{
oldValue = type.convertToEMF(eType, oldValue);
}
if (newValue != null)
{
newValue = type.convertToEMF(eType, newValue);
}
}
}
Class<?> instanceClass = eType.getInstanceClass();
if (instanceClass.isPrimitive())
{
Object defaultValue = null;
if (oldValue == null)
{
defaultValue = feature.getDefaultValue();
oldValue = defaultValue;
}
if (newValue == null)
{
if (defaultValue == null)
{
defaultValue = feature.getDefaultValue();
}
newValue = defaultValue;
}
// There cannot be a position if it's a primitive value because primitives cannot be in lists
return createPrimitiveNotification(eventType, feature, instanceClass, oldValue, newValue);
}
return new CDODeltaNotificationImpl(object, eventType, feature, oldValue, newValue, position);
}
private CDODeltaNotificationImpl createPrimitiveNotification(int eventType, EStructuralFeature feature, Class<?> instanceClass, Object oldValue,
Object newValue)
{
switch (ReflectUtil.PrimitiveType.forClass(instanceClass))
{
case BOOLEAN:
return new CDODeltaNotificationImpl(object, eventType, feature, ((Boolean)oldValue).booleanValue(), ((Boolean)newValue).booleanValue());
case BYTE:
return new CDODeltaNotificationImpl(object, eventType, feature, ((Number)oldValue).byteValue(), ((Number)newValue).byteValue());
case CHAR:
return new CDODeltaNotificationImpl(object, eventType, feature, ((Character)oldValue).charValue(), ((Character)newValue).charValue());
case SHORT:
return new CDODeltaNotificationImpl(object, eventType, feature, ((Number)oldValue).shortValue(), ((Number)newValue).shortValue());
case INT:
return new CDODeltaNotificationImpl(object, eventType, feature, ((Number)oldValue).intValue(), ((Number)newValue).intValue());
case LONG:
return new CDODeltaNotificationImpl(object, eventType, feature, ((Number)oldValue).longValue(), ((Number)newValue).longValue());
case FLOAT:
return new CDODeltaNotificationImpl(object, eventType, feature, ((Number)oldValue).floatValue(), ((Number)newValue).floatValue());
case DOUBLE:
return new CDODeltaNotificationImpl(object, eventType, feature, ((Number)oldValue).doubleValue(), ((Number)newValue).doubleValue());
case VOID:
throw new IllegalArgumentException("Feature of void type not supported: " + feature); //$NON-NLS-1$
default:
throw new IllegalArgumentException("Not a primitive type: " + instanceClass); //$NON-NLS-1$
}
}
private void add(CDODeltaNotificationImpl newNotificaton)
{
newNotificaton.setRevisionDelta(revisionDelta);
if (notification.add(newNotificaton))
{
int size = notification.size();
if (size > 1)
{
CDODeltaNotificationImpl previousNotification = (CDODeltaNotificationImpl)notification.get(size - 2);
// Ensure that previousNotification.next is set
previousNotification.add(newNotificaton);
}
}
}
}