package org.flowerplatform.editor.model.changes_processor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.change.ChangeDescription;
import org.eclipse.emf.ecore.change.ChangeKind;
import org.eclipse.emf.ecore.change.FeatureChange;
import org.eclipse.emf.ecore.change.ListChange;
import org.flowerplatform.common.util.Pair;
import org.flowerplatform.editor.model.EditorModelPlugin;
import org.flowerplatform.editor.model.change_processor.DiagramUpdaterChangeProcessorContext;
import org.flowerplatform.editor.model.change_processor.IDiagrammableElementFeatureChangesProcessor;
import org.flowerplatform.emf_model.notation.NotationElement;
import org.flowerplatform.emf_model.notation.NotationPackage;
import org.flowerplatform.emf_model.notation.View;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Cristi
*/
public class MainChangesDispatcher {
private static final Logger logger = LoggerFactory.getLogger(MainChangesDispatcher.class);
protected List<IChangesProcessor> processors = new ArrayList<IChangesProcessor>();
public MainChangesDispatcher addProcessor(IChangesProcessor processor) {
processors.add(processor);
return this;
}
private Changes getOrCreateDataForProcessors(Map<EObject, Changes> map, EObject key) {
Changes dataForProcessors = map.get(key);
if (dataForProcessors == null) {
dataForProcessors = new Changes();
map.put(key, dataForProcessors);
}
return dataForProcessors;
}
protected void findAddedToRemovedFrom(Map<EObject, Changes> changesInPreparation, Map.Entry<EObject, EList<FeatureChange>> entry) {
EObject object = entry.getKey();
for (FeatureChange featureChange : entry.getValue()) {
if (featureChange.getFeature() instanceof EReference) {
if (!featureChange.getFeature().isMany()) {
// TODO CS/CS3 LOW de prelucrat si cazul in care avem containment pe feature care nu e many
// throw new UnsupportedOperationException("Reference feature that is not many is not yet supported; feature = " + featureChange.getFeatureName());
} else {
// many
if (featureChange.getListChanges().isEmpty()) {
// special case: the list was empty before adding => we don't have listChanges; this happens because
// it's not actually an ADD operation; it's a SET operation, from an empty list. I think the same scenario
// happens if the user does a SET instead of an ADD (on a non-empty list)
@SuppressWarnings("unchecked")
List<EObject> oldList = (List<EObject>) featureChange.getValue();
@SuppressWarnings("unchecked")
List<EObject> currentList = (List<EObject>) object.eGet(featureChange.getFeature());
for (EObject mostProbablyNewlyAddedElement : currentList) {
if (oldList == null || !oldList.contains(mostProbablyNewlyAddedElement)) {
// the .contains(...) is for the case where the current object was both in the old + new list;
// I named the variable "probably..." because this case is unlikely to exist, but not impossible
getOrCreateDataForProcessors(changesInPreparation, mostProbablyNewlyAddedElement).addAndLogAddedTo(mostProbablyNewlyAddedElement, object, featureChange.getFeature());
// TODO CS/CS3 LOW De testat pentru cazul setList(null), sau setList(ceva): cred ca tot aici picam, si tr sa raportam niste obiecte suplimentare ca sterse;
// de asemenea de testat si ca cazul de mai sus, pt. add in care avem o lista noua care contine si elemente care erau in lista veche; eu l-am copiat, insa
// nu l-am vazut cu ochii mei.
}
}
} else {
// normal case: the list was not empty before adding
int indexCorrectionBecauseOfPreviousItems = 0;
for (ListChange listChange : featureChange.getListChanges()) {
if (listChange.getKind() == ChangeKind.REMOVE_LITERAL) {
// an element was added; the recording indicates the index where we
// should delete, in order to remove it. This happens because the changes are in a reverse
// delta form. E.g. if we have a list with 2 elements (a, b), and we add 2 elements at the end
// (c, d), we would have c = 2, d = 2; meaning remove twice at 2, and you'll get the initial list.
//
// That's why we need a correction, that we increment each time. It seems that the list is always sorted
// according to the index, and if there are additions AND removals, the additions are processed first. So
// the index correction doesn't need to happen there.
@SuppressWarnings("unchecked")
EObject newlyAddedObject = ((List<EObject>) object.eGet(featureChange.getFeature())).get(listChange.getIndex() + indexCorrectionBecauseOfPreviousItems);
getOrCreateDataForProcessors(changesInPreparation, newlyAddedObject).addAndLogAddedTo(newlyAddedObject, object, featureChange.getFeature());
indexCorrectionBecauseOfPreviousItems++;
} else if (listChange.getKind() == ChangeKind.ADD_LITERAL) {
// an element was removed
for (EObject removedObject : listChange.getReferenceValues()) {
getOrCreateDataForProcessors(changesInPreparation, removedObject).addAndLogRemovedFrom(removedObject, object, featureChange.getFeature());
}
}
}
}
}
}
}
}
public void processChangeDescription(Map<String, Object> context, ChangeDescription changeDescription) {
// We have 2 passes and not just one because we want to only one notification for a given
// object. E.g. if it has featureChanges, addedTo, removedFrom, we want to send them together. For
// this purpose we buffer the data into this variable.
//
// If we didn't have 2 passes, the objects would receive multiple invocations, because of the findAddedToRemovedFrom()
// algorithm
Map<EObject, Changes> changesInPreparation = new HashMap<EObject, Changes>();
for (Map.Entry<EObject, EList<FeatureChange>> entry : changeDescription.getObjectChanges().entrySet()) {
if (logger.isTraceEnabled()) {
logger.trace("Pass1: processing change description for object = {}, feature changes = {}", entry.getKey(), Changes.printFeatureChanges(entry.getValue()));
}
Changes dataForProcessors = getOrCreateDataForProcessors(changesInPreparation, entry.getKey());
dataForProcessors.setFeatureChanges(entry.getValue());
findAddedToRemovedFrom(changesInPreparation, entry);
}
for (Map.Entry<EObject, Changes> entry : changesInPreparation.entrySet()) {
if (logger.isDebugEnabled()) {
logger.debug("Pass2: dispatching changes for object = {}, changes = {}", entry.getKey(), entry.getValue());
}
for (IChangesProcessor processor : processors) {
processor.processChanges(context, entry.getKey(), entry.getValue());
}
}
}
}