Coverage Report - org.argouml.model.mdr.ModelEventPumpMDRImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
ModelEventPumpMDRImpl
60%
182/299
48%
72/148
4.167
Registry
84%
44/52
71%
30/42
4.167
 
 1  
 /* $Id: ModelEventPumpMDRImpl.java 18269 2010-04-15 17:53:16Z tfmorris $
 2  
  *****************************************************************************
 3  
  * Copyright (c) 2005,2010 Contributors - see below
 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  
  *    Tom Morris
 11  
  *****************************************************************************
 12  
  *
 13  
  * Some portions of this file was previously release using the BSD License:
 14  
  */
 15  
 
 16  
 // Copyright (c) 2005-2008 The Regents of the University of California. All
 17  
 // Rights Reserved. Permission to use, copy, modify, and distribute this
 18  
 // software and its documentation without fee, and without a written
 19  
 // agreement is hereby granted, provided that the above copyright notice
 20  
 // and this paragraph appear in all copies. This software program and
 21  
 // documentation are copyrighted by The Regents of the University of
 22  
 // California. The software program and documentation are supplied "AS
 23  
 // IS", without any accompanying services from The Regents. The Regents
 24  
 // does not warrant that the operation of the program will be
 25  
 // uninterrupted or error-free. The end-user understands that the program
 26  
 // was developed for research purposes and is advised not to rely
 27  
 // exclusively on the program for any reason. IN NO EVENT SHALL THE
 28  
 // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
 29  
 // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
 30  
 // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 31  
 // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 32  
 // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
 33  
 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 34  
 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 35  
 // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 36  
 // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
 37  
 // UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 38  
 
 39  
 package org.argouml.model.mdr;
 40  
 
 41  
 import java.beans.PropertyChangeListener;
 42  
 import java.util.ArrayList;
 43  
 import java.util.Collection;
 44  
 import java.util.Collections;
 45  
 import java.util.HashMap;
 46  
 import java.util.HashSet;
 47  
 import java.util.Iterator;
 48  
 import java.util.List;
 49  
 import java.util.Map;
 50  
 import java.util.Set;
 51  
 
 52  
 import javax.jmi.model.Association;
 53  
 import javax.jmi.model.AssociationEnd;
 54  
 import javax.jmi.model.Attribute;
 55  
 import javax.jmi.model.GeneralizableElement;
 56  
 import javax.jmi.model.ModelElement;
 57  
 import javax.jmi.model.ModelPackage;
 58  
 import javax.jmi.model.MofClass;
 59  
 import javax.jmi.model.NameNotFoundException;
 60  
 import javax.jmi.model.Reference;
 61  
 import javax.jmi.reflect.InvalidObjectException;
 62  
 import javax.jmi.reflect.RefAssociation;
 63  
 import javax.jmi.reflect.RefBaseObject;
 64  
 import javax.jmi.reflect.RefObject;
 65  
 
 66  
 import org.apache.log4j.Logger;
 67  
 import org.argouml.model.AbstractModelEventPump;
 68  
 import org.argouml.model.AddAssociationEvent;
 69  
 import org.argouml.model.AttributeChangeEvent;
 70  
 import org.argouml.model.DeleteInstanceEvent;
 71  
 import org.argouml.model.InvalidElementException;
 72  
 import org.argouml.model.Model;
 73  
 import org.argouml.model.RemoveAssociationEvent;
 74  
 import org.argouml.model.UmlChangeEvent;
 75  
 import org.netbeans.api.mdr.MDRManager;
 76  
 import org.netbeans.api.mdr.MDRepository;
 77  
 import org.netbeans.api.mdr.events.AssociationEvent;
 78  
 import org.netbeans.api.mdr.events.AttributeEvent;
 79  
 import org.netbeans.api.mdr.events.InstanceEvent;
 80  
 import org.netbeans.api.mdr.events.MDRChangeEvent;
 81  
 import org.netbeans.api.mdr.events.MDRPreChangeListener;
 82  
 import org.netbeans.api.mdr.events.TransactionEvent;
 83  
 import org.netbeans.api.mdr.events.VetoChangeException;
 84  
 
 85  
 /**
 86  
  * The ModelEventPump for the MDR implementation.<p>
 87  
  *
 88  
  * This implements three different event dispatching interfaces
 89  
  * which support a variety of different types of listener registration.
 90  
  * We keep a single event listener registered with the repository
 91  
  * for all events and then re-dispatch events to those listeners
 92  
  * who have requested them.<p>
 93  
  *
 94  
  * @since ARGO0.19.5
 95  
  * @author Ludovic Ma&icirc;tre
 96  
  * @author Tom Morris
 97  
  */
 98  
 class ModelEventPumpMDRImpl extends AbstractModelEventPump implements
 99  
         MDRPreChangeListener {
 100  
 
 101  
     /**
 102  
      * Logger.
 103  
      */
 104  900
     private static final Logger LOG =
 105  
         Logger.getLogger(ModelEventPumpMDRImpl.class);
 106  
     
 107  
     private static final boolean VETO_READONLY_CHANGES = true;
 108  
 
 109  
     private MDRModelImplementation modelImpl;
 110  
 
 111  900
     private Object registrationMutex = new Byte[0];
 112  
 
 113  
     private MDRepository repository;
 114  
 
 115  900
     private Boolean eventCountMutex = new Boolean(false);
 116  900
     private int pendingEvents = 0;
 117  
     
 118  
     private Thread eventThread;
 119  
 
 120  
     /**
 121  
      * Map of Element/attribute tuples and the listeners they have registered.
 122  
      */
 123  900
     private Registry<PropertyChangeListener> elements = 
 124  
         new Registry<PropertyChangeListener>();
 125  
 
 126  
     /**
 127  
      * Map of Class/attribute tuples and the listeners they have registered.
 128  
      */
 129  900
     private Registry<PropertyChangeListener> listenedClasses = 
 130  
         new Registry<PropertyChangeListener>();
 131  
 
 132  
     /**
 133  
      * Map of subtypes for all types in our metamodel.
 134  
      */
 135  
     private Map<String, Collection<String>> subtypeMap;
 136  
 
 137  
     /**
 138  
      * Map of all valid property names (association end names & attribute names)
 139  
      * for each class.
 140  
      */
 141  
     private Map<String, Collection<String>> propertyNameMap;
 142  
     
 143  
     /**
 144  
      * Constructor.
 145  
      *
 146  
      * @param implementation The implementation.
 147  
      */
 148  
     public ModelEventPumpMDRImpl(MDRModelImplementation implementation) {
 149  0
         this(implementation, MDRManager.getDefault().getDefaultRepository());
 150  0
     }
 151  
 
 152  
     /**
 153  
      * Constructor.
 154  
      *
 155  
      * @param implementation The implementation.
 156  
      * @param repo The repository.
 157  
      */
 158  
     public ModelEventPumpMDRImpl(MDRModelImplementation implementation,
 159  
             MDRepository repo) {
 160  900
         super();
 161  900
         modelImpl = implementation;
 162  900
         repository = repo;
 163  900
         subtypeMap = buildTypeMap(modelImpl.getModelPackage());
 164  900
         propertyNameMap = buildPropertyNameMap(modelImpl.getModelPackage());
 165  900
     }
 166  
     
 167  
     /*
 168  
      * @see org.argouml.model.AbstractModelEventPump#addModelEventListener(java.beans.PropertyChangeListener,
 169  
      *      java.lang.Object, java.lang.String[])
 170  
      */
 171  
     public void addModelEventListener(PropertyChangeListener listener,
 172  
             Object modelElement, String[] propertyNames) {
 173  2174
         if (listener == null) {
 174  0
             throw new IllegalArgumentException("A listener must be supplied");
 175  
         }
 176  
 
 177  2174
         if (modelElement == null) {
 178  0
             throw new IllegalArgumentException(
 179  
                     "A model element must be supplied");
 180  
         }
 181  
 
 182  2174
         registerModelEvent(listener, modelElement, propertyNames);
 183  2174
     }
 184  
 
 185  
     /*
 186  
      * @see org.argouml.model.AbstractModelEventPump#addModelEventListener(java.beans.PropertyChangeListener,
 187  
      *      java.lang.Object)
 188  
      */
 189  
     public void addModelEventListener(PropertyChangeListener listener,
 190  
             Object modelElement) {
 191  900
         if (listener == null) {
 192  0
             throw new IllegalArgumentException("A listener must be supplied");
 193  
         }
 194  
 
 195  900
         if (modelElement == null) {
 196  0
             throw new IllegalArgumentException(
 197  
                     "A model element must be supplied");
 198  
         }
 199  
 
 200  900
         registerModelEvent(listener, modelElement, null);
 201  900
     }
 202  
 
 203  
     /*
 204  
      * @see org.argouml.model.AbstractModelEventPump#removeModelEventListener(java.beans.PropertyChangeListener,
 205  
      *      java.lang.Object, java.lang.String[])
 206  
      */
 207  
     public void removeModelEventListener(PropertyChangeListener listener,
 208  
             Object modelelement, String[] propertyNames) {
 209  1
         unregisterModelEvent(listener, modelelement, propertyNames);
 210  1
     }
 211  
 
 212  
     /*
 213  
      * @see org.argouml.model.AbstractModelEventPump#removeModelEventListener(java.beans.PropertyChangeListener,
 214  
      *      java.lang.Object)
 215  
      */
 216  
     public void removeModelEventListener(PropertyChangeListener listener,
 217  
             Object modelelement) {
 218  0
         unregisterModelEvent(listener, modelelement, null);
 219  0
     }
 220  
 
 221  
     /*
 222  
      * @see org.argouml.model.AbstractModelEventPump#addClassModelEventListener(java.beans.PropertyChangeListener,
 223  
      *      java.lang.Object, java.lang.String[])
 224  
      */
 225  
     public void addClassModelEventListener(PropertyChangeListener listener,
 226  
             Object modelClass, String[] propertyNames) {
 227  900
         registerClassEvent(listener, modelClass, propertyNames);
 228  900
     }
 229  
 
 230  
     /*
 231  
      * @see org.argouml.model.AbstractModelEventPump#removeClassModelEventListener(java.beans.PropertyChangeListener,
 232  
      *      java.lang.Object, java.lang.String[])
 233  
      */
 234  
     public void removeClassModelEventListener(PropertyChangeListener listener,
 235  
             Object modelClass, String[] propertyNames) {
 236  0
         unregisterClassEvent(listener, modelClass, propertyNames);
 237  0
     }
 238  
 
 239  
     /**
 240  
      * Detect a change event in MDR and convert this to a change event from the
 241  
      * model interface.  We also keep track of the number of pending changes so
 242  
      * that we can implement a simple flush interface.<p>
 243  
      *
 244  
      * The conversions are according to this table.
 245  
      * <pre>
 246  
      * MDR Event         MDR Event Type            Propogated Event
 247  
      *
 248  
      * InstanceEvent     EVENT_INSTANCE_DELETE     DeleteInstanceEvent
 249  
      * AttributeEvent    EVENT_ATTRIBUTE_SET       AttributeChangeEvent
 250  
      * AssociationEvent  EVENT_ASSOCIATION_ADD     AddAssociationEvent
 251  
      * AssociationEvent  EVENT_ASSOCIATION_REMOVE  RemoveAssociationEvent
 252  
      * </pre>
 253  
      * Any other events are ignored and not propogated beyond the model
 254  
      * subsystem.
 255  
      *
 256  
      * @param mdrEvent Change event from MDR
 257  
      * @see org.netbeans.api.mdr.events.MDRChangeListener#change
 258  
      */
 259  
     public void change(MDRChangeEvent mdrEvent) {
 260  
         
 261  6985
         if (eventThread == null) {
 262  900
             eventThread = Thread.currentThread();
 263  
         }
 264  
         
 265  
         // TODO: This should be done after all events are delivered, but leave
 266  
         // it here for now to avoid last minute synchronization problems
 267  6985
         decrementEvents();
 268  
 
 269  
         // Quick exit if it's a transaction event
 270  
         // (we get a lot of them and they are all ignored)
 271  6985
         if (mdrEvent instanceof TransactionEvent) {
 272  4604
             return;
 273  
         }
 274  
 
 275  2381
         List<UmlChangeEvent> events = new ArrayList<UmlChangeEvent>();
 276  
 
 277  2381
         if (mdrEvent instanceof AttributeEvent) {
 278  996
             AttributeEvent ae = (AttributeEvent) mdrEvent;
 279  996
             events.add(new AttributeChangeEvent(ae.getSource(),
 280  
                     ae.getAttributeName(), ae.getOldElement(),
 281  
                     ae.getNewElement(), mdrEvent));
 282  996
         } else if (mdrEvent instanceof InstanceEvent
 283  
                 && mdrEvent.isOfType(InstanceEvent.EVENT_INSTANCE_DELETE)) {
 284  2
             InstanceEvent ie = (InstanceEvent) mdrEvent;
 285  2
             events.add(new DeleteInstanceEvent(ie.getSource(),
 286  
                     "remove", null, null, mdrEvent));
 287  
             // Clean up index entries
 288  2
             String mofid = ((InstanceEvent)mdrEvent).getInstance().refMofId();
 289  2
             modelImpl.removeElement(mofid);
 290  2
         } else if (mdrEvent instanceof AssociationEvent) {
 291  329
             AssociationEvent ae = (AssociationEvent) mdrEvent;
 292  329
             if (ae.isOfType(AssociationEvent.EVENT_ASSOCIATION_ADD)) {
 293  250
                 events.add(new AddAssociationEvent(
 294  
                         ae.getNewElement(),
 295  
                         mapPropertyName(ae.getEndName()),
 296  
                         ae.getOldElement(), // will always be null
 297  
                         ae.getFixedElement(),
 298  
                         ae.getFixedElement(),
 299  
                         mdrEvent));
 300  
                 // Create a change event for the corresponding property
 301  250
                 events.add(new AttributeChangeEvent(
 302  
                         ae.getNewElement(),
 303  
                         mapPropertyName(ae.getEndName()),
 304  
                         ae.getOldElement(), // will always be null
 305  
                         ae.getFixedElement(),
 306  
                         mdrEvent));
 307  
                 // Create an event for the other end of the association
 308  250
                 events.add(new AddAssociationEvent(
 309  
                         ae.getFixedElement(),
 310  
                         otherAssocEnd(ae),
 311  
                         ae.getOldElement(), // will always be null
 312  
                         ae.getNewElement(),
 313  
                         ae.getNewElement(),
 314  
                         mdrEvent));
 315  
                 // and a change event for that end
 316  250
                 events.add(new AttributeChangeEvent(
 317  
                         ae.getFixedElement(),
 318  
                         otherAssocEnd(ae),
 319  
                         ae.getOldElement(), // will always be null
 320  
                         ae.getNewElement(),
 321  
                         mdrEvent));
 322  79
             } else if (ae.isOfType(AssociationEvent.EVENT_ASSOCIATION_REMOVE)) {
 323  79
                 events.add(new RemoveAssociationEvent(
 324  
                         ae.getOldElement(),
 325  
                         mapPropertyName(ae.getEndName()),
 326  
                         ae.getFixedElement(),
 327  
                         ae.getNewElement(), // will always be null
 328  
                         ae.getFixedElement(),
 329  
                         mdrEvent));
 330  
                 // Create a change event for the associated property
 331  79
                 events.add(new AttributeChangeEvent(
 332  
                         ae.getOldElement(),
 333  
                         mapPropertyName(ae.getEndName()),
 334  
                         ae.getFixedElement(),
 335  
                         ae.getNewElement(), // will always be null
 336  
                         mdrEvent));
 337  
                 // Create an event for the other end of the association
 338  79
                 events.add(new RemoveAssociationEvent(
 339  
                         ae.getFixedElement(),
 340  
                         otherAssocEnd(ae),
 341  
                         ae.getOldElement(),
 342  
                         ae.getNewElement(), // will always be null
 343  
                         ae.getOldElement(),
 344  
                         mdrEvent));
 345  
                 // Create a change event for the associated property
 346  79
                 events.add(new AttributeChangeEvent(
 347  
                         ae.getFixedElement(),
 348  
                         otherAssocEnd(ae),
 349  
                         ae.getOldElement(),
 350  
                         ae.getNewElement(), // will always be null
 351  
                         mdrEvent));
 352  0
             } else if (ae.isOfType(AssociationEvent.EVENT_ASSOCIATION_SET)) {
 353  0
                 LOG.error("Unexpected EVENT_ASSOCIATION_SET received");
 354  
             } else {
 355  0
                 LOG.error("Unknown association event type " + ae.getType());
 356  
             }
 357  329
         } else {
 358  1054
             if (LOG.isDebugEnabled()) {
 359  0
                 String name = mdrEvent.getClass().getName();
 360  
                 // Cut down on debugging noise
 361  0
                 if (!name.endsWith("CreateInstanceEvent")) {
 362  0
                     LOG.debug("Ignoring MDR event " + mdrEvent);
 363  
                 }
 364  
             }
 365  
         }
 366  
 
 367  2381
         for (UmlChangeEvent event : events) {
 368  2314
             fire(event);
 369  
             // Unregister deleted instances after all events have been delivered
 370  2314
             if (event instanceof DeleteInstanceEvent) {
 371  2
                 elements.unregister(null, ((RefBaseObject) event.getSource())
 372  
                         .refMofId(), null);
 373  
             }
 374  
         }
 375  2381
     }
 376  
 
 377  
     private boolean isReadOnly(RefBaseObject object) {
 378  2052
         return modelImpl.isReadOnly(object.refOutermostPackage());
 379  
     }
 380  
     
 381  
     /**
 382  
      * @param e Event from MDR indicating a planned change.
 383  
      * @see org.netbeans.api.mdr.events.MDRPreChangeListener#plannedChange
 384  
      */
 385  
     public void plannedChange(MDRChangeEvent e) {
 386  
         
 387  
         if (VETO_READONLY_CHANGES) {
 388  6985
             if (e instanceof InstanceEvent) {
 389  1056
                 if (e.isOfType(InstanceEvent.EVENT_INSTANCE_CREATE)) {
 390  1054
                     RefBaseObject element = (RefBaseObject) ((InstanceEvent) e)
 391  
                             .getSource();
 392  1054
                     if (isReadOnly(element)) {
 393  0
                         throw new VetoChangeException(e.getSource(), null);
 394  
                     }
 395  1054
                 } else {
 396  2
                     RefObject element = ((InstanceEvent) e).getInstance();
 397  2
                     if (isReadOnly(element)) {
 398  0
                         throw new VetoChangeException(e.getSource(), element);
 399  
                     }
 400  2
                 }
 401  5929
             } else if (e instanceof AssociationEvent) {
 402  
                 // TODO: association changes are too hard to check easily
 403  
                 // we don't know which end of the association is significant
 404  
                 // without checking the metamodel for navigability of the ends
 405  
 //                RefObject element = ((AssociationEvent) e).getFixedElement();
 406  
 //                if (isReadOnly(element)) {
 407  
 //                    throw new VetoChangeException(element, element);
 408  
 //                }
 409  5600
             } else if (e instanceof AttributeEvent) {
 410  996
                 RefObject element = (RefObject) ((AttributeEvent) e)
 411  
                         .getSource();
 412  996
                 if (isReadOnly(element)) {
 413  0
                     throw new VetoChangeException(element, element);
 414  
                 }
 415  
             }
 416  
         }
 417  
 
 418  6985
         synchronized (eventCountMutex) {
 419  6985
             pendingEvents++;
 420  6985
         }
 421  
         
 422  
         // Prototypical logging code that can be enabled and modified to
 423  
         // discover who's creating certain types of events
 424  
 //        if (/* LOG.isDebugEnabled() && */ e instanceof AssociationEvent) {
 425  
 //            AssociationEvent ae = (AssociationEvent) e;
 426  
 //            if (ae.isOfType(AssociationEvent.EVENT_ASSOCIATION_REMOVE)
 427  
 //                    && "namespace".equals(ae.getEndName())
 428  
 //                    /* && ae.getFixedElement() instanceof UmlPackage */) {
 429  
 //                LOG.debug("Removing element " + ae.getOldElement()
 430  
 //                        + " from package " + ae.getFixedElement());
 431  
 //            }
 432  
 //        }
 433  6985
     }
 434  
 
 435  
     /**
 436  
      * @param e
 437  
      *            MDR event which was announced to plannedChange then
 438  
      *            subsequently cancelled.
 439  
      * @see org.netbeans.api.mdr.events.MDRPreChangeListener#changeCancelled
 440  
      */
 441  
     public void changeCancelled(MDRChangeEvent e) {
 442  0
         decrementEvents();
 443  0
     }
 444  
 
 445  
     /**
 446  
      * Decrement count of outstanding events and wake
 447  
      * any waiters when it becomes zero.
 448  
      */
 449  
     private void decrementEvents() {
 450  
 
 451  6985
         synchronized (eventCountMutex) {
 452  6985
             pendingEvents--;
 453  6985
             if (pendingEvents <= 0) {
 454  2077
                 eventCountMutex.notifyAll();
 455  
             }
 456  6985
         }
 457  6985
     }
 458  
 
 459  
     /**
 460  
      * Fire an event to any registered listeners.
 461  
      */
 462  
     private void fire(UmlChangeEvent event) {
 463  2314
         String mofId = ((RefBaseObject) event.getSource()).refMofId();
 464  2314
         String className  = getClassName(event.getSource());
 465  
 
 466  
         // Any given listener is only called once even if it is
 467  
         // registered for multiple relevant matches
 468  2314
         Set<PropertyChangeListener> listeners =
 469  
                 new HashSet<PropertyChangeListener>();
 470  2314
         synchronized (registrationMutex) {
 471  2314
             listeners.addAll(elements.getMatches(mofId, event
 472  
                     .getPropertyName()));
 473  
 
 474  
             // This will include all subtypes registered
 475  2314
             listeners.addAll(listenedClasses.getMatches(className, event
 476  
                     .getPropertyName()));
 477  2314
         }
 478  
 
 479  2314
         if (LOG.isDebugEnabled()) {
 480  0
             LOG.debug("Firing "
 481  
                     + modelImpl.getMetaTypes().getName(event)
 482  
                     + " source "
 483  
                     + modelImpl.getMetaTypes().getName(
 484  
                             event.getSource())
 485  
                     + " [" + ((RefBaseObject) event.getSource()).refMofId()
 486  
                     + "]."  + event.getPropertyName()
 487  
                     + "," + formatElement(event.getOldValue())
 488  
                     + "->" + formatElement(event.getNewValue()));
 489  
         }
 490  
 
 491  2314
         if (!listeners.isEmpty()) {
 492  2314
             for (PropertyChangeListener pcl : listeners) {
 493  
                 if (false /*(LOG.isDebugEnabled()*/) {
 494  
                     LOG.debug("Firing event on " + pcl.getClass().getName()
 495  
                             + "[" + pcl + "]");
 496  
                 }
 497  2547
                 pcl.propertyChange(event);
 498  
             }
 499  
         } else {
 500  
             // For debugging you probably want either this
 501  
             // OR the logging for every event which is fired - not both
 502  
             if (false/*LOG.isDebugEnabled()*/) {
 503  
                 LOG.debug("No listener for "
 504  
                         + modelImpl.getMetaTypes().getName(event)
 505  
                         + " source "
 506  
                         + modelImpl.getMetaTypes().getName(
 507  
                                 event.getSource())
 508  
                         + " ["
 509  
                         + ((RefBaseObject) event.getSource()).refMofId() + "]."
 510  
                         + event.getPropertyName() + "," + event.getOldValue()
 511  
                         + "->" + event.getNewValue());
 512  
             }
 513  
         }
 514  2314
     }
 515  
 
 516  
 
 517  
     /**
 518  
      * Register a listener for a Model Event.  The ModelElement's
 519  
      * MofID is used as the string to match against.
 520  
      */
 521  
     private void registerModelEvent(PropertyChangeListener listener,
 522  
             Object modelElement, String[] propertyNames) {
 523  3074
         if (listener == null || modelElement == null) {
 524  0
             throw new IllegalArgumentException("Neither listener (" + listener
 525  
                     + ") or modelElement (" + modelElement
 526  
                     + ") can be null! [Property names: " + propertyNames + "]");
 527  
         }
 528  
 
 529  
         // Fetch the key before going in synchronized mode
 530  3074
         String mofId = ((RefBaseObject) modelElement).refMofId();
 531  
         try {
 532  3074
             verifyAttributeNames(((RefBaseObject) modelElement).refMetaObject(),
 533  
                     propertyNames);
 534  0
         } catch (InvalidObjectException e) {
 535  0
             throw new InvalidElementException(e);
 536  3074
         }
 537  3074
         if (LOG.isDebugEnabled()) {
 538  0
             LOG.debug("Register ["
 539  
                     + " element:" + formatElement(modelElement)
 540  
                     + ", properties:" + formatArray(propertyNames)
 541  
                     + ", listener:" + listener
 542  
                     + "]");
 543  
         }
 544  3074
         synchronized (registrationMutex) {
 545  3074
             elements.register(listener, mofId, propertyNames);
 546  3074
         }
 547  3074
     }
 548  
 
 549  
     /**
 550  
      * Unregister a listener for a Model Event.
 551  
      */
 552  
     private void unregisterModelEvent(PropertyChangeListener listener,
 553  
             Object modelElement, String[] propertyNames) {
 554  1
         if (listener == null || modelElement == null) {
 555  0
             LOG.error("Attempt to unregister null listener(" + listener
 556  
                     + ") or modelElement (" + modelElement
 557  
                     + ")! [Property names: " + propertyNames + "]");
 558  0
             return;
 559  
         }
 560  1
         if (!(modelElement instanceof RefBaseObject)) {
 561  0
             LOG.error("Ignoring non-RefBaseObject received by "
 562  
                     + "unregisterModelEvent - " + modelElement);
 563  0
             return;
 564  
         }
 565  1
         String mofId = ((RefBaseObject) modelElement).refMofId();
 566  1
         if (LOG.isDebugEnabled()) {
 567  0
             LOG.debug("Unregister ["
 568  
                     + " element:" + formatElement(modelElement)
 569  
                     + ", properties:" + formatArray(propertyNames)
 570  
                     + ", listener:" + listener
 571  
                     + "]");
 572  
         }
 573  1
         synchronized (registrationMutex) {
 574  1
             elements.unregister(listener, mofId, propertyNames);
 575  1
         }
 576  1
     }
 577  
 
 578  
     /**
 579  
      * Register a listener for metamodel Class (and all its
 580  
      * subclasses), optionally qualified by a list of
 581  
      * property names.
 582  
      *
 583  
      * TODO: verify that property/event names are legal for
 584  
      * this class in the metamodel
 585  
      */
 586  
     private void registerClassEvent(PropertyChangeListener listener,
 587  
             Object modelClass, String[] propertyNames) {
 588  
 
 589  900
         if (modelClass instanceof Class) {
 590  900
             String className = getClassName(modelClass);
 591  900
             if (LOG.isDebugEnabled()) {
 592  0
                 LOG.debug("Register class ["
 593  
                         + modelImpl.getMetaTypes().getName(modelClass)
 594  
                         + "properties:" + formatArray(propertyNames)
 595  
                         + ", listener:" + listener + "]");
 596  
             }
 597  900
             Collection<String> subtypes = subtypeMap.get(className);
 598  900
             verifyAttributeNames(className, propertyNames);
 599  900
             synchronized (registrationMutex) {
 600  900
                 listenedClasses.register(listener, className, propertyNames);
 601  900
                 for (String subtype : subtypes) {
 602  90900
                     listenedClasses.register(listener, subtype, propertyNames);
 603  
                 }
 604  900
             }
 605  900
             return;
 606  
         }
 607  0
         throw new IllegalArgumentException(
 608  
                 "Don't know how to register class event for object "
 609  
                         + modelClass);
 610  
     }
 611  
 
 612  
 
 613  
     /**
 614  
      * Unregister a listener for a class and its subclasses.
 615  
      */
 616  
     private void unregisterClassEvent(PropertyChangeListener listener,
 617  
             Object modelClass, String[] propertyNames) {
 618  0
         if (modelClass instanceof Class) {
 619  0
             String className = getClassName(modelClass);
 620  0
             if (LOG.isDebugEnabled()) {
 621  0
                 LOG.debug("Unregister class [" + className
 622  
                         + ", properties:" + formatArray(propertyNames)
 623  
                         + ", listener:" + listener + "]");
 624  
             }
 625  0
             Collection<String> subtypes = subtypeMap.get(className);
 626  0
             synchronized (registrationMutex) {
 627  0
                 listenedClasses.unregister(listener, className, propertyNames);
 628  0
                 for (String subtype : subtypes) {
 629  0
                     listenedClasses.unregister(listener, subtype,
 630  
                             propertyNames);
 631  
                 }
 632  0
             }
 633  0
             return;
 634  
         }
 635  0
         throw new IllegalArgumentException(
 636  
                 "Don't know how to unregister class event for object "
 637  
                         + modelClass);
 638  
     }
 639  
 
 640  
     private String getClassName(Object elementOrClass) {
 641  3214
         return modelImpl.getMetaTypes().getName(elementOrClass);
 642  
     }
 643  
 
 644  
     /*
 645  
      * @see org.argouml.model.ModelEventPump#startPumpingEvents()
 646  
      */
 647  
     public void startPumpingEvents() {
 648  2836
         LOG.debug("Start pumping events");
 649  2836
         repository.addListener(this);
 650  2836
     }
 651  
 
 652  
     /*
 653  
      * @see org.argouml.model.ModelEventPump#stopPumpingEvents()
 654  
      */
 655  
     public void stopPumpingEvents() {
 656  1946
         LOG.debug("Stop pumping events");
 657  1946
         repository.removeListener(this);
 658  1946
     }
 659  
 
 660  
     /*
 661  
      * @see org.argouml.model.ModelEventPump#flushModelEvents()
 662  
      */
 663  
     public void flushModelEvents() {
 664  
         while (true) {
 665  156
             synchronized (eventCountMutex) {
 666  156
                 if (pendingEvents <= 0 
 667  
                         // Don't wait on ourselves, we'll deadlock!
 668  
                         // TODO: We might want to throw an exception here
 669  
                         || Thread.currentThread().equals(eventThread)) {
 670  156
                     return;
 671  
                 }
 672  
                 try {
 673  0
                     eventCountMutex.wait();
 674  0
                 } catch (InterruptedException e) {
 675  0
                     LOG.error("Interrupted while waiting in flushModelEvents");
 676  0
                 }
 677  0
             }
 678  
         }
 679  
 
 680  
     }
 681  
 
 682  
     /**
 683  
      * Get name of opposite end of association using
 684  
      * reflection on metamodel.
 685  
      */
 686  
     private String otherAssocEnd(AssociationEvent ae) {
 687  658
         RefAssociation ra = (RefAssociation) ae.getSource();
 688  658
         Association a = (Association) ra.refMetaObject();
 689  658
         AssociationEnd aend = null;
 690  
         try {
 691  658
             aend = (AssociationEnd) a.lookupElementExtended(ae.getEndName());
 692  0
         } catch (NameNotFoundException e) {
 693  0
             LOG.error("Failed to find other end of association : "
 694  
                     + ae.getSource() + " -> " + ae.getEndName());
 695  0
             return null;
 696  658
         }
 697  658
         return aend.otherEnd().getName();
 698  
     }
 699  
 
 700  
 
 701  
     /**
 702  
      * Map from UML 1.4 names to UML 1.3 names
 703  
      * expected by ArgoUML.<p>
 704  
      *
 705  
      * Note: It would have less performance impact to do the
 706  
      * mapping during listener registration, but ArgoUML
 707  
      * depends on the value in the event.
 708  
      */
 709  
     private static String mapPropertyName(String name) {
 710  
 
 711  
         // TODO: We don't want to do this once we have dropped UML1.3
 712  
         // Map UML 1.4 names to UML 1.3 equivalents
 713  658
         if ("typedParameter".equals(name)) {
 714  0
             return "parameter";
 715  
         }
 716  658
         if ("typedFeature".equals(name)) {
 717  0
             return "feature";
 718  
         }
 719  658
         return name;
 720  
     }
 721  
 
 722  
     /**
 723  
      * Formatters for debug output.
 724  
      */
 725  
     private String formatArray(String[] array) {
 726  0
         if (array == null) {
 727  0
             return null;
 728  
         }
 729  0
         String result = "[";
 730  0
         for (int i = 0; i < array.length; i++) {
 731  0
             result = result + array[i] + ", ";
 732  
         }
 733  0
         return result.substring(0, result.length() - 2) + "]";
 734  
     }
 735  
 
 736  
     private String formatElement(Object element) {
 737  
         try {
 738  0
             if (element instanceof RefBaseObject) {
 739  0
                 return modelImpl.getMetaTypes().getName(element)
 740  
                         + "<" + ((RefBaseObject) element).refMofId() + ">";
 741  0
             } else if (element != null) {
 742  0
                 return element.toString();
 743  
             }
 744  0
         } catch (InvalidObjectException e) {
 745  0
             return modelImpl.getMetaTypes().getName(element)
 746  
                     + "<deleted>";
 747  0
         }
 748  0
         return null;
 749  
     }
 750  
 
 751  
 
 752  
     /**
 753  
      * Traverse metamodel and build list of subtypes for every metatype.
 754  
      */
 755  
     private Map<String, Collection<String>> buildTypeMap(ModelPackage extent) {
 756  900
         Map<String, Collection<String>> names = 
 757  
             new HashMap<String, Collection<String>>();
 758  900
         for (Object metaclass : extent.getMofClass().refAllOfClass()) {
 759  108000
             ModelElement element = (ModelElement) metaclass;
 760  108000
             String name = element.getName();
 761  108000
             if (names.containsKey(name)) {
 762  0
                 LOG.error("Found duplicate class '" + name + "' in metamodel");
 763  
             } else {
 764  108000
                 names.put(name, getSubtypes(extent, element));
 765  
                 // LOG.debug(" Class " + name + " has subtypes : "
 766  
                 // + names.get(name));
 767  
             }
 768  108000
         }
 769  900
         return names;
 770  
     }
 771  
 
 772  
     /**
 773  
      * Recursive method to get all subtypes.
 774  
      * 
 775  
      * TODO: Does this have a scalability problem?
 776  
      */
 777  
     private Collection<String> getSubtypes(ModelPackage extent, 
 778  
             ModelElement me) {
 779  485100
         Collection<String> allSubtypes = new HashSet<String>();
 780  485100
         if (me instanceof GeneralizableElement) {
 781  485100
             GeneralizableElement ge = (GeneralizableElement) me;
 782  485100
             Collection<ModelElement> subtypes = extent.getGeneralizes()
 783  
                     .getSubtype(ge);
 784  485100
             for (ModelElement st : subtypes) {
 785  377100
                 allSubtypes.add(st.getName());
 786  377100
                 allSubtypes.addAll(getSubtypes(extent, st));
 787  
             }
 788  
         }
 789  485100
         return allSubtypes;
 790  
     }
 791  
 
 792  
     /**
 793  
      * Traverse metamodel and build list of names for all attributes and
 794  
      * reference ends.
 795  
      */
 796  
     private Map<String, Collection<String>> buildPropertyNameMap(
 797  
             ModelPackage extent) {
 798  900
         Map<String, Collection<String>> names =
 799  
                 new HashMap<String, Collection<String>>();
 800  900
         for (Reference reference : (Collection<Reference>) extent
 801  
                 .getReference().refAllOfClass()) {
 802  148500
             mapAssociationEnd(names, reference.getExposedEnd());
 803  148500
             mapAssociationEnd(names, reference.getReferencedEnd());
 804  
         }
 805  900
         for (Attribute attribute : (Collection<Attribute>) extent
 806  
                 .getAttribute().refAllOfClass()) {
 807  67500
             mapPropertyName(names, attribute.getContainer(),
 808  
                     attribute.getName());
 809  
         }
 810  900
         return names;
 811  
     }
 812  
 
 813  
     private void mapAssociationEnd(Map<String, Collection<String>> names,
 814  
             AssociationEnd end) {
 815  297000
         ModelElement type = end.otherEnd().getType();
 816  297000
         mapPropertyName(names, type, end.getName());
 817  297000
     }
 818  
 
 819  
     private boolean mapPropertyName(Map<String, Collection<String>> names,
 820  
             ModelElement type, String propertyName) {
 821  364500
         String typeName = type.getName();
 822  364500
         boolean added = mapPropertyName(names, typeName, propertyName);
 823  
 
 824  364500
         Collection<String> subtypes = subtypeMap.get(typeName);
 825  364500
         if (subtypes != null) {
 826  364500
             for (String subtype : subtypes) {
 827  3826800
                 added &= mapPropertyName(names, subtype, propertyName);
 828  
             }
 829  
         }
 830  
         
 831  364500
         return added;
 832  
     }
 833  
 
 834  
     private boolean mapPropertyName(Map<String, Collection<String>> names,
 835  
             String typeName, String propertyName) {
 836  4191300
         if (!names.containsKey(typeName)) {
 837  107100
             names.put(typeName, new HashSet<String>());
 838  
         }
 839  4191300
         boolean added = names.get(typeName).add(propertyName);
 840  4191300
         if (LOG.isDebugEnabled()) {
 841  0
             if (!added) {
 842  
                 // Because we map both ends of an association we'll see many
 843  
                 // names twice
 844  
 //                LOG.debug("Duplicate property name found - " + typeName + ":"
 845  
 //                        + propertyName);
 846  
             } else {
 847  0
                 LOG.debug("Added property name - " + typeName + ":"
 848  
                         + propertyName);
 849  
             }
 850  
         }
 851  4191300
         return added;
 852  
     }
 853  
     
 854  
     /**
 855  
      * Check whether given attribute names exist for this
 856  
      * metatype in the metamodel.  Throw exception if not found.
 857  
      */
 858  
     private void verifyAttributeNames(String className, String[] attributes) {
 859  
         // convert classname to RefObject
 860  900
         RefObject ro = null;
 861  900
         verifyAttributeNames(ro, attributes);
 862  900
     }
 863  
 
 864  
     /**
 865  
      * Check whether given attribute names exist for this
 866  
      * metatype in the metamodel.  Throw exception if not found.
 867  
      */
 868  
     private void verifyAttributeNames(RefObject metaobject,
 869  
             String[] attributes) {
 870  
         // Only do verification if debug level logging is on
 871  
         // TODO: Should we leave this on always? - tfm
 872  3974
         if (LOG.isDebugEnabled()) {
 873  0
             if (metaobject == null || attributes == null) {
 874  0
                 return;
 875  
             }
 876  
             // If we don't have a MofClass, see if we can get one from the
 877  
             // instance
 878  0
             if (!(metaobject instanceof MofClass)) {
 879  0
                 metaobject = metaobject.refMetaObject();
 880  
             }
 881  
 
 882  
             // If we still don't have a MofClass, something's wrong
 883  0
             if (!(metaobject instanceof MofClass)) {
 884  0
                 throw new IllegalArgumentException(
 885  
                         "Argument must be MofClass or instance of MofClass");
 886  
             }
 887  
 
 888  0
             MofClass metaclass = (MofClass) metaobject;
 889  0
             Collection<String> names = propertyNameMap.get(metaclass.getName());
 890  0
             if (names == null) {
 891  0
                 names = Collections.emptySet();
 892  
             }
 893  
 
 894  0
             for (String attribute : attributes) {
 895  0
                 if (!names.contains(attribute) 
 896  
                         && !"remove".equals(attribute)) {
 897  
 
 898  
                     // TODO: We also have code registering for the names of
 899  
                     // a tagged value like "derived"
 900  0
                     LOG.error("Property '" + attribute
 901  
                              + "' for class '"
 902  
                              + metaclass.getName()
 903  
                              + "' doesn't exist in metamodel");
 904  
 //                    throw new IllegalArgumentException("Property '"
 905  
 //                            + attribute + "' doesn't exist in metamodel");
 906  
                 }
 907  
             }
 908  
         }
 909  3974
     }
 910  
     
 911  
 
 912  
     @SuppressWarnings("unchecked")
 913  
     public List getDebugInfo() {
 914  0
         List info = new ArrayList();
 915  0
         info.add("Event Listeners");
 916  0
         for (Iterator it = elements.registry.entrySet().iterator(); 
 917  0
                 it.hasNext(); ) {
 918  0
             Map.Entry entry = (Map.Entry) it.next();
 919  0
             String item = entry.getKey().toString();
 920  0
             List modelElementNode = newDebugNode(getDebugDescription(item));
 921  0
             info.add(modelElementNode);
 922  0
             Map propertyMap = (Map) entry.getValue();
 923  0
             for (Iterator propertyIterator = propertyMap.entrySet().iterator(); 
 924  0
                     propertyIterator.hasNext();) {
 925  0
                 Map.Entry propertyEntry = (Map.Entry) propertyIterator.next();
 926  0
                 List propertyNode =
 927  
                     newDebugNode(propertyEntry.getKey().toString());
 928  0
                 modelElementNode.add(propertyNode);
 929  
 
 930  0
                 List listenerList = (List) propertyEntry.getValue();
 931  0
                 for (Iterator listIt = listenerList.iterator();
 932  0
                         listIt.hasNext(); ) {
 933  0
                     Object listener = listIt.next();
 934  0
                     List listenerNode =
 935  
                         newDebugNode(
 936  
                                 listener.getClass().getName());
 937  0
                     propertyNode.add(listenerNode);
 938  0
                 }
 939  0
             }
 940  0
         }
 941  
 
 942  0
         return info;
 943  
     }
 944  
 
 945  
     private List<String> newDebugNode(String name) {
 946  0
         List<String> list = new ArrayList<String>();
 947  0
         list.add(name);
 948  0
         return list;
 949  
     }
 950  
     
 951  
     private String getDebugDescription(String mofId) {
 952  0
         Object modelElement = repository.getByMofId(mofId);
 953  0
         String name = Model.getFacade().getName(modelElement);
 954  0
         if (name != null && name.trim().length() != 0) {
 955  0
             return "\"" + name + "\" - " + modelElement.toString();
 956  
         } else {
 957  0
             return modelElement.toString();
 958  
         }
 959  
     }
 960  
 
 961  
 }
 962  
 
 963  
 
 964  
 /**
 965  
  * A simple typed registry which supports two levels of string keys.
 966  
  * 
 967  
  * @param <T> type of object to be registered
 968  
  * @author Tom Morris
 969  
  */
 970  
 class Registry<T> {
 971  
     
 972  900
     private static final Logger LOG = Logger.getLogger(Registry.class);
 973  
     
 974  
     Map<String, Map<String, List<T>>> registry;
 975  
     
 976  
     /**
 977  
      * Construct a new registry for the given type of object.
 978  
      */
 979  1800
     Registry() {
 980  1800
         registry = Collections
 981  
                 .synchronizedMap(new HashMap<String, Map<String, List<T>>>());
 982  1800
     }
 983  
 
 984  
     /**
 985  
      * Register an object with given keys(s) in the registry. The object is
 986  
      * registered in multiple locations for quick lookup. During matching an
 987  
      * object registered without subkeys will match any subkey. Multiple calls
 988  
      * with the same item and key pair will only result in a single registration
 989  
      * being made.
 990  
      * 
 991  
      * @param item object to be registered
 992  
      * @param key primary key for registration
 993  
      * @param subkeys array of subkeys. If null, register under primary key
 994  
      *                only. The special value of the empty string ("") must not
 995  
      *                be used as a subkey by the caller.
 996  
      */
 997  
     void register(T item, String key,
 998  
             String[] subkeys) {
 999  
 
 1000  
         // Lookup primary key, creating new entry if needed
 1001  94874
         Map<String, List<T>> entry = registry.get(key);
 1002  94874
         if (entry == null) {
 1003  92864
             entry = new HashMap<String, List<T>>();
 1004  92864
             registry.put(key, entry);
 1005  
         }
 1006  
 
 1007  
         // If there are no subkeys, register using our special value
 1008  
         // to indicate that this is a primary key only registration
 1009  94874
         if (subkeys == null || subkeys.length < 1) {
 1010  92700
             subkeys =
 1011  
                 new String[] {
 1012  
                     "",
 1013  
                 };
 1014  
         }
 1015  
 
 1016  189806
         for (int i = 0; i < subkeys.length; i++) {
 1017  94932
             List<T> list = entry.get(subkeys[i]);
 1018  94932
             if (list == null) {
 1019  93822
                 list = new ArrayList<T>();
 1020  93822
                 entry.put(subkeys[i], list);
 1021  
             }
 1022  94932
             if (!list.contains(item)) {
 1023  94932
                 list.add(item);
 1024  
             } else {
 1025  0
                 if (LOG.isDebugEnabled()) {
 1026  0
                     LOG.debug("Duplicate registration attempt for " + key + ":"
 1027  
                             + subkeys + " Listener: " + item);
 1028  
                 }
 1029  
             }
 1030  
         }
 1031  94874
     }
 1032  
 
 1033  
     /**
 1034  
      * Unregister an item or all items which match key set.
 1035  
      *
 1036  
      * @param item object to be unregistered.  If null, unregister all
 1037  
      * matching objects.
 1038  
      * @param key primary key for registration
 1039  
      * @param subkeys array of subkeys.  If null, unregister under primary
 1040  
      * key only.
 1041  
      */
 1042  
     void unregister(T item, String key, String[] subkeys) {
 1043  3
         Map<String, List<T>> entry = registry.get(key);
 1044  3
         if (entry == null) {
 1045  1
             return;
 1046  
         }
 1047  
 
 1048  2
         if (subkeys != null && subkeys.length > 0) {
 1049  3
             for (int i = 0; i < subkeys.length; i++) {
 1050  2
                 lookupRemoveItem(entry, subkeys[i], item);
 1051  
             }
 1052  
         } else {
 1053  1
             if (item == null) {
 1054  1
                 registry.remove(key);
 1055  
             } else {
 1056  0
                 lookupRemoveItem(entry, "", item);
 1057  
             }
 1058  
         }
 1059  2
     }
 1060  
 
 1061  
     private void lookupRemoveItem(Map<String, List<T>> map, String key, 
 1062  
             T item) {
 1063  2
         List<T> list = map.get(key);
 1064  2
         if (list == null) {
 1065  0
             return;
 1066  
         }
 1067  2
         if (item == null) {
 1068  0
             map.remove(key);
 1069  0
             return;
 1070  
         }
 1071  2
         if (LOG.isDebugEnabled()) {
 1072  0
             if (!list.contains(item)) {
 1073  0
                 LOG.debug("Attempt to unregister non-existant registration"
 1074  
                         + key + " Listener: " + item);
 1075  
             }
 1076  
         }
 1077  4
         while (list.contains(item)) {
 1078  2
             list.remove(item);
 1079  
         }
 1080  2
         if (list.isEmpty()) {
 1081  2
             map.remove(key);
 1082  
         }
 1083  2
     }
 1084  
 
 1085  
     /**
 1086  
      * Return a list of items which have been registered for given key(s).
 1087  
      * Returns items registered both for the key/subkey pair as well as
 1088  
      * those registered just for the primary key.
 1089  
      * @param key
 1090  
      * @param subkey
 1091  
      * @return collection of items previously registered.
 1092  
      */
 1093  
     Collection<T> getMatches(String key, String subkey) {
 1094  4628
         List<T> results = new ArrayList<T>();
 1095  4628
         Map<String, List<T>> entry = registry.get(key);
 1096  4628
         if (entry != null) {
 1097  2551
             if (entry.containsKey(subkey)) {
 1098  1
                 results.addAll(entry.get(subkey));
 1099  
             }
 1100  2551
             if (entry.containsKey("")) {
 1101  2546
                 results.addAll(entry.get(""));
 1102  
             }
 1103  
         }
 1104  4628
         return results;
 1105  
     }
 1106  
 
 1107  
 }