KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > emf > edit > command > RemoveCommand


1 /**
2  * <copyright>
3  *
4  * Copyright (c) 2002-2004 IBM Corporation and others.
5  * All rights reserved. This program and the accompanying materials
6  * are made available under the terms of the Eclipse Public License v1.0
7  * which accompanies this distribution, and is available at
8  * http://www.eclipse.org/legal/epl-v10.html
9  *
10  * Contributors:
11  * IBM - Initial API and implementation
12  *
13  * </copyright>
14  *
15  * $Id: RemoveCommand.java,v 1.5 2005/06/08 06:17:05 nickb Exp $
16  */

17 package org.eclipse.emf.edit.command;
18
19
20 import java.util.ArrayList JavaDoc;
21 import java.util.Collection JavaDoc;
22 import java.util.Collections JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.ListIterator JavaDoc;
26
27 import org.eclipse.emf.common.command.Command;
28 import org.eclipse.emf.common.util.EList;
29 import org.eclipse.emf.ecore.EObject;
30 import org.eclipse.emf.ecore.EStructuralFeature;
31 import org.eclipse.emf.edit.EMFEditPlugin;
32 import org.eclipse.emf.edit.domain.EditingDomain;
33
34
35 /**
36  * The remove command logically acts upon an owner object that has a collection-type feature from which objects can be removed.
37  * The static create methods delegate command creation to {@link EditingDomain#createCommand EditingDomain.createCommand},
38  * which may or may not result in the actual creation of an instance of this class.
39  *
40  * <p>
41  * The implementation of this class is low-level and EMF specific;
42  * it allows one or more objects to be removed from a many-valued feature of an owner.
43  * i.e., it is almost equivalent of the call
44  * <pre>
45  * ((EList)((EObject)owner).eGet((EStructuralFeature)feature)).removeAll((Collection)collection);
46  * </pre>
47  *
48  * <p>
49  * It can also be used as a near-equivalent to the call
50  * <pre>
51  * ((EList)extent).removeAll((Collection)collection);
52  * </pre>
53  * which is how root objects are removed from the contents of a resource.
54  *
55  * <p>
56  * The one difference is that, while <code>EList.removeAll(Collection)</code> removes all values equal to a value in
57  * the collection, this command will remove no more than one value per value in the collection. When duplicates are
58  * allowed and present in the list, this command will first look for identical (<code>==</code>) values, in order, and
59  * failing that, equal values (<code>.equals()</code>).
60  *
61  * <p>
62  * Like all the low-level comands in this package, the remove command is undoable.
63  *
64  * <p>
65  * A remove command is an {@link OverrideableCommand}.
66  */

67 public class RemoveCommand extends AbstractOverrideableCommand
68 {
69   /**
70    * This creates a command to remove an object.
71    */

72   public static Command create(EditingDomain domain, Object JavaDoc value)
73   {
74     return create(domain, Collections.singleton(value));
75   }
76
77   /**
78    * This creates a command to remove a particular value from the specified feature of the owner.
79    */

80   public static Command create(EditingDomain domain, Object JavaDoc owner, Object JavaDoc feature, Object JavaDoc value)
81   {
82     return create(domain, owner, feature, Collections.singleton(value));
83   }
84
85   /**
86    * This creates a command to remove multiple objects.
87    */

88   public static Command create(final EditingDomain domain, final Collection JavaDoc collection)
89   {
90     return create(domain, null, null, collection);
91   }
92
93   /**
94    * This creates a command to remove a collection of values from the specified feature of the owner.
95    */

96   public static Command create(final EditingDomain domain, final Object JavaDoc owner, final Object JavaDoc feature, final Collection JavaDoc collection)
97   {
98     return domain.createCommand(RemoveCommand.class, new CommandParameter(owner, feature, collection));
99   }
100
101   /**
102    * This caches the label.
103    */

104   protected static final String JavaDoc LABEL = EMFEditPlugin.INSTANCE.getString("_UI_RemoveCommand_label");
105
106   /**
107    * This caches the description.
108    */

109   protected static final String JavaDoc DESCRIPTION = EMFEditPlugin.INSTANCE.getString("_UI_RemoveCommand_description");
110
111   /**
112    * This caches the description for a list-based command.
113    */

114   protected static final String JavaDoc DESCRIPTION_FOR_LIST = EMFEditPlugin.INSTANCE.getString("_UI_RemoveCommand_description_for_list");
115
116   /**
117    * This is the owner object upon which the command will act.
118    * It could be null, in the case that we are dealing with an {@link org.eclipse.emf.common.util.EList}.
119    */

120   protected EObject owner;
121
122   /**
123    * This is the feature of the owner object upon the command will act.
124    * It could be null, in the case that we are dealing with an {@link org.eclipse.emf.common.util.EList}.
125    */

126   protected EStructuralFeature feature;
127
128   /**
129    * This is the list from which the command will remove.
130    */

131   protected EList ownerList;
132
133   /**
134    * This is the collection of objects being removed.
135    */

136   protected Collection JavaDoc collection;
137
138   /**
139    * These are the indices at which to reinsert the removed objects during an undo so as to achieve the original list order.
140    */

141   protected int[] indices;
142
143   /**
144    * The is the value returned by {@link Command#getAffectedObjects}.
145    * The affected objects are different after an execute than after an undo, so we record it.
146    */

147   protected Collection JavaDoc affectedObjects;
148
149   /**
150    * This constructs a primitive command to remove a particular value from the specified feature of the owner.
151    */

152   public RemoveCommand(EditingDomain domain, EObject owner, EStructuralFeature feature, Object JavaDoc value)
153   {
154     this(domain, owner, feature, Collections.singleton(value));
155   }
156
157   /**
158    * This constructs a primitive command to remove a collection of values from the specified feature of the owner.
159    */

160   public RemoveCommand(EditingDomain domain, EObject owner, EStructuralFeature feature, Collection JavaDoc collection)
161   {
162     super(domain, LABEL, DESCRIPTION);
163
164     // Initialize all the fields from the command parameter.
165
//
166
this.owner = owner;
167     this.feature = feature;
168     this.collection = collection == null ? null : new ArrayList JavaDoc(collection);
169
170     ownerList = getOwnerList(this.owner, feature);
171   }
172
173   /**
174    * This constructs a primitive command to remove a particular value from the specified extent.
175    */

176   public RemoveCommand(EditingDomain domain, EList list, Object JavaDoc value)
177   {
178     this(domain, list, Collections.singleton(value));
179   }
180
181   /**
182    * This constructs a primitive command to remove a collection of values from the specified extent.
183    */

184   public RemoveCommand(EditingDomain domain, EList list, Collection JavaDoc collection)
185   {
186     super(domain, LABEL, DESCRIPTION_FOR_LIST);
187
188     // Initialize all the fields from the command parameter.
189
//
190
this.collection = collection == null ? null : new ArrayList JavaDoc(collection);
191
192     ownerList = list;
193   }
194
195   /**
196    * This returns the owner object upon which the command will act.
197    * It could be null, in the case that we are dealing with an {@link org.eclipse.emf.common.util.EList}.
198    */

199   public EObject getOwner()
200   {
201     return owner;
202   }
203
204   /**
205    * This returns the feature of the owner object upon the command will act.
206    * It could be null, in the case that we are dealing with an {@link org.eclipse.emf.common.util.EList}.
207    */

208   public EStructuralFeature getFeature()
209   {
210     return feature;
211   }
212
213   /**
214    * This returns the list from which the command will remove.
215    */

216   public EList getOwnerList()
217   {
218     return ownerList;
219   }
220
221   /**
222    * This returns the collection of objects being removed.
223    */

224   public Collection JavaDoc getCollection()
225   {
226     return collection;
227   }
228
229   /**
230    * These returns the indices at which to reinsert the removed objects during an undo so as to achieve the original list order.
231    */

232   public int[] getIndices()
233   {
234     return indices;
235   }
236
237   protected boolean prepare()
238   {
239     // This can execute if there is an owner list and a collection and the owner list contains all the objects of the collection.
240
//
241
boolean result =
242       ownerList != null &&
243         collection != null &&
244         ownerList.containsAll(collection) &&
245         (owner == null || !domain.isReadOnly(owner.eResource()));
246
247     return result;
248   }
249
250   public void doExecute()
251   {
252     // Iterate over the owner list twice, first matching objects from the collection by identity (==), then matching
253
// objects by value equality (.equals()). The positions of matched objects in the owner list are recorded, and
254
// the objects are stored in the same order. The lists are then merged to form a final, in-order list of objects
255
// and corresponding indices in ownerList. This is very important for undo to interpret the indices correctly.
256
// Also, this yields exactly one object removed for each object in the collection, with preference given to
257
// identity over value equality.
258
//
259
List JavaDoc identity = new ArrayList JavaDoc(collection.size());
260     int[] identityIndices = new int[collection.size()];
261
262     int i = 0;
263
264     for (ListIterator JavaDoc ownedObjects = ownerList.listIterator(); ownedObjects.hasNext(); )
265     {
266       Object JavaDoc ownedObject = ownedObjects.next();
267
268       // If this owned object is one from the collection...
269
//
270
if (containsExact(collection, ownedObject))
271       {
272         // Remove the object from the collection and add it to the identity list.
273
//
274
removeExact(collection, ownedObject);
275         identity.add(ownedObject);
276
277         // Record the index.
278
//
279
identityIndices[i++] = ownedObjects.previousIndex();
280       }
281     }
282
283     // Second pass: match by value equality.
284
//
285
List JavaDoc equality = new ArrayList JavaDoc(collection.size());
286     int[] equalityIndices = new int[collection.size()];
287     i = 0;
288
289     for (ListIterator JavaDoc ownedObjects = ownerList.listIterator(); ownedObjects.hasNext(); )
290     {
291       Object JavaDoc ownedObject = ownedObjects.next();
292       int index = ownedObjects.previousIndex();
293
294       // If this owned object is equal to one from the collection...
295
//
296
if (collection.contains(ownedObject) && !contains(identityIndices, index))
297       {
298         // Remove the object from the collection and add it to the equality list.
299
//
300
collection.remove(ownedObject);
301         equality.add(ownedObject);
302
303         // Record the index.
304
//
305
equalityIndices[i++] = index;
306       }
307     }
308
309     // Merge the lists.
310
//
311
merge(identity, identityIndices, equality, equalityIndices);
312
313     // Remove objects from the owner list by index, starting from the end.
314
//
315
for (i = indices.length - 1; i >= 0; i--)
316     {
317       ownerList.remove(indices[i]);
318     }
319
320     // We'd like the owner selected after this remove completes.
321
//
322
affectedObjects = owner == null ? Collections.EMPTY_SET : Collections.singleton(owner);
323   }
324
325   /**
326    * Returns whether the given collection contains the given target object itself (according to ==, not .equals()).
327    */

328   protected boolean containsExact(Collection JavaDoc collection, Object JavaDoc target)
329   {
330     for (Iterator JavaDoc i = collection.iterator(); i.hasNext(); )
331     {
332       if (i.next() == target) return true;
333     }
334     return false;
335   }
336
337   /**
338    * Returns whether the given int array contains the given target value.
339    */

340   protected boolean contains(int[] values, int target)
341   {
342     for (int i = 0, len = values.length; i < len; i++)
343     {
344       if (values[i] == target) return true;
345     }
346     return false;
347   }
348
349   /**
350    * Removes the first occurence of the given target object, itself, from the collection.
351    */

352   protected boolean removeExact(Collection JavaDoc collection, Object JavaDoc target)
353   {
354     for (Iterator JavaDoc i = collection.iterator(); i.hasNext(); )
355     {
356       if (i.next() == target)
357       {
358         i.remove();
359         return true;
360       }
361     }
362     return false;
363   }
364
365   /**
366    * Merges two sets of object lists and index arrays, such that both are ordered by increasing indices. The results
367    * are stored as the {@link #collection} and {@link #indices}. The two input sets must already be in increasing index
368    * order, with the corresponding object-index pairs in the same positions.
369    */

370   protected void merge(List JavaDoc objects1, int[] indices1, List JavaDoc objects2, int[] indices2)
371   {
372     // If either list is empty, the result is simply the other.
373
//
374
if (objects2.isEmpty())
375     {
376       collection = objects1;
377       indices = indices1;
378       return;
379     }
380
381     if (objects1.isEmpty())
382     {
383       collection = objects2;
384       indices = indices2;
385       return;
386     }
387
388     // Allocate list and array for objects and indices.
389
//
390
int size = objects1.size() + objects2.size();
391     collection = new ArrayList JavaDoc(size);
392     indices = new int[size];
393
394     // Index counters into indices1, indices2, and indices.
395
//
396
int i1 = 0;
397     int i2 = 0;
398     int i = 0;
399
400     // Object iterators.
401
//
402
Iterator JavaDoc iter1 = objects1.iterator();
403     Iterator JavaDoc iter2 = objects2.iterator();
404
405     Object JavaDoc o1 = iter1.hasNext() ? iter1.next() : null;
406     Object JavaDoc o2 = iter2.hasNext() ? iter2.next() : null;
407
408     // Repeatedly select the lower index and corresponding object, and advance past the selected pair.
409
//
410
while (o1 != null && o2 != null)
411     {
412       if (indices1[i1] < indices2[i2])
413       {
414         indices[i++] = indices1[i1++];
415         collection.add(o1);
416         o1 = iter1.hasNext() ? iter1.next() : null;
417       }
418       else
419       {
420         indices[i++] = indices2[i2++];
421         collection.add(o2);
422         o2 = iter2.hasNext() ? iter2.next() : null;
423       }
424     }
425
426     // Add any remaining object-index pairs from either set.
427
//
428
while (o1 != null)
429     {
430       indices[i++] = indices1[i1++];
431       collection.add(o1);
432       o1 = iter1.hasNext() ? iter1.next() : null;
433     }
434
435     while (o2 != null)
436     {
437       indices[i++] = indices2[i2++];
438       collection.add(o2);
439       o2 = iter2.hasNext() ? iter2.next() : null;
440     }
441   }
442
443   public void doUndo()
444   {
445     // Note that the way they are sorted, the values of index[i++] always increase,
446
// so the objects are added from right to left in the list.
447
//
448
// EATM TODO
449
//
450
// We could make this more efficient by grouping the adds when indices increment by one,
451
// so that a single grouped notification would result.
452
//
453
int i = 0;
454     for (Iterator JavaDoc objects = collection.iterator(); objects.hasNext(); )
455     {
456       ownerList.add(indices[i++], objects.next());
457     }
458   
459     // We'd like the collection of things added to be selected after this command completes.
460
//
461
affectedObjects = collection;
462   }
463   
464   public void doRedo()
465   {
466     // Remove objects from the owner list by index, starting from the end.
467
//
468
for (int i = indices.length - 1; i >= 0; i--)
469     {
470       ownerList.remove(indices[i]);
471     }
472
473     // We'd like the owner selected after this remove completes.
474
//
475
affectedObjects = owner == null ? (Collection JavaDoc)Collections.EMPTY_SET : Collections.singleton(owner);
476   }
477
478   public Collection JavaDoc doGetResult()
479   {
480     return collection;
481   }
482
483   public Collection JavaDoc doGetAffectedObjects()
484   {
485     return affectedObjects;
486   }
487
488   /**
489    * This gives an abbreviated name using this object's own class' name, without package qualification,
490    * followed by a space separated list of <tt>field:value</tt> pairs.
491    */

492   public String JavaDoc toString()
493   {
494     StringBuffer JavaDoc result = new StringBuffer JavaDoc(super.toString());
495     result.append(" (owner: " + owner + ")");
496     result.append(" (feature: " + feature + ")");
497     result.append(" (ownerList: " + ownerList + ")");
498     result.append(" (collection: " + collection + ")");
499     result.append(" (indices: " + indices + ")");
500     result.append(" (affectedObjects: " + affectedObjects + ")");
501
502     return result.toString();
503   }
504 }
505
Popular Tags