Coverage Report - org.argouml.model.euml.ModelEventPumpEUMLImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
ModelEventPumpEUMLImpl
0%
0/170
0%
0/102
3.194
ModelEventPumpEUMLImpl$1
0%
0/3
N/A
3.194
ModelEventPumpEUMLImpl$1EventAndListeners
0%
0/5
N/A
3.194
ModelEventPumpEUMLImpl$Listener
0%
0/19
0%
0/12
3.194
 
 1  
 // $Id: ModelEventPumpEUMLImpl.java 19025 2011-02-13 12:11:30Z bobtarling $
 2  
 /*******************************************************************************
 3  
  * Copyright (c) 2007,2010 Bogdan Pistol and other contributors
 4  
  * All rights reserved. This program and the accompanying materials
 5  
  * are made available under the terms of the Eclipse Public License v1.0
 6  
  * which accompanies this distribution, and is available at
 7  
  * http://www.eclipse.org/legal/epl-v10.html
 8  
  *
 9  
  * Contributors:
 10  
  *    Bogdan Pistol - initial implementation
 11  
  *******************************************************************************/
 12  
 package org.argouml.model.euml;
 13  
 
 14  
 import java.beans.PropertyChangeEvent;
 15  
 import java.beans.PropertyChangeListener;
 16  
 import java.util.ArrayList;
 17  
 import java.util.EventObject;
 18  
 import java.util.HashMap;
 19  
 import java.util.HashSet;
 20  
 import java.util.Iterator;
 21  
 import java.util.LinkedHashMap;
 22  
 import java.util.LinkedList;
 23  
 import java.util.List;
 24  
 import java.util.Map;
 25  
 import java.util.Set;
 26  
 
 27  
 import org.apache.log4j.Logger;
 28  
 import org.argouml.model.AbstractModelEventPump;
 29  
 import org.argouml.model.AddAssociationEvent;
 30  
 import org.argouml.model.AttributeChangeEvent;
 31  
 import org.argouml.model.DeleteInstanceEvent;
 32  
 import org.argouml.model.RemoveAssociationEvent;
 33  
 import org.eclipse.emf.common.command.CommandStackListener;
 34  
 import org.eclipse.emf.common.notify.Notification;
 35  
 import org.eclipse.emf.common.notify.Notifier;
 36  
 import org.eclipse.emf.common.notify.impl.NotificationImpl;
 37  
 import org.eclipse.emf.ecore.ENamedElement;
 38  
 import org.eclipse.emf.ecore.EObject;
 39  
 import org.eclipse.emf.ecore.EReference;
 40  
 import org.eclipse.uml2.uml.Property;
 41  
 
 42  
 /**
 43  
  * The implementation of the ModelEventPump for eUML.
 44  
  */
 45  
 class ModelEventPumpEUMLImpl extends AbstractModelEventPump {
 46  
 
 47  
     /**
 48  
      * A list of model elements that when removed should not create delete
 49  
      * events. See issue 
 50  
 
 51  
      */
 52  0
     final private List<Property> deleteEventIgnoreList =
 53  
         new ArrayList<Property>();
 54  
     
 55  
     /**
 56  
      * A listener attached to a UML element
 57  
      */
 58  
     private class Listener {
 59  
 
 60  
         private PropertyChangeListener listener;
 61  
 
 62  
         private Set<String> props;
 63  
 
 64  0
         Listener(PropertyChangeListener listener, String[] properties) {
 65  0
             this.listener = listener;
 66  0
             if (properties != null) {
 67  0
                 setProperties(properties);
 68  
             }
 69  0
         }
 70  
 
 71  
         void setProperties(String[] properties) {
 72  0
             if (properties == null) {
 73  0
                 props = null;
 74  
             } else {
 75  0
                 if (props == null) {
 76  0
                     props = new HashSet<String>();
 77  
                 }
 78  0
                 for (String s : properties) {
 79  0
                     props.add(s);
 80  
                 }
 81  
             }
 82  0
         }
 83  
 
 84  
         void removeProperties(String[] properties) {
 85  0
             if (props == null) {
 86  0
                 return;
 87  
             }
 88  0
             for (String s : properties) {
 89  0
                 props.remove(s);
 90  
             }
 91  0
         }
 92  
 
 93  
         PropertyChangeListener getListener() {
 94  0
             return listener;
 95  
         }
 96  
 
 97  
         Set<String> getProperties() {
 98  0
             return props;
 99  
         }
 100  
 
 101  
     }
 102  
 
 103  
     /**
 104  
      * The model implementation.
 105  
      */
 106  
     private EUMLModelImplementation modelImpl;
 107  
 
 108  0
     private RootContainerAdapter rootContainerAdapter =
 109  
             new RootContainerAdapter(this);
 110  
 
 111  
     // Access should be fast
 112  0
     private Map<Object, List<Listener>> registerForElements =
 113  
             new HashMap<Object, List<Listener>>();
 114  
 
 115  
     // Iteration should be fast
 116  0
     private Map<Object, List<Listener>> registerForClasses =
 117  
             new LinkedHashMap<Object, List<Listener>>();
 118  
 
 119  
     private Object mutex;
 120  
 
 121  0
     private static final Logger LOG =
 122  
             Logger.getLogger(ModelEventPumpEUMLImpl.class);
 123  
 
 124  
     public static final int COMMAND_STACK_UPDATE =
 125  
             Notification.EVENT_TYPE_COUNT + 1;
 126  
 
 127  
     /**
 128  
      * Constructor.
 129  
      * 
 130  
      * @param implementation
 131  
      *                The ModelImplementation.
 132  
      */
 133  0
     public ModelEventPumpEUMLImpl(EUMLModelImplementation implementation) {
 134  0
         modelImpl = implementation;
 135  0
         mutex = this;
 136  0
         implementation.getEditingDomain().getCommandStack()
 137  0
                 .addCommandStackListener(new CommandStackListener() {
 138  
 
 139  
                     public void commandStackChanged(EventObject event) {
 140  0
                         notifyChanged(new NotificationImpl(
 141  
                                 COMMAND_STACK_UPDATE, false, false));
 142  0
                     }
 143  
 
 144  
                 });
 145  0
     }
 146  
 
 147  
     /**
 148  
      * Setter for the root container
 149  
      * 
 150  
      * @param container
 151  
      */
 152  
     public void setRootContainer(Notifier container) {
 153  0
         rootContainerAdapter.setRootContainer(container);
 154  0
     }
 155  
 
 156  
     public RootContainerAdapter getRootContainer() {
 157  0
         return rootContainerAdapter;
 158  
     }
 159  
 
 160  
     public void addClassModelEventListener(PropertyChangeListener listener,
 161  
             Object modelClass, String[] propertyNames) {
 162  0
         if (!(modelClass instanceof Class 
 163  
                 && EObject.class.isAssignableFrom((Class) modelClass))) {
 164  0
             throw new IllegalArgumentException(
 165  
                     "The model class must be instance of " //$NON-NLS-1$
 166  
                             + "java.lang.Class<EObject>"); //$NON-NLS-1$
 167  
         }
 168  0
         registerListener(
 169  
                 modelClass, listener, propertyNames, registerForClasses);
 170  0
     }
 171  
 
 172  
     public void addModelEventListener(PropertyChangeListener listener,
 173  
             Object modelElement, String[] propertyNames) {
 174  0
         if (LOG.isDebugEnabled()) {
 175  0
             LOG.debug("Adding a listener to " //$NON-NLS-1$
 176  
                     + modelElement
 177  
                     + " for " //$NON-NLS-1$
 178  
                     + propertyNames);
 179  
         }
 180  0
         if (!(modelElement instanceof EObject)) {
 181  0
             throw new IllegalArgumentException(
 182  
                     "The modelelement must be instance " //$NON-NLS-1$
 183  
                             + "of EObject."); //$NON-NLS-1$
 184  
         }
 185  0
         registerListener(
 186  
                 modelElement, listener, propertyNames, registerForElements);
 187  0
     }
 188  
 
 189  
     public void addModelEventListener(PropertyChangeListener listener,
 190  
             Object modelelement) {
 191  0
         addModelEventListener(listener, modelelement, (String[]) null);
 192  0
     }
 193  
 
 194  
     private void registerListener(Object notifier,
 195  
             PropertyChangeListener listener, String[] propertyNames,
 196  
             Map<Object, List<Listener>> register) {
 197  0
         if (notifier == null || listener == null) {
 198  0
             throw new NullPointerException(
 199  
                     "The model element/class and the " //$NON-NLS-1$
 200  
                     + "listener must be non-null."); //$NON-NLS-1$
 201  
         }
 202  0
         synchronized (mutex) {
 203  0
             List<Listener> list = register.get(notifier);
 204  0
             boolean found = false;
 205  0
             if (list == null) {
 206  0
                 list = new ArrayList<Listener>();
 207  
             } else {
 208  0
                 for (Listener l : list) {
 209  0
                     if (l.getListener() == listener) {
 210  0
                         l.setProperties(propertyNames);
 211  0
                         found = true;
 212  0
                         break;
 213  
                     }
 214  
                 }
 215  
             }
 216  0
             if (!found) {
 217  0
                 list.add(new Listener(listener, propertyNames));
 218  0
                 register.put(notifier, list);
 219  
             }
 220  0
         }
 221  0
     }
 222  
 
 223  
     public void flushModelEvents() {
 224  
         // TODO: Auto-generated method stub
 225  0
     }
 226  
 
 227  
     public void removeClassModelEventListener(PropertyChangeListener listener,
 228  
             Object modelClass, String[] propertyNames) {
 229  0
         if (!(modelClass instanceof Class && EObject.class
 230  
                 .isAssignableFrom((Class) modelClass))) {
 231  0
             throw new IllegalArgumentException();
 232  
         }
 233  0
         unregisterListener(
 234  
                 modelClass, listener, propertyNames, registerForClasses);
 235  0
     }
 236  
 
 237  
     public void removeModelEventListener(PropertyChangeListener listener,
 238  
             Object modelelement, String[] propertyNames) {
 239  0
         if (!(modelelement instanceof EObject)) {
 240  0
             throw new IllegalArgumentException();
 241  
         }
 242  0
         unregisterListener(
 243  
                 modelelement, listener, propertyNames, registerForElements);
 244  0
     }
 245  
 
 246  
     public void removeModelEventListener(PropertyChangeListener listener,
 247  
             Object modelelement) {
 248  0
         removeModelEventListener(listener, modelelement, (String[]) null);
 249  0
     }
 250  
 
 251  
     private void unregisterListener(Object notifier,
 252  
             PropertyChangeListener listener, String[] propertyNames,
 253  
             Map<Object, List<Listener>> register) {
 254  0
         if (notifier == null || listener == null) {
 255  0
             throw new NullPointerException(
 256  
                     "The model element/class and the "  //$NON-NLS-1$
 257  
                     + "listener must be non-null."); //$NON-NLS-1$
 258  
         }
 259  0
         synchronized (mutex) {
 260  0
             List<Listener> list = register.get(notifier);
 261  0
             if (list == null) {
 262  0
                 return;
 263  
             }
 264  0
             Iterator<Listener> iter = list.iterator();
 265  0
             while (iter.hasNext()) {
 266  0
                 Listener l = iter.next();
 267  0
                 if (l.getListener() == listener) {
 268  0
                     if (propertyNames != null) {
 269  0
                         l.removeProperties(propertyNames);
 270  
                     } else {
 271  0
                         iter.remove();
 272  
                     }
 273  0
                     break;
 274  
                 }
 275  0
             }
 276  0
         }
 277  0
     }
 278  
 
 279  
     /**
 280  
      * @see org.eclipse.emf.common.notify.Adapter#notifyChanged(Notification)
 281  
      * @param notification
 282  
      *                The notification event
 283  
      */
 284  
     public void notifyChanged(Notification notification) {
 285  
         
 286  0
         if (notification.getEventType() == Notification.REMOVING_ADAPTER) {
 287  0
             return;
 288  
         }
 289  
         
 290  0
         final ENamedElement feature = (ENamedElement) notification.getFeature();
 291  
         
 292  0
         final String featureName =
 293  
             feature == null ? "" : feature.getName(); //$NON-NLS-1$
 294  
 
 295  
         final EReference oppositeRef;
 296  0
         if (feature instanceof EReference) {
 297  0
             oppositeRef = ((EReference) feature).getEOpposite();
 298  
         } else {
 299  0
             oppositeRef = null;
 300  
         }
 301  
 
 302  0
         fireEvent(
 303  
                 notification.getNotifier(), 
 304  
                 notification.getOldValue(), 
 305  
                 notification.getNewValue(), 
 306  
                 notification.getEventType(), 
 307  
                 featureName,
 308  
                 oppositeRef);
 309  0
     }
 310  
 
 311  
     /**
 312  
      * @see org.eclipse.emf.common.notify.Adapter#notifyChanged(Notification)
 313  
      * @param notification
 314  
      *                The notification event
 315  
      */
 316  
     void fireEvent(
 317  
             Object notifier, 
 318  
             Object oldValue, 
 319  
             Object newValue, 
 320  
             int eventType, 
 321  
             String featureName,
 322  
             EReference oppositeRef) {
 323  
 
 324  0
         LOG.debug("event  - Property: " //$NON-NLS-1$
 325  
                 + featureName 
 326  
                 + " Old: " + oldValue //$NON-NLS-1$
 327  
                 + " New: " + newValue //$NON-NLS-1$
 328  
                 + " From: " + notifier); //$NON-NLS-1$
 329  
         
 330  0
         class EventAndListeners {
 331  
             public EventAndListeners(PropertyChangeEvent e,
 332  0
                     List<PropertyChangeListener> l) {
 333  0
                 event = e;
 334  0
                 listeners = l;
 335  0
             }
 336  
 
 337  
             private PropertyChangeEvent event;
 338  
 
 339  
             private List<PropertyChangeListener> listeners;
 340  
         }
 341  
 
 342  0
         List<EventAndListeners> events = new ArrayList<EventAndListeners>();
 343  
 
 344  0
         if (eventType == Notification.SET) {
 345  0
             String propName =
 346  
                     mapPropertyName(featureName);
 347  0
             events.add(new EventAndListeners(new AttributeChangeEvent(
 348  
                     notifier, propName,
 349  
                     oldValue, newValue,
 350  
                     null), getListeners(
 351  
                             notifier, propName)));
 352  0
         } else if (eventType == Notification.ADD
 353  
                 || eventType == Notification.REMOVE) {
 354  0
             String propName = mapPropertyName(featureName);
 355  0
             if (eventType == Notification.ADD) {
 356  0
                 events.add(new EventAndListeners(new AddAssociationEvent(
 357  
                         notifier, propName, null,
 358  
                         newValue,
 359  
                         newValue, null), getListeners(
 360  
                                 notifier, propName)));
 361  0
                 events.add(new EventAndListeners(new AttributeChangeEvent(
 362  
                         notifier, propName, null,
 363  
                         newValue, null), getListeners(
 364  
                                 notifier, propName)));
 365  
             } else {
 366  0
                 if (isDeleteEventRequired(oldValue)) {
 367  
                     // Changing of a property can result in the property
 368  
                     // being removed and added again (eclipse behaviour)
 369  
                     // we don't want to mistake this for deletion of the
 370  
                     // property. See issue 5853
 371  0
                     events.add(new EventAndListeners(
 372  
                             new DeleteInstanceEvent(
 373  
                                     oldValue,
 374  
                                     "remove",  //$NON-NLS-1$
 375  
                                     null, null, null),
 376  
                                     getListeners(
 377  
                                         oldValue)));
 378  
                 }
 379  0
                 events.add(new EventAndListeners(
 380  
                         new RemoveAssociationEvent(
 381  
                                 notifier, propName,
 382  
                                 oldValue, null,
 383  
                                 oldValue, null),
 384  
                         getListeners(
 385  
                                 notifier, propName)));
 386  0
                 events.add(new EventAndListeners(
 387  
                         new AttributeChangeEvent(
 388  
                                 notifier, propName,
 389  
                                 oldValue, null, null),
 390  
                         getListeners(
 391  
                                 notifier, propName)));
 392  
             }
 393  
 
 394  0
             if (oppositeRef != null) {
 395  0
                 propName = mapPropertyName(oppositeRef.getName());
 396  0
                 if (eventType == Notification.ADD) {
 397  0
                     events.add(new EventAndListeners(
 398  
                             new AddAssociationEvent(
 399  
                                     newValue,
 400  
                                     propName, null,
 401  
                                     notifier,
 402  
                                     notifier, null),
 403  
                             getListeners(
 404  
                                     newValue,
 405  
                                     propName)));
 406  0
                     events.add(new EventAndListeners(
 407  
                             new AttributeChangeEvent(
 408  
                                     newValue,
 409  
                                     propName, null,
 410  
                                     notifier, null),
 411  
                             getListeners(
 412  
                                     newValue,
 413  
                                     propName)));
 414  
                 } else {
 415  0
                     events.add(new EventAndListeners(
 416  
                             new AddAssociationEvent(
 417  
                                     oldValue,
 418  
                                     propName,
 419  
                                     notifier, null,
 420  
                                     notifier, null),
 421  
                             getListeners(
 422  
                                     oldValue,
 423  
                                     propName)));
 424  0
                     events.add(new EventAndListeners(
 425  
                             new AttributeChangeEvent(
 426  
                                     oldValue,
 427  
                                     propName,
 428  
                                     notifier, null, null),
 429  
                             getListeners(
 430  
                                     oldValue,
 431  
                                     propName)));
 432  
                 }
 433  
             }
 434  
         }
 435  
 
 436  0
         for (EventAndListeners e : events) {
 437  0
             if (e.listeners != null) {
 438  0
                 for (PropertyChangeListener l : e.listeners) {
 439  0
                     l.propertyChange(e.event);
 440  
                 }
 441  
             }
 442  
         }
 443  0
     }
 444  
     
 445  
     
 446  
     
 447  
     /**
 448  
      * Determine if we should create a delete event for the given property
 449  
      * when EMF tells us it has been removed. This is currently used to
 450  
      * work around the problem discussed in issue 5853.
 451  
      * 
 452  
      * @param element
 453  
      * @return true if 
 454  
      */
 455  
     private boolean isDeleteEventRequired(
 456  
             final Object element) {
 457  0
         if (element instanceof Property) {
 458  0
             synchronized (deleteEventIgnoreList) {
 459  0
                 if (deleteEventIgnoreList.contains(element)) {
 460  0
                     deleteEventIgnoreList.remove(element);
 461  0
                     return false;
 462  
                 }
 463  0
             }
 464  
         }
 465  0
         return true;
 466  
     }
 467  
     
 468  
     /**
 469  
      * Add Element to list which will cause the next delete event to
 470  
      * be ignored.
 471  
      * 
 472  
      * @param property
 473  
      */
 474  
     void addElementForDeleteEventIgnore(Property property) {
 475  0
         synchronized (deleteEventIgnoreList) {
 476  0
             deleteEventIgnoreList.add(property);
 477  0
         }
 478  0
     }
 479  
 
 480  
     private List<PropertyChangeListener> getListeners(Object element) {
 481  0
         return getListeners(element, null);
 482  
     }
 483  
 
 484  
     @SuppressWarnings("unchecked")
 485  
     private List<PropertyChangeListener> getListeners(Object element,
 486  
             String propName) {
 487  0
         List<PropertyChangeListener> returnedList =
 488  
                 new ArrayList<PropertyChangeListener>();
 489  
 
 490  0
         synchronized (mutex) {
 491  0
             addListeners(returnedList, element, propName, registerForElements);
 492  0
             for (Object o : registerForClasses.keySet()) {
 493  0
                 if (o instanceof Class) {
 494  0
                     Class type = (Class) o;
 495  0
                     if (type.isAssignableFrom(element.getClass())) {
 496  0
                         addListeners(
 497  
                                 returnedList, o, propName, registerForClasses);
 498  
                     }
 499  0
                 }
 500  
             }
 501  0
         }
 502  0
         return returnedList.isEmpty() ? null : returnedList;
 503  
     }
 504  
 
 505  
     private void addListeners(List<PropertyChangeListener> listeners,
 506  
             Object element, String propName,
 507  
             Map<Object, List<Listener>> register) {
 508  0
         List<Listener> list = register.get(element);
 509  0
         if (list != null) {
 510  0
             for (Listener l : list) {
 511  0
                 if (propName == null || l.getProperties() == null
 512  
                         || l.getProperties().contains(propName)) {
 513  0
                     listeners.add(l.getListener());
 514  
                 }
 515  
             }
 516  
         }
 517  0
     }
 518  
 
 519  
     public void startPumpingEvents() {
 520  0
         rootContainerAdapter.setDeliverEvents(true);
 521  0
     }
 522  
 
 523  
     public void stopPumpingEvents() {
 524  0
         rootContainerAdapter.setDeliverEvents(false);
 525  0
     }
 526  
     
 527  
     private String mapPropertyName(String name) {
 528  
         // TODO: map UML2 names to UML1.x names
 529  0
         if (name.equals("ownedAttribute")) { //$NON-NLS-1$
 530  0
             return "feature"; //$NON-NLS-1$
 531  
         }
 532  0
         return name;
 533  
     }
 534  
 
 535  
     @SuppressWarnings("unchecked")
 536  
     public List getDebugInfo() {
 537  0
         List info = new ArrayList();
 538  0
         info.add("Event Listeners"); //$NON-NLS-1$
 539  0
         for (Iterator it = registerForElements.entrySet().iterator(); 
 540  0
                 it.hasNext(); ) {
 541  0
             Map.Entry entry = (Map.Entry) it.next();
 542  
 
 543  0
             String item = entry.getKey().toString();
 544  0
             List modelElementNode = newDebugNode(item);
 545  0
             info.add(modelElementNode);
 546  0
             List listenerList = (List) entry.getValue();
 547  
             
 548  0
             Map<String, List<String>> map = new HashMap<String, List<String>>();
 549  
             
 550  0
             for (Iterator listIt = listenerList.iterator(); listIt.hasNext();) {
 551  0
                 Listener listener = (Listener) listIt.next();
 552  
 
 553  0
                 if (listener.getProperties() != null) {
 554  0
                     for (String eventName : listener.getProperties()) {
 555  0
                         if (!map.containsKey(eventName)) {
 556  0
                             map.put(eventName, new LinkedList<String>());
 557  
                         }
 558  0
                         map.get(eventName).add(
 559  
                                 listener.getListener().getClass().getName());
 560  
                     }
 561  
                 } else {
 562  0
                     if (!map.containsKey("")) {
 563  0
                         map.put("", new LinkedList<String>());
 564  
                     }
 565  0
                     map.get("")
 566  
                             .add(listener.getListener().getClass().getName());
 567  
                 }
 568  0
             }
 569  0
             for (Map.Entry o : map.entrySet()) {
 570  0
                 modelElementNode.add((String) o.getKey());
 571  0
                 modelElementNode.add((List<String>) o.getValue());
 572  
             }
 573  0
         }
 574  0
         return info;
 575  
     }
 576  
        
 577  
     private List<String> newDebugNode(String name) {
 578  0
         List<String> list = new ArrayList<String>();
 579  0
         list.add(name);
 580  0
         return list;
 581  
     }
 582  
 
 583  
 }