/* * Copyright (c) 2008, 2009 Borland Software Corporation * * 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: * Artem Tikhomirov (Borland) - initial API and implementation */ package org.eclipse.gmf.internal.common.migrate; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EDataType; import org.eclipse.emf.ecore.EEnum; import org.eclipse.emf.ecore.EFactory; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.FeatureMap; import org.eclipse.emf.ecore.util.FeatureMapUtil; /** * Copies object tree, replacing elements with new instances from another metamodel, * with option to ignore certain features. * Ignored features and their owners are recorded for later processing (usually, * model version migration) * @author artem */ public class FilteringCopier extends EcoreUtil.Copier { private final HashMap<EStructuralFeature, List<EObject>> myIgnoredFeatures; private final HashMap<EStructuralFeature, EStructuralFeature> mySubstitutedFeatures; private final HashMap<EStructuralFeature, EClass> myIgnoreData; private final EPackage ePack; private final EFactory eFact; /** * Same as <code>FilteringCopier(false, true, metaPackage)</code> */ public FilteringCopier(EPackage metaPackage) { this(false, true, metaPackage); } /** * @param resolveProxies - see superclass for documentation * @param useOriginalReferences - see superclass for documentation * @param metaPackage target metamodel, the one copies would get created with. */ public FilteringCopier(boolean resolveProxies, boolean useOriginalReferences, EPackage metaPackage) { super(resolveProxies, useOriginalReferences); ePack = metaPackage; eFact = ePack.getEFactoryInstance(); myIgnoredFeatures = new HashMap<EStructuralFeature, List<EObject>>(); mySubstitutedFeatures = new HashMap<EStructuralFeature, EStructuralFeature>(); myIgnoreData = new HashMap<EStructuralFeature, EClass>(); } /** * @param eFeature property from <em>old/original</em> metamodel */ public void ignore(EStructuralFeature eFeature) { assert eFeature != null; assert !myIgnoredFeatures.containsKey(eFeature); myIgnoredFeatures.put(eFeature, new LinkedList<EObject>()); } /** * Marks feature to be ignored although only in subclasses of specific type. * This may be handy when feature was declared in some generic supertype, * shared in few different hierarchies. When feature ceases to be useful * in any of the hierarchies, it's reasonable to exclude only that hierarchy * during the copy. * XXX perhaps, multiple classes may be specified here, though for now suffice to have one */ public void ignoreIn(EStructuralFeature eFeature, EClass original) { ignore(eFeature); myIgnoreData.put(eFeature, original); } /** * Features that have different containing class in a new model won't be found * using default logic (which relies on {@link EStructuralFeature#getEContainingClass()}) * and hence need proper substitute to be specified. * XXX Perhaps, this would also work for renamed features? */ public void substitute(EStructuralFeature oldFeature, EStructuralFeature newFeature) { assert oldFeature != null && newFeature != null; assert !mySubstitutedFeatures.containsKey(oldFeature); mySubstitutedFeatures.put(oldFeature, newFeature); } /** * Handy alternative to copy()/copyReferences() sequence, similar to * {@link EcoreUtil#copy(EObject)} */ public EObject go(EObject original) { EObject result = copy(original); copyReferences(); return result; } /** * Access to collected objects that were found holding/referencing * values of the feature passed. * * @param eFeature one of the properties registered with * {@link #ignore(EStructuralFeature)} prior to {@link #go(EObject)}. * @return unmodifiable collection of objects. */ public List<EObject> getIgnoredOwners(EStructuralFeature eFeature) { return Collections.unmodifiableList(myIgnoredFeatures.get(eFeature)); } /** * Copy-of method from superclass, with injected support for feature filtering */ @Override public EObject copy(EObject eObject) { EObject copyEObject = createCopy(eObject); put(eObject, copyEObject); EClass eClass = eObject.eClass(); for (int i = 0, size = eClass.getFeatureCount(); i < size; ++i) { EStructuralFeature eStructuralFeature = eClass.getEStructuralFeature(i); // [artem] >>> if (isIgnored(eStructuralFeature, eObject)) { handleIgnored(eStructuralFeature, eObject); continue; } // [artem] <<< if (eStructuralFeature.isChangeable() && !eStructuralFeature.isDerived()) { if (eStructuralFeature instanceof EAttribute) { copyAttribute((EAttribute) eStructuralFeature, eObject, copyEObject); } else { EReference eReference = (EReference) eStructuralFeature; if (eReference.isContainment()) { copyContainment(eReference, eObject, copyEObject); } } } } copyProxyURI(eObject, copyEObject); return copyEObject; } /** * Copy-of method from superclass, with injected support to transform * attribute values (mostly enumerations) */ @Override protected void copyAttribute(EAttribute eAttribute, EObject eObject, EObject copyEObject) { if (eObject.eIsSet(eAttribute)) { if (FeatureMapUtil.isFeatureMap(eAttribute)) { FeatureMap featureMap = (FeatureMap) eObject.eGet(eAttribute); for (int i = 0, size = featureMap.size(); i < size; ++i) { EStructuralFeature feature = featureMap.getEStructuralFeature(i); if (feature instanceof EReference && ((EReference) feature).isContainment()) { Object value = featureMap.getValue(i); if (value != null) { copy((EObject) value); } } } } else if (eAttribute.isMany()) { List<?> source = (List<?>) eObject.eGet(eAttribute); @SuppressWarnings("unchecked") List<Object> target = (List<Object>) copyEObject.eGet(getTarget(eAttribute)); if (source.isEmpty()) { target.clear(); } else { // [artem] >>> if (needsTransform(eAttribute, eObject)) { for (Object o : source) { target.add(transformValue(eAttribute, o)); } } else { // [artem] <<< target.addAll(source); } } } else { // [artem] >>> if (needsTransform(eAttribute, eObject)) { copyEObject.eSet(getTarget(eAttribute), transformValue(eAttribute, eObject.eGet(eAttribute))); } else { // [artem] <<< copyEObject.eSet(getTarget(eAttribute), eObject.eGet(eAttribute)); } } } } /* * Try to get similar class from supplied metamodel, to create * a new instance of copied object from another metamodel. */ @Override protected EClass getTarget(EClass eClass) { EClassifier c = ePack.getEClassifier(eClass.getName()); if (c instanceof EClass) { return (EClass) c; } return super.getTarget(eClass); } @Override protected EStructuralFeature getTarget(EStructuralFeature sf) { if (mySubstitutedFeatures.containsKey(sf)) { return mySubstitutedFeatures.get(sf); } EStructuralFeature rv = getTarget(sf.getEContainingClass()).getEStructuralFeature(sf.getName()); if (rv != null) { return rv; } return super.getTarget(sf); } @Override protected void copyReference(EReference reference, EObject object, EObject copyEObject) { // do not navigate excluded reference during Copier#copyReferences() if (!isIgnored(reference, object)) { super.copyReference(reference, object, copyEObject); } } // // Custom additions // /** * Both references and attributes get here, former regardless of their * isContainment state, effectively allowing to perform custom handling * of any subset of element data. Features that are ignored are instantly * handed to {@link #handleIgnored(EStructuralFeature, EObject)} and no * further processing is done for them by copier. * * @return true to indicate feature should not be processed for copy. */ protected boolean isIgnored(EStructuralFeature eFeature, EObject original) { if (myIgnoredFeatures.containsKey(eFeature)) { if (myIgnoreData.containsKey(eFeature)) { EClass filter = myIgnoreData.get(eFeature); return filter.isSuperTypeOf(original.eClass()); } return true; } return false; } protected void handleIgnored(EStructuralFeature eFeature, EObject original) { myIgnoredFeatures.get(eFeature).add(original); } /** * @return true to indicate attribute values need special care */ protected boolean needsTransform(EAttribute eAttr, EObject original) { return eAttr.getEType() instanceof EEnum; } /** * Produces attribute value that complies with new model. Mostly needed * for data types and enumerations. For attributes with * <code>isMany == true</code> each single value (element of original collection) * is passed one by one. * * Though inherently protected, the method is visible to everyone to allow * post-processing/migration code to utilize it for further model tweaking. * @param eAttr structural feature from <em>original metamodel</em> * @param value original model's attribute value */ public Object transformValue(EAttribute eAttr, Object value) { if (eAttr.getEType() instanceof EEnum) { EFactory f = eAttr.getEContainingClass().getEPackage().getEFactoryInstance(); String s = f.convertToString(eAttr.getEAttributeType(), value); EClassifier newModelEnum = ePack.getEClassifier(eAttr.getEType().getName()); Object r = eFact.createFromString((EDataType) newModelEnum, s); return r; } return value; } }