/** * <copyright> * * Copyright (c) 2009, 2010 Springsite BV (The Netherlands) 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: * Martin Taal - Initial API and implementation * * </copyright> * * $Id: EMFModelConverter.java,v 1.23 2011/08/29 05:16:04 mtaal Exp $ */ package org.eclipse.emf.texo.converter; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.FeatureMapUtil; import org.eclipse.emf.texo.component.TexoComponent; import org.eclipse.emf.texo.model.ModelFeatureMapEntry; import org.eclipse.emf.texo.model.ModelObject; import org.eclipse.emf.texo.model.ModelResolver; import org.eclipse.emf.texo.provider.IdProvider; import org.eclipse.emf.texo.utils.ModelUtils; /** * Compares {@link ModelObject} objects and reports the first encountered difference as a * {@link ObjectComparatorException}. * * This is just a simple comparison class, for extensive comparison consider converting objects to EMF objects using the * {@link ModelEMFConverter} and then use EMF Compare. * * @author <a href="mtaal@elver.org">Martin Taal</a> * @see ModelObject */ public class ObjectComparator implements TexoComponent { private Stack<String> path = new Stack<String>(); private List<Object> compared = new ArrayList<Object>(); /** * Clears internal datastructure, must be called when doing subsequent compare calls. */ public void clear() { path.clear(); compared.clear(); } /** * Compare two objects and throw an {@link ObjectComparatorException} if there are differences. */ public void compare(Object o1, Object o2) { if (o1 == o2) { return; } if (compared.contains(o1)) { return; } compared.add(o1); if (o1 == null || o2 == null) { throw new ObjectComparatorException(o1, o2, "One of the values is null: " + o1 + "/" + o2 + getPath()); //$NON-NLS-1$ //$NON-NLS-2$ } if (ModelResolver.getInstance().isModelEnabled(o1) != ModelResolver.getInstance().isModelEnabled(o2)) { throw new ObjectComparatorException(o1, o2, "One of these 2 is not model enabled: " + o1 + "/" + o2 + getPath()); //$NON-NLS-1$ //$NON-NLS-2$ } final ModelObject<?> m1 = ModelResolver.getInstance().getModelObject(o1); final ModelObject<?> m2 = ModelResolver.getInstance().getModelObject(o2); compareType(m1, m2); path.push(m1.eClass().getName()); for (EStructuralFeature eFeature : m1.eClass().getEAllStructuralFeatures()) { if (eFeature.isVolatile()) { continue; } if (FeatureMapUtil.isFeatureMap(eFeature)) { compareFeatureMapEFeature(m1, m2, eFeature); } else if (ModelUtils.isEMap(eFeature)) { compareMapEFeature(m1, m2, eFeature); } else if (eFeature.isMany()) { compareManyEFeature(m1, m2, eFeature); } else if (eFeature instanceof EAttribute) { compareSingleEAttribute(m1, m2, (EAttribute) eFeature); } else { compareSingleEReference(m1, m2, (EReference) eFeature); } } path.pop(); } protected void compareFeatureMapEFeature(ModelObject<?> m1, ModelObject<?> m2, EStructuralFeature eFeature) { @SuppressWarnings("unchecked") final Collection<Object> targetCollection = (Collection<Object>) m2.eGet(eFeature); @SuppressWarnings("unchecked") final Collection<Object> sourceCollection = (Collection<Object>) m1.eGet(eFeature); path.push(eFeature.getName()); if (targetCollection.size() != sourceCollection.size()) { throw new ObjectComparatorException(m1, m2, "Collections have different sizes " + sourceCollection.size() + "/" //$NON-NLS-1$//$NON-NLS-2$ + targetCollection.size() + getPath()); } final Iterator<?> targetIterator = targetCollection.iterator(); int i = 0; for (Object sourceEntry : (Collection<?>) m1.eGet(eFeature)) { path.push("[" + i + "]"); //$NON-NLS-1$ //$NON-NLS-2$ final Object targetEntry = targetIterator.next(); final ModelFeatureMapEntry<?> sourceModelEntry = ModelResolver.getInstance().getModelFeatureMapEntry(eFeature, sourceEntry); final ModelFeatureMapEntry<?> targetModelEntry = ModelResolver.getInstance().getModelFeatureMapEntry(eFeature, targetEntry); if (sourceModelEntry.getEStructuralFeature() != targetModelEntry.getEStructuralFeature()) { throw new ObjectComparatorException(sourceModelEntry, targetModelEntry, "Feature map entries have different efeatures " + sourceModelEntry + "/" //$NON-NLS-1$//$NON-NLS-2$ + targetModelEntry + getPath()); } final Object v1 = sourceModelEntry.getValue(); final Object v2 = targetModelEntry.getValue(); if (sourceModelEntry.getEStructuralFeature() instanceof EReference) { compare(v1, v2); } else { compareValue(v1, v2, (EAttribute) sourceModelEntry.getEStructuralFeature()); } path.pop(); i++; } path.pop(); } protected void compareManyEFeature(ModelObject<?> m1, ModelObject<?> m2, EStructuralFeature eFeature) { final Object v1 = m1.eGet(eFeature); final Object v2 = m2.eGet(eFeature); if (v1.getClass() != v2.getClass()) { throw new ObjectComparatorException(v1, v2, "Different many efeature value types " + v1 + "/" + v2 + getPath()); //$NON-NLS-1$//$NON-NLS-2$ } boolean compareAsSet = v1 instanceof Set<?>; // compare many-to-many also as set if (!compareAsSet && eFeature instanceof EReference) { final EReference eReference = (EReference) eFeature; compareAsSet = eReference.isMany() && eReference.getEOpposite() != null && eReference.getEOpposite().isMany(); } if (ModelUtils.isEMap(eFeature)) { compareMapEFeature(m1, m2, eFeature); } else if (compareAsSet) { compareSetEFeature(m1, m2, eFeature); } else if (v2 instanceof List<?>) { compareListEFeature(m1, m2, eFeature); } } protected void compareMapEFeature(ModelObject<?> m1, ModelObject<?> m2, EStructuralFeature eFeature) { path.push(eFeature.getName()); final Map<?, ?> c1 = (Map<?, ?>) m1.eGet(eFeature); final Map<?, ?> c2 = (Map<?, ?>) m2.eGet(eFeature); if (c1.size() != c2.size()) { throw new ObjectComparatorException(m1, m2, "Different collection sizes " + c1.size() + "/" + c2.size() //$NON-NLS-1$ //$NON-NLS-2$ + getPath()); } final EClass mapEClass = ((EReference) eFeature).getEReferenceType(); final EStructuralFeature keyFeature = mapEClass.getEStructuralFeature("key"); //$NON-NLS-1$ for (Object key : c1.keySet()) { final Object o1 = c1.get(key); Object o2 = null; if (keyFeature instanceof EReference && IdProvider.getInstance().hasIdEAttribute(key)) { final Object id1 = IdProvider.getInstance().getId(key); if (id1 != null) { for (Object key2 : c2.keySet()) { if (IdProvider.getInstance().hasIdEAttribute(key2)) { final Object id2 = IdProvider.getInstance().getId(key2); if (id2 != null && id1.equals(id2)) { o2 = c2.get(key2); break; } } } } } if (o2 == null) { o2 = c2.get(key); } path.push("[" + key + "]"); //$NON-NLS-1$//$NON-NLS-2$ if (eFeature instanceof EReference) { compare(o1, o2); } else { compareValue(o1, o2, (EAttribute) eFeature); } path.pop(); } path.pop(); } protected void compareSetEFeature(ModelObject<?> m1, ModelObject<?> m2, EStructuralFeature eFeature) { path.push(eFeature.getName()); final Collection<?> c1 = (Collection<?>) m1.eGet(eFeature); final Collection<?> c2 = (Collection<?>) m2.eGet(eFeature); if (c1.size() != c2.size()) { throw new ObjectComparatorException(m1, m2, "Different collection sizes " + c1.size() + "/" + c2.size() //$NON-NLS-1$ //$NON-NLS-2$ + getPath()); } final Iterator<?> i1 = c1.iterator(); int cnt = 0; while (i1.hasNext()) { final Object o1 = i1.next(); path.push("[" + cnt + "]"); //$NON-NLS-1$ //$NON-NLS-2$ boolean found = false; for (Object o2 : c2) { try { if (eFeature instanceof EReference) { compare(o1, o2); } else { compareValue(o1, o2, (EAttribute) eFeature); } found = true; break; } catch (ObjectComparatorException e) { // ignore } } if (!found) { throw new ObjectComparatorException(m1, m2, "Value " + o1 + " not present in collection " + getPath()); //$NON-NLS-1$ //$NON-NLS-2$ } path.pop(); cnt++; } path.pop(); } protected void compareListEFeature(ModelObject<?> m1, ModelObject<?> m2, EStructuralFeature eFeature) { path.push(eFeature.getName()); final Collection<?> c1 = (Collection<?>) m1.eGet(eFeature); final Collection<?> c2 = (Collection<?>) m2.eGet(eFeature); if (c1.size() != c2.size()) { throw new ObjectComparatorException(m1, m2, "Different collection sizes " + c1.size() + "/" + c2.size() //$NON-NLS-1$ //$NON-NLS-2$ + getPath()); } final Iterator<?> i1 = c1.iterator(); final Iterator<?> i2 = c2.iterator(); int cnt = 0; while (i1.hasNext()) { final Object o1 = i1.next(); final Object o2 = i2.next(); path.push("[" + cnt + "]"); //$NON-NLS-1$ //$NON-NLS-2$ if (eFeature instanceof EReference) { compare(o1, o2); } else { compareValue(o1, o2, (EAttribute) eFeature); } path.pop(); cnt++; } path.pop(); } protected void compareSingleEAttribute(ModelObject<?> m1, ModelObject<?> m2, EAttribute eAttribute) { path.push(eAttribute.getName()); compareValue(m1.eGet(eAttribute), m2.eGet(eAttribute), eAttribute); path.pop(); } protected void compareSingleEReference(ModelObject<?> m1, ModelObject<?> m2, EReference eReference) { path.push(eReference.getName()); try { final Object o1 = m1.eGet(eReference); final Object o2 = m2.eGet(eReference); if (o1 == o2) { return; } if (o1 != null && o2 == null) { throw new ObjectComparatorException(o1, o2, "Different Values " + o1 + "/" + o2 + getPath()); //$NON-NLS-1$ //$NON-NLS-2$ } else if (o1 == null && o2 != null) { throw new ObjectComparatorException(o1, o2, "Different Values " + o1 + "/" + o2 + getPath()); //$NON-NLS-1$ //$NON-NLS-2$ } compare(o1, o2); } finally { path.pop(); } } protected void compareValue(Object v1, Object v2, EAttribute eAttribute) { if (v1 == v2) { return; } if (v1 != null && v1.getClass().isArray()) { checkEqualArrays(v1, v2, eAttribute); } else if (v1 != null && !v1.equals(v2)) { throw new ObjectComparatorException(v1, v2, "Different values " + v1 + "/" + v2 + getPath()); //$NON-NLS-1$ //$NON-NLS-2$ } } private void checkEqualArrays(Object v1, Object v2, EAttribute eAttribute) { if (v1 == null || !v1.getClass().isArray() || v2 == null || !v2.getClass().isArray()) { throw new ObjectComparatorException(v1, v2, "Different values " + v1 + "/" + v2 + getPath()); //$NON-NLS-1$ //$NON-NLS-2$ } final int l1 = Array.getLength(v1); final int l2 = Array.getLength(v1); if (l1 != l2) { throw new ObjectComparatorException(v1, v2, "Different values " + v1 + "/" + v2 + getPath()); //$NON-NLS-1$ //$NON-NLS-2$ } for (int i = 0; i < l1; i++) { final Object e1 = Array.get(v1, i); final Object e2 = Array.get(v2, i); compareValue(e1, e2, eAttribute); } } protected void compareType(ModelObject<?> m1, ModelObject<?> m2) { if (m1.eClass() != m2.eClass()) { throw new ObjectComparatorException(m1, m2, "Different EClasses " + m1.eClass().getName() + "/" //$NON-NLS-1$ //$NON-NLS-2$ + m2.eClass().getName() + getPath()); } } protected String getPath() { final StringBuilder sb = new StringBuilder(" - path: "); //$NON-NLS-1$ for (String part : path) { sb.append("/" + part); //$NON-NLS-1$ } return sb.toString(); } public class ObjectComparatorException extends RuntimeException { private static final long serialVersionUID = 1L; private Object o1; private Object o2; public ObjectComparatorException(Object o1, Object o2, String msg) { super(msg); this.o1 = o1; this.o2 = o2; } protected Object getO1() { return o1; } protected Object getO2() { return o2; } } }