/* license-start * * Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details, at <http://www.gnu.org/licenses/>. * * Contributors: * Crispico - Initial API and implementation * * license-end */ package com.crispico.flower.mp.codesync.code.adapter; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil; import com.crispico.flower.mp.codesync.base.CodeSyncAlgorithm; import com.crispico.flower.mp.codesync.base.FilteredIterable; import com.crispico.flower.mp.codesync.base.IModelAdapter; import com.crispico.flower.mp.codesync.base.action.ActionResult; import com.crispico.flower.mp.codesync.merge.SyncElementModelAdapter; import com.crispico.flower.mp.model.astcache.code.AstCacheCodePackage; import com.crispico.flower.mp.model.astcache.code.Operation; import com.crispico.flower.mp.model.astcache.code.Parameter; import com.crispico.flower.mp.model.codesync.AstCacheElement; import com.crispico.flower.mp.model.codesync.CodeSyncElement; import com.crispico.flower.mp.model.codesync.CodeSyncPackage; import com.crispico.flower.mp.model.codesync.FeatureChange; /** * @author Mariana */ public class CodeSyncElementModelAdapterLeft extends CodeSyncElementModelAdapter { public CodeSyncElementModelAdapterLeft() { super(); } public CodeSyncElementModelAdapterLeft(SyncElementModelAdapter modelAdapter) { super(modelAdapter); } @Override public Object getMatchKey(Object element) { CodeSyncElement codeSyncElement = (CodeSyncElement) element; FeatureChange change = codeSyncElement.getFeatureChanges().get(CodeSyncPackage.eINSTANCE.getCodeSyncElement_Name()); if (change != null) { return change.getNewValue(); } return super.getMatchKey(element); } /** * Filters out deleted {@link CodeSyncElement}s. Returns the new containment list from the {@link FeatureChange}s map for * the <code>feature</code>, if it exists. Also recreates the children from <code>correspondingIterable</code> from the * right side, in case the AST cache was deleted. */ @Override public Iterable<?> getContainmentFeatureIterable(final Object element, Object feature, Iterable<?> correspondingIterable) { Iterable<?> list = super.getContainmentFeatureIterable(element, feature, correspondingIterable); List result = (List) list; // if the AST cache was deleted, recreate the children using the corresponding iterable from the right side if (isUndefinedList(list)) { result = new ArrayList<Object>(); for (Object correspondingElement : correspondingIterable) { result.add(createChildOnContainmentFeature(element, feature, correspondingElement)); } } // get the children from the FeatureChange, if it exists FeatureChange change = getFeatureChange(element, feature); if (change != null) { result = (List) change.getNewValue(); } // filter out deleted elements return new FilteredIterable<Object, Object>((Iterator<Object>) result.iterator()) { protected boolean isAccepted(Object candidate) { if (candidate instanceof CodeSyncElement && ((CodeSyncElement) candidate).isDeleted()) return false; return true; } }; } /** * Returns the new value from the {@link FeatureChange}s map for the <code>feature</code>, if it exists. * Also sets the <code>correspondingValue</code> from the right side, in case the AST cache was deleted. */ @Override public Object getValueFeatureValue(Object element, Object feature, Object correspondingValue) { // if the AST cache was deleted, recreate the value using the corresponding value from the right side Object value = astCacheElementModelAdapter != null ? astCacheElementModelAdapter.getValueFeatureValue(element, feature, correspondingValue) : super.getValueFeatureValue(element, feature, correspondingValue); if (CodeSyncAlgorithm.UNDEFINED.equals(value)) { setValueFeatureValue(element, feature, correspondingValue); } // get the value from the FeatureChange, if it exists FeatureChange change = getFeatureChange(element, feature); if (change != null) { return change.getNewValue(); } return value; } /** * Before the features are processed for <code>element</code>, checks if the AST cache was deleted, and * recreates it. Note: we cannot do this while the features are processed because upon requesting the * value for a 2nd feature, the AST cache will be refreshed again, thus losing the value for a previously * processed feature. */ @Override public void beforeFeaturesProcessed(Object element, Object correspondingElement) { CodeSyncElement cse = getCodeSyncElement(element); if (cse != null) { if (cse.getAstCacheElement() == null || cse.getAstCacheElement().eResource() == null) { AstCacheElement ace = (AstCacheElement) createCorrespondingModelElement(correspondingElement); cse.setAstCacheElement(ace); } } } /** * Adds the {@link AstCacheElement} to the AST cache resource. */ @Override public void featuresProcessed(Object element) { CodeSyncElement cse = getCodeSyncElement(element); if (cse != null) { AstCacheElement ace = cse.getAstCacheElement(); if (ace != null && ace.eResource() == null) { addToResource(ace); } } } @Override public void allActionsPerformedForFeature(Object element, Object correspondingElement, Object feature) { CodeSyncElement cse = getCodeSyncElement(element); if (cse != null) { FeatureChange change = cse.getFeatureChanges().get(feature); if (change != null) { if (getModelAdapterFactorySet().getFeatureProvider(cse).getFeatureType(feature) == FEATURE_TYPE_CONTAINMENT) { List<Object> children = (List<Object>) super.getContainmentFeatureIterable(element, feature, null); List<Object> newValues = (List<Object>) change.getNewValue(); // iterate through the children list; for each child not found in the new children, see if it exists in the corresponding element for (Iterator it = children.iterator(); it.hasNext();) { Object existingChild = (Object) it .next(); Object matchKey = getModelAdapterFactory().getModelAdapter(existingChild).getMatchKey(existingChild); Object newChild = findChild(newValues, matchKey); if (newChild == null) { if (!elementContainsChildWithMatchKey(correspondingElement, feature, matchKey)) { // the child doesn't exist in the corresponding element either => delete it it.remove(); } } } // iterate through the new values; for each child not found in the children list, see if it exists in the corresponding element for (Object newChild : newValues) { Object matchKey = getModelAdapterFactory().getModelAdapter(newChild).getMatchKey(newChild); Object existingChild = findChild(children, matchKey); if (existingChild == null) { if (elementContainsChildWithMatchKey(correspondingElement, feature, matchKey)) { // the child exists in the corresponding element as well => add it to the children children.add(newChild instanceof EObject ? EcoreUtil.copy((EObject) newChild) : newChild); } } } // compare the new values with the existing children if (newValues.size() == children.size()) { int matches = 0; for (Object newChild : newValues) { Object existingChild = findChild(children, getModelAdapterFactory().getModelAdapter(newChild).getMatchKey(newChild)); if (existingChild != null) { children.remove(existingChild); children.add(newChild instanceof EObject ? EcoreUtil.copy((EObject) newChild) : newChild); matches++; } } if (newValues.size() == matches) { // the new values list and the existing list have the same children => remove the feature change cse.getFeatureChanges().removeKey(feature); } else { // the new values list and the existing list are not identical => update the old values change.setOldValue(children); } } } else { setValueFeatureValue(element, feature, change.getNewValue()); cse.getFeatureChanges().removeKey(feature); } } if (AstCacheCodePackage.eINSTANCE.getOperation_Parameters().equals(feature)) { AstCacheElement ace = cse.getAstCacheElement(); if (ace != null && ace instanceof Operation) { String newName = cse.getName().substring(0, cse.getName().indexOf("(")); newName += "("; for (Parameter parameter : ((Operation) ace).getParameters()) { newName += parameter.getType() + ","; } if (newName.endsWith(",")) { newName = newName.substring(0, newName.length() - 1); } newName += ")"; cse.setName(newName); } } } } @Override public void actionPerformed(Object element, Object feature, ActionResult result) { if (result != null && !result.conflict) { CodeSyncElement cse = getCodeSyncElement(element); if (cse != null) { FeatureChange change = cse.getFeatureChanges().get(feature); if (change != null) { int featureType = getModelAdapterFactorySet().getFeatureProvider(cse).getFeatureType(feature); if (featureType == IModelAdapter.FEATURE_TYPE_VALUE) { setValueFeatureValue(element, feature, change.getNewValue()); cse.getFeatureChanges().removeKey(feature); } else { if (featureType == IModelAdapter.FEATURE_TYPE_CONTAINMENT) { List<Object> children = (List<Object>) super.getContainmentFeatureIterable(element, feature, null); List<Object> newValues = (List<Object>) change.getNewValue(); if (result.childAdded) { Object existingChild = findChild(children, result.childMatchKey); if (existingChild == null) { // child added to source from new children => add it to the actual children as well Object newChild = findChild(newValues, result.childMatchKey); if (newChild != null) { children.add(newChild instanceof EObject ? EcoreUtil.copy((EObject) newChild) : newChild); } } else { // child added to model from source => add it to the new values as well Object newChild = findChild(newValues, result.childMatchKey); if (newChild == null) { newValues.add(existingChild instanceof EObject ? EcoreUtil.copy((EObject) existingChild) : existingChild); } } } else { Object existingChild = findChild(children, result.childMatchKey); if (existingChild != null) { // child removed from source => remove it from the actual children as well children.remove(existingChild); } else { Object newChild = findChild(newValues, result.childMatchKey); if (newChild != null) { // child removed from model => remove it from the new children as well newValues.remove(newChild); } } } if (newValues.size() == 0) { cse.getFeatureChanges().removeKey(feature); } } } } else { List<Object> children = (List<Object>) super.getContainmentFeatureIterable(element, feature, null); Object child = findChild(children, result.childMatchKey); if (child != null && child instanceof CodeSyncElement) { if (result.childAdded) { ((CodeSyncElement) child).setAdded(false); } else { children.remove(child); } } } } } } private boolean elementContainsChildWithMatchKey(Object element, Object feature, Object matchKey) { if (element == null || matchKey == null) { return false; } Iterable<?> children = getEObjectConverter().getModelAdapter(element).getContainmentFeatureIterable(element, feature, null); for (Object child : children) { if (matchKey.equals(getEObjectConverter().getModelAdapter(child).getMatchKey(child))) { return true; } } return false; } /** * Checks if the <code>list</code> contains the <code>child</code> based on its match key. */ private Object findChild(List list, Object matchKey) { if (matchKey == null) return null; for (Object existingChild : list) { if (matchKey.equals(getModelAdapterFactory().getModelAdapter(existingChild).getMatchKey(existingChild))) { return existingChild; } } return null; } }