/*******************************************************************************
* Copyright (c) 2006-2011
* Software Technology Group, Dresden University of Technology
*
* 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:
* Software Technology Group - TU Dresden, Germany
* - initial API and implementation
******************************************************************************/
package org.reuseware.coconut.roundtrip;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.EcoreUtil.Copier;
/**
* FIXME: This code is copied from an old version of the CopiedFromAdapter in the org.reuseware.coconut.fragment
* All round-trip functionality should go somewhere separate...
*
* TODO mseifert: remove all the code that confirms or discards modifications.
* these actions must be performed separately.
*/
public class RoundTripAdapter extends AdapterImpl {
// Original fragment data
protected EObject original;
protected String originalURIFragment;
protected final EObject contextObject;
public boolean active = false;
private Collection<IModificationHandler> modificationHandlers;
private final Resource eResource;
// Data for change operations
private EList<EObject> copyValues;
private int idx;
private int type;
private EObject copy;
private EStructuralFeature feature;
// Temporary data
private Object oldChangedValue;
private EList<EObject> tmp = new BasicEList<EObject>();
private Object newValue;
private Object oldValue;
/**
* Standard constructor
*
* This constructor is used when adding adapters to each element of a syntax tree
*
* @param original The original element in source fragment
* @param contextObject The source fragment
*/
public RoundTripAdapter(EObject original, EObject contextObject) {
super();
this.original = original;
this.contextObject = contextObject;
this.eResource = original.eResource();
if (eResource != null) {
this.originalURIFragment = eResource.getURIFragment(original);
}
}
/**
* Constructor used when adding a new element during model editing. This constructor is
* needed because eResource is null when new elements are added.
*
* @param original The original element in source fragment
* @param contextObject The source fragment
* @param originalUFI The UFI of the original element
* @param eResource The eResource of original fragment
*/
public RoundTripAdapter(EObject original, EObject contextObject,
Resource eResource) {
super();
this.original = original;
this.originalURIFragment = eResource.getURIFragment(original);
this.contextObject = contextObject;
this.eResource = eResource;
}
/**
* Adds new elements to the source fragment. This method is called if all
* conflicts are resolved and adding should be propagated.
*/
public void confirmAddElements() {
//System.out.println(hashCode() + ": confirmAddElements() originalValues = " + originalValues);
EObject newValue = EcoreUtil.copy(copyValues.get(idx));
//System.out.println(hashCode() + ": confirmAddElements() newValue = " + newValue);
RoundTripAdapter newAdapter = new RoundTripAdapter(newValue,
this.contextObject, this.eResource);
//System.out.println(hashCode() + ": confirmAddElements() feature = " + feature);
//System.out.println(hashCode() + ": confirmAddElements() feature = " + feature.getEContainingClass());
EObject originalContainer = original.eContainer();
//System.out.println(hashCode() + ": confirmAddElements() original = " + original);
Object list = original.eGet(original.eClass().getEStructuralFeature(feature.getName()));
EList<EObject> originalList = castUnchecked(list);
//System.out.println(hashCode() + ": confirmAddElements() listInOriginal = " + originalList);
if (originalList != null && copyValues != null) {
//newAdapter.openerUFI = this.openerUFI;
copyValues.get(idx).eAdapters().add(newAdapter);
if (idx < originalList.size() - 1) {
originalList.add(idx, newValue);
} else {
originalList.add(newValue);
}
}
saveOriginalFragment();
newAdapter.active = true;
}
@SuppressWarnings("unchecked")
private EList<EObject> castUnchecked(Object listOfEObjects) {
return (EList<EObject>) listOfEObjects;
}
/**
* Method for adding new elements temporary to the source fragment. This is used when
* a compare view is being opened.
*/
public void addTemporary() {
Copier copier = new Copier();
Collection<EObject> tmpCopied = copier.copyAll(getOriginalValues());
Iterator<EObject> contents = tmpCopied.iterator();
tmp = new BasicEList<EObject>();
while (contents.hasNext()) {
tmp.add(contents.next());
}
confirmAddElements();
}
/**
* Adds an handler that listens for modifications.
*
* @param newHandler an IModificationHandler that listens to changes
*/
public void addModificationHandler(IModificationHandler newHandler) {
Collection<IModificationHandler> handlers = getModificationHandlers();
handlers.add(newHandler);
}
/**
* Method for changing an element temporary to the source fragment. This is used when
* a compare view is being opened.
*/
public void changeTemporary() {
active = false;
oldChangedValue = original.eGet(feature);
original.eSet(feature, newValue);
saveOriginalFragment();
active = true;
}
/**
*
* Changes an element in the source fragment. This method is called if all
* conflicts are resolved and changes should be propagated.
*
*/
public void confirmChangeAttribute() {
//TODO because original might be reloaded... we need to handle this in some uniform way
EObject altOriginal = eResource.getEObject(originalURIFragment);
if (altOriginal != null) {
original = altOriginal;
}
if (feature instanceof EAttribute && newValue != null) {
original.eSet(feature, newValue);
} else {
//TODO #689: this check is necessary, because sometimes the copy is not the copy but the container of the copy (that should not happen)
if (copy.eClass().getEAllStructuralFeatures().contains(feature)) {
return;
}
else {
original.eSet(feature, copy);
}
}
saveOriginalFragment();
}
/**
* Method for removing the new added element from the edited model.
* This is called if conflicts arose from adding an element.
*/
public void discardAdd() {
// TODO #689: also remove elements from diagram, not only from model (e.g. TopCased)
active = false;
copy.eSet(feature, getOriginalValues());
active = true;
}
/**
* Method for undoing the changes at an element in the edited model.
* This is called if conflicts arose from changing an element.
*/
public void discardChange() {
active = false;
copy.eSet(feature, oldValue);
active = true;
}
/**
* Method for undoing removal of an element from the edited model.
* This is called if conflicts arose from removing an element.
*/
public void discardRemove() {
// TODO #689: also undo removal from diagram, not only from model (e.g. TopCased)
active = false;
copy.eSet(feature, getOriginalValues());
active = true;
}
/**
* Searches for extensions that implement the IConflictResolver interface.
*/
private Collection<IModificationHandler> getRegisteredModificationHandlers() {
Collection<IModificationHandler> handlers = new ArrayList<IModificationHandler>();
IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
if (extensionRegistry == null) {
return handlers;
}
IExtensionPoint extensionPoint = extensionRegistry
.getExtensionPoint("org.reuseware.coconut.fragment.modificationHandler");
IExtension[] extensions = extensionPoint.getExtensions();
for (int i = 0; i < extensions.length; i++) {
IConfigurationElement[] elements = extensions[i].getConfigurationElements();
for (int j = 0; j < elements.length; j++) {
try {
handlers.add((IModificationHandler) elements[j].createExecutableExtension("class"));
} catch (CoreException ce) {
ce.printStackTrace();
} catch (ClassCastException cc) {
cc.printStackTrace();
}
}
}
if (handlers.size() > 1) {
System.err.println("CopiedFromAdapter.getRegisteredModificationHandlers() found multiple registered modification handlers (" + handlers + "). This is most probably a configuration issue.");
}
return handlers;
}
private Collection<IModificationHandler> getModificationHandlers() {
if (modificationHandlers == null) {
modificationHandlers = new LinkedHashSet<IModificationHandler>();
modificationHandlers.addAll(getRegisteredModificationHandlers());
}
return modificationHandlers;
}
public EObject getContextObject() {
return contextObject;
}
public EList<EObject> getCopyValues() {
return copyValues;
}
public EStructuralFeature getFeature() {
return feature;
}
public int getIdx() {
return idx;
}
public EObject getOriginal() {
return original;
}
public URI getOriginalUFI() {
URI originalUFI = URI.createURI("unknown:/UNKNOWN_SOURCE");
Resource resource = original.eResource();
if (resource != null) {
if (resource.getURI() != null) {
originalUFI = resource.getURI();
}
}
return originalUFI;
}
@Override
public boolean isAdapterForType(Object type) {
return type == RoundTripAdapter.class;
}
@Override
@SuppressWarnings("unchecked")
/**
* Method that is called when the adapted element is changed.
*
* @param notification The notification holding change type and additional informations
*/
public void notifyChanged(Notification notification) {
feature = (EStructuralFeature) notification.getFeature();
copy = (EObject) notification.getNotifier();
newValue = notification.getNewValue();
oldValue = notification.getOldValue();
if (!active) {
return;
}
type = notification.getEventType();
// non-list feature
//if (feature.getUpperBound() == 1) {
if (type == Notification.SET) {
notifyChange();
} else if (type == Notification.ADD || type == Notification.REMOVE ||
type == Notification.ADD_MANY || type == Notification.REMOVE_MANY) {
idx = notification.getPosition();
copyValues = (EList<EObject>) copy.eGet(feature);
// Remove elements
if (type == Notification.REMOVE) {
notifyRemove();
}
// Add elements
if (type == Notification.ADD) {
//System.out.println(hashCode() + ": notifyChanged() originalValues = " + originalValues);
notifyAdd();
}
}
}
private EList<EObject> getOriginalValues() {
return castUnchecked(original.eGet(feature));
}
/**
* Notifies all registered listeners about the addition
* of an element.
*/
private void notifyAdd() {
notifyAboutModification(EModificationType.ADD);
}
private void notifyAboutModification(EModificationType modificationType) {
for (IModificationHandler handler : getModificationHandlers()) {
URI uri = null;
//in case of remove copy.eResource() == null
if (copy.eResource() != null) {
uri = copy.eResource().getURI();
}
handler.handle(modificationType, uri, this);
}
}
/**
* Notifies the registered handlers about an attribute change
* made to the element this adapter is attached to.
*/
private void notifyChange() {
notifyAboutModification(EModificationType.CHANGE);
}
/**
* Notifies the registered handlers about the removal of
* an object from the element this adapter is attached to.
*/
private void notifyRemove() {
notifyAboutModification(EModificationType.REMOVE);
}
/**
* Removes the element from source fragment
*/
public void confirmRemoveElementsFromFragment() {
active = false;
getOriginalValues().remove(idx);
saveOriginalFragment();
active = true;
}
/**
* Method for removing elements temporary from the source fragment. This is used when
* a compare view is being opened.
*/
public void removeTemporary() {
active = false;
tmp = new BasicEList<EObject>(EcoreUtil.copyAll(getOriginalValues()));
getOriginalValues().remove(idx);
saveOriginalFragment();
}
/**
* Saves the source fragment after changes are propagated.
*/
public void saveOriginalFragment() {
if (this.eResource != null) {
//System.out.println("saveOriginalFragment() " + this.eResource.getURI());
try {
this.eResource.save(Collections.EMPTY_MAP);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void setCopyValues(EList<EObject> copyValues) {
this.copyValues = copyValues;
}
public void setFeature(EStructuralFeature feature) {
this.feature = feature;
}
public void setIdx(int idx) {
this.idx = idx;
}
/**
* Undo the propagation of changing an element.
*/
public void unchange() {
active = false;
original.eSet(feature, oldChangedValue);
saveOriginalFragment();
active = true;
}
/**
* Undo the propagation of changing several elements.
*/
public void undoChanges() {
getOriginalValues().clear();
getOriginalValues().addAll(tmp);
tmp = null;
active = true;
saveOriginalFragment();
}
public void setOriginal(EObject original) {
this.original = original;
}
public Object getNewValue() {
return newValue;
}
public Object getOldValue() {
return oldValue;
}
public String getOriginalURIFragment() {
return originalURIFragment;
}
public Resource getResource() {
return eResource;
}
}