KickJava   Java API By Example, From Geeks To Geeks.

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


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: SetCommand.java,v 1.8 2005/06/08 06:17:05 nickb Exp $
16  */

17 package org.eclipse.emf.edit.command;
18
19
20 import java.util.Collection JavaDoc;
21 import java.util.Collections JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.ListIterator JavaDoc;
25
26 import org.eclipse.emf.common.command.Command;
27 import org.eclipse.emf.common.command.CommandWrapper;
28 import org.eclipse.emf.common.command.CompoundCommand;
29 import org.eclipse.emf.common.command.IdentityCommand;
30 import org.eclipse.emf.common.util.BasicEList;
31 import org.eclipse.emf.common.util.EList;
32 import org.eclipse.emf.ecore.EAttribute;
33 import org.eclipse.emf.ecore.EClass;
34 import org.eclipse.emf.ecore.EClassifier;
35 import org.eclipse.emf.ecore.EObject;
36 import org.eclipse.emf.ecore.EReference;
37 import org.eclipse.emf.ecore.EStructuralFeature;
38 import org.eclipse.emf.ecore.EcorePackage;
39 import org.eclipse.emf.edit.EMFEditPlugin;
40 import org.eclipse.emf.edit.domain.EditingDomain;
41
42
43 /**
44  * The set command logically acts upon an owner object to set a particular feature to a specified value.
45  * The static create methods delegate command creation to {@link EditingDomain#createCommand EditingDomain.createCommand},
46  * which may or may not result in the actual creation of an instance of this class.
47  *
48  * <p>
49  * The implementation of this class is low-level and EMF specific;
50  * it allows a value to be set to a single-valued feature of an owner,
51  * i.e., it is equivalent of the call
52  * <pre>
53  * ((EObject)object).eSet((EStructuralFeature)feature, value);
54  * </pre>
55  * or to
56  * <pre>
57  * ((EObject)object).eUnset((EStructuralFeature)feature);
58  * </pre>
59  * if the value is null and the feature is an attribute.
60  * <p>
61  * Setting a feature that is a bi-directional reference with a multiplicity-many reverse or with a
62  * multiplicity-1 reverse that is already set (on value), is not undoable.
63  * In this case, the SetCommand static create function will not return an instance of this class, but
64  * instead will return a compound command (e.g., a {@link RemoveCommand} followed by an {@link AddCommand}
65  * for the other end of the relation) which can be undone.
66  * <p>
67  * When setting a containment (or container) feature, we always assume that the object that will be
68  * contained is not already in a container, but take no action in this class to ensure this is the case.
69  * <p>
70  * A set command is an {@link OverrideableCommand}.
71  */

72 public class SetCommand extends AbstractOverrideableCommand
73 {
74   /**
75    * This creates a command to set the owner's feature to the specified value.
76    */

77   public static Command create(EditingDomain domain, final Object JavaDoc owner, Object JavaDoc feature, Object JavaDoc value)
78   {
79     return create(domain, owner, feature, value, CommandParameter.NO_INDEX);
80   }
81
82   /**
83    * This creates a command to set the owner's feature to the specified value at the specified index.
84    */

85   public static Command create(EditingDomain domain, final Object JavaDoc owner, Object JavaDoc feature, Object JavaDoc value, int index)
86   {
87     // If the feature is a bi-directional reference with a multiplicity-many reverse, a composite reverse,
88
// or a multiplicity-1 reverse that is already set (on value), then we'll switch things around and
89
// execute this command a little differently, because otherwise it's not undoable.
90
//
91
if (owner instanceof EObject && ((EObject)owner).eClass().getEAllReferences().contains(feature))
92     {
93       EReference eReference = (EReference)feature;
94       if (eReference.isMany() && index == CommandParameter.NO_INDEX)
95       {
96         // We never directly set a multiplicity-many feature to a list directly. Instead, we remove the old values
97
// values and insert the new values. If there are no old values or new values, we simply return an identity
98
// command, which is executable but does nothing.
99
//
100
List JavaDoc values = (List JavaDoc)value;
101         List JavaDoc oldValues = (List JavaDoc)((EObject)owner).eGet(eReference);
102
103         if (values.isEmpty() && oldValues.isEmpty())
104         {
105           return
106             new IdentityCommand(LABEL, DESCRIPTION, owner)
107             {
108               public Collection JavaDoc getAffectedObjects()
109               {
110                 return Collections.singleton(owner);
111               }
112             };
113         }
114         else
115         {
116           CompoundCommand compound =
117             new CompoundCommand(CompoundCommand.LAST_COMMAND_ALL, LABEL, DESCRIPTION)
118             {
119               public Collection JavaDoc getAffectedObjects()
120               {
121                 return Collections.singleton(owner);
122               }
123             };
124
125           if (!oldValues.isEmpty())
126           {
127             if (!values.isEmpty())
128             {
129               List JavaDoc removedValues = new BasicEList.FastCompare(oldValues);
130               removedValues.removeAll(values);
131               
132               // If we aren't simply removing all the old values...
133
//
134
if (!removedValues.equals(oldValues))
135               {
136                 // If there are values to remove, append a command for them.
137
//
138
if (!removedValues.isEmpty())
139                 {
140                   compound.append(RemoveCommand.create(domain, owner, feature, new BasicEList(removedValues)));
141                 }
142                 
143                 // Determine the values that will remain and move them into the right order, if necessary.
144
//
145
List JavaDoc remainingValues = new BasicEList.FastCompare(oldValues);
146                 remainingValues.removeAll(removedValues);
147                 int count = -1;
148                 for (Iterator JavaDoc i = values.iterator(); i.hasNext(); )
149                 {
150                   Object JavaDoc object = i.next();
151                   int position = remainingValues.indexOf(object);
152                   if (position != -1 && position != ++count)
153                   {
154                     compound.append(MoveCommand.create(domain, owner, feature, object, count));
155                   }
156                 }
157                 
158                 // Determine the values to be added and add them at the right position.
159
//
160
List JavaDoc addedValues = new BasicEList.FastCompare(values);
161                 addedValues.removeAll(remainingValues);
162                 for (ListIterator JavaDoc i = values.listIterator(); i.hasNext(); )
163                 {
164                   Object JavaDoc object = i.next();
165                   if (addedValues.contains(object))
166                   {
167                     compound.append(AddCommand.create(domain, owner, feature, object, i.previousIndex()));
168                   }
169                 }
170                 return compound;
171               }
172             }
173
174             compound.append(RemoveCommand.create(domain, owner, feature, new BasicEList(oldValues)));
175           }
176           if (!values.isEmpty())
177           {
178             compound.append(AddCommand.create(domain, owner, feature, values));
179           }
180           return compound;
181         }
182       }
183       else if (eReference.getEOpposite() != null)
184       {
185         EReference eOtherEnd = eReference.getEOpposite();
186         if (eOtherEnd.isMany())
187         {
188           if (eReference.isMany())
189           {
190             // For a many-to-many association, the command can only be undoable if the value or owner is last in its
191
// respective list, since the undo will include an inverse add. So, if the value is last, but the owner is
192
// not, we create an undoable compound command that removes from the opposite end and then inserts the new
193
// value.
194
//
195
EList list = (EList)((EObject)owner).eGet(eReference);
196             if (index == list.size() - 1)
197             {
198               EObject oldValue = (EObject)list.get(index);
199               EList oppositeList = (EList)oldValue.eGet(eOtherEnd);
200               if (oppositeList.get(oppositeList.size() - 1) != owner)
201               {
202                 CompoundCommand compound =
203                   new CompoundCommand(CompoundCommand.LAST_COMMAND_ALL, LABEL, DESCRIPTION)
204                   {
205                     public Collection JavaDoc getAffectedObjects()
206                     {
207                       return Collections.singleton(owner);
208                     }
209                   };
210                 compound.append(RemoveCommand.create(domain, oldValue, eOtherEnd, owner));
211                 compound.append(AddCommand.create(domain, owner, feature, value));
212                 return compound;
213               }
214             }
215           }
216           else
217           {
218             // For a 1-to-many association, doing the set as a remove and add from the other end will make it undoable.
219
// In particular, if there is an existing non-null value, we first need to remove it from the other end, so
220
// that it will be reinserted at the correct index on undo.
221
//
222
Object JavaDoc oldValue = ((EObject)owner).eGet(eReference);
223             if (value == null)
224             {
225               if (oldValue == null)
226               { // (value == null) && (oldValue == null)
227
// A simple set will suffice.
228
//
229
return domain.createCommand(SetCommand.class, new CommandParameter(owner, eReference, value));
230               }
231               else
232               { // (value == null) && (oldValue != null)
233
// Remove owner from the old value.
234
//
235
return RemoveCommand.create(domain, oldValue, eOtherEnd, Collections.singleton(owner));
236               }
237             }
238             else
239             { // ((value != null)
240
Command addCommand =
241                 new CommandWrapper(AddCommand.create(domain, value, eOtherEnd, Collections.singleton(owner)))
242                 {
243                   public Collection JavaDoc getAffectedObjects()
244                   {
245                     return Collections.singleton(owner);
246                   }
247                 };
248   
249               if (oldValue == null)
250               { // (value != null) && (oldValue == null)
251
// Add owner to new value.
252
//
253
return addCommand;
254               }
255               else
256               { // ((value != null) && (oldValue != null))
257
// Need a compound command to remove owner from old value and add it to new value.
258
//
259
CompoundCommand compound = new CompoundCommand(CompoundCommand.LAST_COMMAND_ALL, LABEL, DESCRIPTION);
260                 compound.append(RemoveCommand.create(domain, oldValue, eOtherEnd, Collections.singleton(owner)));
261                 compound.append(addCommand);
262                 return compound;
263               }
264             }
265           }
266         }
267         else if (eOtherEnd.isContainment())
268         {
269           if (value != null)
270           {
271             // For consistency, we always set 1-1 container relations from the container end.
272
//
273
return
274               new CommandWrapper(SetCommand.create(domain, value, eOtherEnd, owner))
275               {
276                 public Collection JavaDoc getResult()
277                 {
278                   return Collections.singleton(owner);
279                 }
280                 public Collection JavaDoc getAffectedObjects()
281                 {
282                   return Collections.singleton(owner);
283                 }
284               };
285           }
286         }
287         else
288         {
289           // For a many-to-1 or 1-to-1 association, if the opposite reference on the new value is already set to
290
// something, we need a compound command that first explicitly removes that reference, so that it will be
291
// restored in the undo.
292
//
293
if (value instanceof EObject && ((EObject)value).eGet(eOtherEnd) != null)
294           {
295             CompoundCommand compound =
296               new CompoundCommand(CompoundCommand.LAST_COMMAND_ALL)
297               {
298                 public boolean canUndo()
299                 {
300                   return true;
301                 }
302               };
303             if (eReference.isMany())
304             {
305               // For a many-to-1, we use SetCommand.create() to create the commmand to remove the opposite reference;
306
// a RemoveCommand on its opposite will actually result.
307
//
308
compound.append(SetCommand.create(domain, value, eOtherEnd, null));
309             }
310             else
311             {
312               // For a 1-to-1, we can directly create a SetCommand.
313
//
314
compound.append(domain.createCommand(SetCommand.class, new CommandParameter(value, eOtherEnd, null)));
315             }
316             compound.append(domain.createCommand(SetCommand.class, new CommandParameter(owner, eReference, value, index)));
317             return compound;
318           }
319         }
320       }
321     }
322     return domain.createCommand(SetCommand.class, new CommandParameter(owner, feature, value, index));
323   }
324
325   /**
326    * This caches the label.
327    */

328   protected static final String JavaDoc LABEL = EMFEditPlugin.INSTANCE.getString("_UI_SetCommand_label");
329
330   /**
331    * This caches the description.
332    */

333   protected static final String JavaDoc DESCRIPTION = EMFEditPlugin.INSTANCE.getString("_UI_SetCommand_description");
334
335   /**
336    * This is the owner object upon which the command will act.
337    */

338   protected EObject owner;
339
340   /**
341    * This is the feature of the owner object upon the command will act.
342    */

343   protected EStructuralFeature feature;
344
345   /**
346    * If non-null, this is the list in which the command will set a value.
347    * If null, feature is single-valued or no index was specified.
348    */

349   protected EList ownerList;
350
351   /**
352    * This is the value to be set.
353    */

354   protected Object JavaDoc value;
355
356   /**
357    * This is the old value of the feature which must be restored during undo.
358    */

359   protected Object JavaDoc oldValue;
360
361   /**
362    * This is the position at which the object will be set.
363    */

364   protected int index;
365
366   /**
367    * This specified whether or not this command can be undone.
368    */

369   protected boolean canUndo = true;
370
371   /**
372    * This constructs a primitive command to set the owner's feature to the specified value.
373    */

374   public SetCommand(EditingDomain domain, EObject owner, EStructuralFeature feature, Object JavaDoc value)
375   {
376     super(domain, LABEL, DESCRIPTION);
377
378     // Initialize all the fields from the command parameter.
379
//
380
this.owner = owner;
381     this.feature = feature;
382     this.value = value;
383     this.index = CommandParameter.NO_INDEX;
384   }
385
386   /**
387    * This constructs a primitive command to set the owner's feature to the specified value at the given index.
388    */

389   public SetCommand(EditingDomain domain, EObject owner, EStructuralFeature feature, Object JavaDoc value, int index)
390   {
391     super(domain, LABEL, DESCRIPTION);
392
393     // Initialize all the fields from the command parameter.
394
//
395
this.owner = owner;
396     this.feature = feature;
397     this.value = value;
398     this.index = index;
399
400     if (index != CommandParameter.NO_INDEX)
401     {
402       ownerList = getOwnerList(owner, feature);
403     }
404   }
405
406   /**
407    * This returns the owner object upon which the command will act.
408    */

409   public EObject getOwner()
410   {
411     return owner;
412   }
413
414   /**
415    * This returns the feature of the owner object upon the command will act.
416    */

417   public EStructuralFeature getFeature()
418   {
419     return feature;
420   }
421
422   /**
423    * If the command will set a single value in a list, this returns the list in which it will set; null otherwise.
424    */

425   public EList getOwnerList()
426   {
427     return ownerList;
428   }
429
430   /**
431    * This returns the position at which the objects will be added.
432    */

433   public int getIndex()
434   {
435     return index;
436   }
437
438   /**
439    * This returns the value to be set.
440    */

441   public Object JavaDoc getValue()
442   {
443     return value;
444   }
445
446   /**
447    * This returns the old value of the feature which must be restored during undo.
448    */

449   public Object JavaDoc getOldValue()
450   {
451     return oldValue;
452   }
453
454   protected static final EcorePackage ecorePackage = EcorePackage.eINSTANCE;
455
456   protected boolean prepare()
457   {
458     boolean result = false;
459
460     // If there is an owner.
461
//
462
if (owner != null)
463     {
464       if (domain.isReadOnly(owner.eResource()))
465       {
466         return false;
467       }
468
469       // Get the owner's meta object.
470
//
471
EClass eMetaObject = owner.eClass();
472
473       // Is the feature an attribute of the owner...
474
//
475
if (eMetaObject.getEAllAttributes().contains(feature))
476       {
477         // If must be of this type then.
478
//
479
EAttribute eAttribute = (EAttribute)feature;
480         EClassifier eType = eAttribute.getEType();
481
482         if (ownerList != null)
483         {
484           // Setting at an index. Make sure the index is valid, the type is valid, and the value isn't already in a
485
// unique feature. Record the old value.
486
//
487
if (index >= 0 && index < ownerList.size() && eType.isInstance(value) &&
488               (!eAttribute.isUnique() || !ownerList.contains(value)))
489           {
490             oldValue = ownerList.get(index);
491             result = true;
492           }
493         }
494         else if (eAttribute.isMany())
495         {
496           // If the attribute is set, record it's old value.
497
//
498
if (owner.eIsSet(eAttribute))
499           {
500             oldValue = new BasicEList((EList)owner.eGet(feature));
501           }
502
503           if (value == null)
504           {
505             result = true;
506           }
507           else if (value instanceof Collection JavaDoc)
508           {
509             Collection JavaDoc collection = (Collection JavaDoc)value;
510             result = true;
511             for (Iterator JavaDoc objects = collection.iterator(); objects.hasNext(); )
512             {
513               if (!eType.isInstance(objects.next()))
514               {
515                 result = false;
516                 break;
517               }
518             }
519           }
520         }
521         else
522         {
523           // If the attribute is set, record it's old value.
524
//
525
if (owner.eIsSet(eAttribute))
526           {
527             oldValue = owner.eGet(feature);
528           }
529
530           result = value == null || eType.isInstance(value);
531         }
532       }
533       // Is the feature an reference of the owner...
534
//
535
else if (eMetaObject.getEAllReferences().contains(feature))
536       {
537         // It must be of this type.
538
//
539
EReference eReference = (EReference)feature;
540
541         // Make sure we're only setting a single value -- either a value at a specified index in a multi-valued
542
// reference or just a single-valued reference is allowed. Setting a whole multi-valued feature is not.
543
//
544
if (ownerList != null)
545         {
546           if (index >= 0 && index < ownerList.size() && eReference.getEType().isInstance(value) &&
547               (!eReference.isUnique() || !ownerList.contains(value)))
548           {
549             oldValue = ownerList.get(index);
550             result = true;
551           }
552         }
553         else if (!eReference.isMany())
554         {
555           oldValue = owner.eGet(feature);
556
557           if (value == null || eReference.getEType().isInstance(value))
558           {
559             result = true;
560           }
561         }
562
563         // Make sure the container is not being put into a contained object.
564
//
565
if (result && eReference.isContainment())
566         {
567           for (EObject container = owner; container != null; container = container.eContainer())
568           {
569             if (value == container)
570             {
571               result = false;
572               break;
573             }
574           }
575         }
576
577         // Check whether the command is undoable.
578
//
579
if (result && eReference.getEOpposite() != null)
580         {
581           EReference eOtherEnd = eReference.getEOpposite();
582           if (eOtherEnd.isMany())
583           {
584             // If there is an existing value, the opposite reference is multi-valued, and the owner is not last in that
585
// opposite reference, then executing the set will remove the owner from its position in the list, and
586
// undoing would add it back at the end, failing to preserve the order.
587
//
588
if (oldValue != null)
589             {
590               EList oppositeList = (EList)((EObject)oldValue).eGet(eOtherEnd);
591               canUndo = oppositeList.get(oppositeList.size() - 1) == owner;
592             }
593           }
594           else
595           {
596             // If the new value is non-null and the opposite reference is single-valued, then the set will clear that
597
// opposite reference, and undoing would not restore it.
598
//
599
canUndo = (value == null || ((EObject)value).eGet(eOtherEnd) == null);
600           }
601
602         }
603       }
604     }
605
606     return result;
607   }
608
609   public void doExecute()
610   {
611     // Either set or unset the feature.
612
//
613
if (ownerList != null)
614     {
615       ownerList.set(index, value);
616     }
617     else if (value == null)
618     {
619       owner.eUnset(feature);
620     }
621     else
622     {
623       owner.eSet(feature, value);
624     }
625   }
626
627   public boolean doCanUndo()
628   {
629     return canUndo;
630   }
631
632   public void doUndo()
633   {
634     // Either set or unset the old value.
635
//
636
if (ownerList != null)
637     {
638       ownerList.set(index, oldValue);
639     }
640     else if (oldValue == null)
641     {
642       owner.eUnset(feature);
643     }
644     else
645     {
646       owner.eSet(feature, oldValue);
647     }
648   }
649
650   public void doRedo()
651   {
652     // Either set or unset the feature.
653
//
654
if (ownerList != null)
655     {
656       ownerList.set(index, value);
657     }
658     else if (value == null)
659     {
660       owner.eUnset(feature);
661     }
662     else
663     {
664       owner.eSet(feature, value);
665     }
666   }
667
668   public Collection JavaDoc doGetResult()
669   {
670     return Collections.singleton(owner);
671   }
672
673   public Collection JavaDoc doGetAffectedObjects()
674   {
675     return Collections.singleton(owner);
676   }
677
678   /**
679    * This gives an abbreviated name using this object's own class' name, without package qualification,
680    * followed by a space separated list of <tt>field:value</tt> pairs.
681    */

682   public String JavaDoc toString()
683   {
684     StringBuffer JavaDoc result = new StringBuffer JavaDoc(super.toString());
685     result.append(" (owner: " + owner + ")");
686     result.append(" (feature: " + feature + ")");
687     if (ownerList != null)
688     {
689       result.append(" (ownerList: " + ownerList + ")");
690       result.append(" (index: " + index + ")");
691     }
692     result.append(" (value: " + value + ")");
693     result.append(" (oldValue: " + oldValue + ")");
694
695     return result.toString();
696   }
697 }
698
Popular Tags