/*****************************************************************************
* Copyright (c) 2010 CEA LIST.
*
* 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:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
*****************************************************************************/
package org.eclipse.papyrus.infra.emf.databinding;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.databinding.observable.list.ObservableList;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.DeleteCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.papyrus.infra.widgets.editors.AbstractEditor;
import org.eclipse.papyrus.infra.widgets.editors.ICommitListener;
/**
* An ObservableList using EMF Commands to edit the underlying list.
* The commands are executed when the {@link #commit(AbstractEditor)} method is called.
* However, the read operations (such as get, size, ...) return up-to-date
* results, even when {@link #commit(AbstractEditor)} hasn't been called.
*
* @author Camille Letavernier
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class EMFObservableList extends ObservableList implements ICommitListener {
/**
* The list of commands that haven't been executed yet
*/
protected List<Command> commands = new LinkedList<Command>();
/**
* The editing domain on which the commands will be executed
*/
protected EditingDomain editingDomain;
/**
* The edited EObject
*/
protected EObject source;
/**
* The feature being edited
*/
protected EStructuralFeature feature;
/**
* The list to be updated only on #commit() calls
*/
protected List<?> concreteList;
/**
*
* Constructor.
*
* @param wrappedList
* The list to be edited when #commit() is called
* @param domain
* The editing domain on which the commands will be executed
* @param source
* The EObject from which the list will be retrieved
* @param feature
* The feature from which the list will be retrieved
*/
public EMFObservableList(List<?> wrappedList, EditingDomain domain, EObject source, EStructuralFeature feature) {
super(new LinkedList<Object>(wrappedList), Object.class);
this.concreteList = wrappedList;
this.editingDomain = domain;
this.source = source;
this.feature = feature;
}
/**
* Forces this list to commit all the pending commands. Only one composite command will
* be executed, and can be undone in a single operation.
*
* @see org.eclipse.papyrus.infra.widgets.editors.ICommitListener#commit(AbstractEditor)
*
*/
public void commit(AbstractEditor editor) {
if(commands.isEmpty()) {
return;
}
CompoundCommand compoundCommand = new CompoundCommand() {
@Override
public void execute() {
super.execute();
refreshCacheList();
}
@Override
public void undo() {
super.undo();
refreshCacheList();
}
@Override
public void redo() {
super.redo();
refreshCacheList();
}
@Override
protected boolean prepare() {
if(commandList.isEmpty()) {
return false;
} else {
//We only test the first command, as the following ones might depend
//on the first command's execution. StrictCompoundCommands don't seem
//to be compatible with emf transaction (execute() is called by
//canExecute(), before the transaction is started)
return commandList.get(0).canExecute();
}
}
};
for(Command cmd : commands) {
compoundCommand.append(cmd);
}
editingDomain.getCommandStack().execute(compoundCommand);
refreshCacheList();
commands.clear();
}
/**
* Refresh the cached list by copying the real list
*/
protected void refreshCacheList() {
if(isDisposed()) {
//This observable can be disposed, but the commands might still be
//in the command stack. Undo() or Redo() will call this method, which
//should be ignored. The command should probably not call refresh directly ;
//we should have listeners on the concrete list... but it is not necessarily
//observable
return;
}
wrappedList.clear();
wrappedList.addAll(concreteList);
fireListChange(null);
}
@Override
public void add(int index, Object value) {
Command command = getAddCommand(index, value);
commands.add(command);
wrappedList.add(index, value);
fireListChange(null);
}
@Override
public void clear() {
Command command = getClearCommand();
commands.add(command);
wrappedList.clear();
fireListChange(null);
}
@Override
public boolean add(Object o) {
Command command = getAddCommand(o);
commands.add(command);
boolean result = wrappedList.add(o);
fireListChange(null);
return result;
}
@Override
public boolean remove(Object o) {
Command command = getRemoveCommand(o);
commands.add(command);
boolean result = wrappedList.remove(o);
fireListChange(null);
return result;
}
@Override
public boolean addAll(Collection c) {
Command command = getAddAllCommand(c);
commands.add(command);
boolean result = wrappedList.addAll(c);
fireListChange(null);
return result;
}
@Override
public boolean addAll(int index, Collection c) {
Command command = getAddAllCommand(index, c);
commands.add(command);
boolean result = wrappedList.addAll(index, c);
fireListChange(null);
return result;
}
@Override
public boolean removeAll(Collection c) {
Command command = getRemoveCommand(c);
commands.add(command);
boolean result = wrappedList.removeAll(c);
fireListChange(null);
return result;
}
@Override
public boolean retainAll(Collection c) {
Command command = getRetainAllCommand(c);
commands.add(command);
boolean result = wrappedList.retainAll(c);
fireListChange(null);
return result;
}
@Override
public Object set(int index, Object element) {
Command command = getSetCommand(index, element);
commands.add(command);
Object result = wrappedList.set(index, element);
fireListChange(null);
return result;
}
@Override
public Object move(int oldIndex, int newIndex) {
commands.addAll(getMoveCommands(oldIndex, newIndex));
Object value = get(oldIndex);
wrappedList.remove(oldIndex);
wrappedList.add(newIndex, value);
fireListChange(null);
return value;
}
@Override
public Object remove(int index) {
Object value = get(index);
if(value != null) {
Command command = getRemoveCommand(index);
commands.add(command);
}
Object result = wrappedList.remove(index);
fireListChange(null);
return result;
}
public Command getAddCommand(int index, Object value) {
return AddCommand.create(editingDomain, source, feature, value, index);
}
public Command getAddCommand(Object value) {
return AddCommand.create(editingDomain, source, feature, value);
}
public Command getAddAllCommand(Collection<?> values) {
return AddCommand.create(editingDomain, source, feature, values);
}
public Command getAddAllCommand(int index, Collection<?> values) {
return AddCommand.create(editingDomain, source, feature, values, index);
}
public Command getClearCommand() {
return getRemoveAllCommand(new LinkedList<Object>(wrappedList));
}
public Command getRemoveCommand(int index) {
Object value = get(index);
return getRemoveCommand(value);
}
public Command getRemoveCommand(Object value) {
Command cmd = RemoveCommand.create(editingDomain, source, feature, value);
if (value instanceof EObject && feature instanceof EReference && ((EReference)feature).isContainment()) {
addDestroyCommand(cmd, (EObject)value);
}
return cmd;
}
public Command getRemoveAllCommand(Collection<?> values) {
CompoundCommand cc = new CompoundCommand("Edit list");
if (feature instanceof EReference && ((EReference)feature).isContainment() && values != null) {
for (Object o : values) {
if (o instanceof EObject) {
addDestroyCommand(cc, (EObject)o);
}
}
}
cc.append(RemoveCommand.create(editingDomain, source, feature, values));
return cc;
}
public List<Command> getMoveCommands(int oldIndex, int newIndex) {
Object value = get(oldIndex);
List<Command> commands = new LinkedList<Command>();
commands.add(getRemoveCommand(value));
commands.add(getAddCommand(newIndex, value));
return commands;
}
public Command getRetainAllCommand(Collection<?> values) {
List<Object> objectsToRemove = new LinkedList<Object>();
for(Object object : values) {
if(!contains(object)) {
objectsToRemove.add(object);
}
}
if(!objectsToRemove.isEmpty()) {
return getRemoveAllCommand(objectsToRemove);
} else {
return null;
}
}
public Command getSetCommand(int index, Object value) {
Object oldValue = get(index);
Command command = SetCommand.create(editingDomain, source, feature, value, index);
if (oldValue instanceof EObject && feature instanceof EReference && ((EReference)feature).isContainment()) {
addDestroyCommand(command, (EObject)oldValue);
}
return command;
}
protected void addDestroyCommand(Command cmd, EObject objToDestroy) {
Command destroyCmd = DeleteCommand.create(editingDomain, objToDestroy);
if (cmd instanceof CompoundCommand) {
((CompoundCommand)cmd).append(destroyCmd);
} else {
cmd.chain(destroyCmd);
}
}
}