Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 67b5f24364bb6064a8d220dbea3085580d043542 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
/*****************************************************************************
 * 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);
		}
	}

}

Back to the top