/*******************************************************************************
* Copyright (c) 2007,2010 Eclipse Modeling Project 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:
* E.D.Willink - initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.examples.editor.ui.utils;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EGenericType;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.EcoreUtil.Copier;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl;
import org.eclipse.ocl.examples.common.utils.ClassUtils;
import org.eclipse.ocl.examples.common.utils.XMIUtils.IdCreator;
/**
* EcoreUpdater provides facilities to take a copy of an Ecore model, and (subsequently)
* to update an Ecore model from an edited copy. The update performs changes to the model
* so that its content is the same as the edited copy, thereby preserving EObjects in so
* far as possible.
*/
public class EcoreUpdater
{
public static void computeIdMaps(XMLResource resource, Map<String, EObject> idToObjectMap, Map<EObject, String> objectToIdMap) throws MissingXmiIdException {
List<EObject> eObjects = computeIdMaps(resource, idToObjectMap, objectToIdMap, null);
if (eObjects != null)
throw new MissingXmiIdException(eObjects.get(0));
}
/**
* Compute one or both of the id-to-object and object-to-id maps for a resource.
* If idCreator is non-null, it is invoked to provide an id for each object that
* lacks an id. If on completion all objects have an id, null is returned,
* otherwise a list of id-less objects is returned.
*
* @param resource
* @param idToObjectMap may be null
* @param objectToIdMap may be null
* @param idCreator may be null
* @return
*/
public static List<EObject> computeIdMaps(XMLResource resource, Map<String, EObject> idToObjectMap, Map<EObject, String> objectToIdMap, IdCreator idCreator) {
if (idToObjectMap != null)
idToObjectMap.clear();
else
idToObjectMap = new HashMap<String, EObject>();
if (objectToIdMap != null)
objectToIdMap.clear();
List<EObject> idLess = null;
for (TreeIterator<EObject> iterator = resource.getAllContents(); iterator.hasNext(); ) {
EObject object = iterator.next();
if (!(object instanceof EGenericType)) {
String id = resource.getID(object);
if (id == null) {
if (idLess == null)
idLess = new ArrayList<EObject>(100);
idLess.add(object);
}
else {
idToObjectMap.put(id, object);
if (objectToIdMap != null)
objectToIdMap.put(object, id);
}
}
}
if (idLess != null) {
if (idCreator == null) {
if (objectToIdMap != null)
for (EObject object : idLess)
objectToIdMap.put(object, null);
return idLess;
}
else {
for (EObject object : idLess) {
String id = idCreator.createId(object, idToObjectMap.keySet());
idToObjectMap.put(id, object);
if (objectToIdMap != null)
objectToIdMap.put(object, id);
}
}
}
return null;
}
/**
* Return a copy of the contents (EObjects, uri and xmi:ids) of oldResource.
* <p>
* This is basically EcoreUtil.copyAll, wrapped up in an XMLResource with an
* added copy of uri and xmi:ids.
* <p>
* The copy is not registered with any ResourceSet. It is intended primarily for
* subsequent use by EcoreUpdater.
* <p>
* Use {@link org.eclipse.ocl.examples.common.utils.XMIUtils#assignIds} to avoid MissingXmiIdExceptions.
*
* @param oldResource to be copied
* @return copy
* @throws MissingXmiIdException if any oldResource object lacks an xmi:id
*/
public static XMLResource copy(XMLResource oldResource) throws MissingXmiIdException {
XMLResource newResource = new XMLResourceImpl();
copy(newResource, oldResource);
return newResource;
}
/**
* Copy the contents (EObjects, uri and xmi:ids) from oldResource to newResource.
* <p>
* This is basically EcoreUtil.copyAll, wrapped up in an XMLResource with an
* added copy of uri and xmi:ids.
* <p>
* The copy is not registered with any ResourceSet. It is intended primarily for
* subsequent use by EcoreUpdater.
* <p>
* Use {@link org.eclipse.ocl.examples.common.utils.XMIUtils#assignIds} to avoid MissingXmiIdExceptions.
*
* @param newResource to be defined
* @param oldResource to be copied
* @throws MissingXmiIdException if any oldResource object lacks an xmi:id
*/
public static void copy(XMLResource newResource, XMLResource oldResource) throws MissingXmiIdException {
newResource.setURI(oldResource.getURI());
Copier copier = new Copier();
newResource.getContents().addAll(copier.copyAll(oldResource.getContents()));
copier.copyReferences();
for (EObject oldObject : copier.keySet()) {
EObject newObject = copier.get(oldObject);
if (!(newObject instanceof EGenericType)) {
String id = oldResource.getID(oldObject);
if (id == null)
throw new MissingXmiIdException(oldObject);
newResource.setID(newObject, id);
}
}
for (TreeIterator<EObject> iterator = newResource.getAllContents(); iterator.hasNext(); ) {
EObject newObject = iterator.next();
if (!(newObject instanceof EGenericType)) {
String id = newResource.getID(newObject);
if (id == null) {
EObject newContainer = newObject.eContainer();
String containerId = newResource.getID(newContainer);
if (containerId == null)
throw new MissingXmiIdException(newContainer);
EStructuralFeature containingFeature = newObject.eContainingFeature();
EObject oldContainer = oldResource.getEObject(containerId);
Object oldObject = oldContainer.eGet(containingFeature);
if (containingFeature.isMany()) {
List<?> newList = (List<?>) newContainer.eGet(containingFeature);
int index = newList.indexOf(newObject);
oldObject = ((List<?>)oldObject).get(index);
}
id = oldResource.getID((EObject) oldObject);
newResource.setID(newObject, id);
}
}
}
}
public static void update(XMLResource oldResource, XMLResource newResource) throws MissingElementException {
EcoreUpdater updater = new EcoreUpdater(oldResource, newResource);
updater.initialize();
// if (debug)
// updater.debug(System.out);
updater.update();
}
private final XMLResource resource;
private final XMLResource newResource;
private final Map<String, EObject> idToObjectMap = new HashMap<String, EObject>();
private final Map<EObject, String> objectToIdMap = new HashMap<EObject, String>();
private final Map<EObject, String> newObjectToIdMap = new HashMap<EObject, String>();
// private final Map<String, List<ENamedElement>> debugMap = new HashMap<String, List<ENamedElement>>();
public EcoreUpdater(XMLResource resource, XMLResource newResource) {
this.resource = resource;
this.newResource = newResource;
}
protected void installCopy(EObject oldObject, EObject newObject) {
String id = newObjectToIdMap.get(newObject);
resource.setID(oldObject, id);
idToObjectMap.put(id, oldObject);
objectToIdMap.put(oldObject, id);
for (EStructuralFeature feature : newObject.eClass().getEAllStructuralFeatures()) {
if ((feature instanceof EReference) && ((EReference) feature).isContainment()) {
Object newValue = newObject.eGet(feature);
Object oldValue = oldObject.eGet(feature);
if (newValue == null)
;
else if (!feature.isMany())
installCopy((EObject)oldValue, (EObject)newValue);
else if (newValue instanceof List<?>) {
List<?> newList = (List<?>) newValue;
List<?> oldList = (List<?>) oldValue;
for (int i = 0; i < newList.size(); i++)
installCopy((EObject)oldList.get(i), (EObject)newList.get(i));
}
else
throw new UnsupportedOperationException(getClass().getName() + ".installCopy for " + newValue.getClass().getName());
}
}
}
public void debug(PrintStream s) {
System.out.println("-----------------------------------------------");
List<String> keys = new ArrayList<String>(idToObjectMap.keySet());
Collections.sort(keys);
for (String key : keys) {
EObject object = idToObjectMap.get(key);
if (object instanceof ENamedElement)
System.out.println(key + " ==> " + object.eClass().getName() + "." + ((ENamedElement) object).getName());
else
System.out.println(key + " ==> " + object.eClass().getName());
}
}
public void initialize() throws MissingXmiIdException {
//
// Compute the maps:
// 1) because access to the resource maps is deprecated
// 2) because the resource maps exclude EGenericType
// 3) because the resource maps are only computed on load, not on edit
//
computeIdMaps(resource, idToObjectMap, objectToIdMap);
computeIdMaps(newResource, null, newObjectToIdMap);
// for (TreeIterator<EObject> iterator = resource.getAllContents(); iterator.hasNext(); ) {
// EObject object = iterator.next();
// if (object instanceof ENamedElement) {
// String name = ((ENamedElement) object).getName();
// List<ENamedElement> list = debugMap.get(name);
// if (list == null) {
// list = new ArrayList<ENamedElement>();
// debugMap.put(name, list);
// }
// list.add((ENamedElement) object);
// }
// if (object instanceof ENamedElement)
// System.out.println(object.eClass().getName() + "." + ((ENamedElement) object).getName());
// }
}
protected <E extends EObject> E newOldObjectFor(E newObject) throws MissingElementException {
if (newObject == null)
return newObject;
if (newObject.eIsProxy())
return newObject;
if (newObject.eResource() != newResource)
return newObject;
String id = newObjectToIdMap.get(newObject);
if (id == null)
throw new MissingXmiIdException(newObject);
EObject oldObject = idToObjectMap.get(id);
if (oldObject == null)
throw new MissingObjectException(newObject, id);
return ClassUtils.asClassUnchecked(oldObject);
}
public void update() throws MissingElementException {
updateList(null, resource.getContents(), newResource.getContents());
}
protected void updateEObject(EObject oldObject, EObject newObject) throws MissingElementException {
List<EStructuralFeature> features = oldObject.eClass().getEAllStructuralFeatures();
for (EStructuralFeature feature : features) {
if (feature.isDerived() || feature.isVolatile() || feature.isTransient())
continue;
boolean oldIsSet = oldObject.eIsSet(feature);
boolean newIsSet = newObject.eIsSet(feature);
if (!newIsSet) {
if (oldIsSet)
oldObject.eUnset(feature);
}
else {
Object newValue = newObject.eGet(feature, false);
if (feature instanceof EAttribute) {
if (!oldIsSet)
oldObject.eSet(feature, newValue);
else {
Object oldValue = oldObject.eGet(feature, false);
if ((newValue != oldValue) && ((newValue == null) || !newValue.equals(oldValue)))
oldObject.eSet(feature, newValue);
}
} else if (!feature.isMany()) {
if (((EReference) feature).isContainment()) {
Object oldValue = oldObject.eGet(feature, false);
if (!oldIsSet || (oldValue != null) || (newValue != null))
updateEObject((EObject)oldValue, (EObject)newValue);
}
else {
EObject newOldValue = newOldObjectFor((EObject)newValue);
if (!oldIsSet) {
if ((newOldValue != null) && newOldValue.eIsProxy())
oldObject.eSet(feature, EcoreUtil.copy(newOldValue));
else
oldObject.eSet(feature, newOldValue);
}
else {
Object oldValue = oldObject.eGet(feature, false);
if (oldValue != newOldValue) {
if ((newOldValue != null) && newOldValue.eIsProxy()) {
if (oldValue != null)
((InternalEObject)oldValue).eSetProxyURI(((InternalEObject)newOldValue).eProxyURI());
else
oldObject.eSet(feature, EcoreUtil.copy(newOldValue));
}
else {
oldObject.eSet(feature, newOldValue);
}
}
}
}
} else if (newValue instanceof List<?>) {
Object oldValue = oldObject.eGet(feature, false);
List<EObject> oldList = ClassUtils.asClassUnchecked(oldValue);
List<EObject> newList = ClassUtils.asClassUnchecked(newValue);
updateList((EReference) feature, oldList, newList);
} else
throw new UnsupportedOperationException(getClass().getName() + ".updateEObject for " + newValue.getClass().getName());
}
}
}
protected <E extends EObject> void updateList(EReference feature, List<E> oldList, List<E> newList) throws MissingElementException {
boolean isContainment = (feature == null) || feature.isContainment();
int iMaxNew = newList.size();
for (int i = 0; i < iMaxNew; i++) {
E newObject = newList.get(i);
EObject oldObject;
if (isContainment) {
String id = newObjectToIdMap.get(newObject);
if (id == null)
throw new MissingXmiIdException(newObject);
oldObject = idToObjectMap.get(id);
if ((oldObject == null) || (oldObject.eClass() != (newObject).eClass())) {
oldObject = EcoreUtil.copy(newObject);
installCopy(oldObject, newObject);
}
}
else
oldObject = newOldObjectFor(newObject);
int index = oldList.indexOf(oldObject);
if (index == i)
;
else if ((index > i) && (oldList instanceof EList<?>))
((EList<?>)oldList).move(i, index);
else {
E oldE = ClassUtils.asClassUnchecked(oldObject);
oldList.add(i, oldE);
}
if (isContainment)
updateEObject(oldObject, newObject);
}
while (iMaxNew < oldList.size())
oldList.remove(iMaxNew);
}
}