// $Id: NotationProvider.java 16400 2008-12-21 16:27:58Z tfmorris $ // Copyright (c) 2005-2007 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph appear in all copies. This software program and // documentation are copyrighted by The Regents of the University of // California. The software program and documentation are supplied "AS // IS", without any accompanying services from The Regents. The Regents // does not warrant that the operation of the program will be // uninterrupted or error-free. The end-user understands that the program // was developed for research purposes and is advised not to rely // exclusively on the program for any reason. IN NO EVENT SHALL THE // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. package org.argouml.notation; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Map; import org.apache.log4j.Logger; import org.argouml.model.Model; /** * A class that implements this abstract class manages a text * shown on a diagram. This means it is able to generate * text that represents one or more UML objects. * And when the user has edited this text, the model may be adapted * by parsing the text. * Additionally, a help text for the parsing is provided, * so that the user knows the syntax. * * @author mvw@tigris.org */ public abstract class NotationProvider { private static final Logger LOG = Logger.getLogger(NotationProvider.class); private static final String LIST_SEPARATOR = ", "; /** * A collection of properties of listeners registered for this notation. * Each entry is a 2 element array containing the element and the property * name(s) for which a listener is registered. This facilitates easy removal * of a complex set of listeners. */ private final Collection<Object[]> listeners = new ArrayList<Object[]>(); /** * @return a i18 key that represents a help string * giving an explanation to the user of the syntax */ public abstract String getParsingHelp(); /** * Utility function to determine the presence of a key. * The default is false. * * @param key the string for the key * @param map the Map to check for the presence * and value of the key * @return true if the value for the key is true, otherwise false */ public static boolean isValue(final String key, final Map map) { if (map == null) { return false; } Object o = map.get(key); if (!(o instanceof Boolean)) { return false; } return ((Boolean) o).booleanValue(); } /** * Parses the given text, and adapts the modelElement and * maybe related elements accordingly. * * @param modelElement the modelelement to adapt * @param text the string given by the user to be parsed * to adapt the model */ public abstract void parse(Object modelElement, String text); /** * Generates a string representation for the given model element. * * @param modelElement the base UML modelelement * @param args arguments that may determine the notation * @return the string written in the correct notation * @deprecated for 0.27.3 by tfmorris. Use * {@link #toString(Object, NotationSettings)}. */ @Deprecated public abstract String toString(Object modelElement, Map args); /** * Generate a string representation for the given model element. * <p> * <em>WARNING:</em> All subclasses must implement this as if it were * abstract. It will become abstract in a future release after the * deprecation period for toString(Object, Map) expires. * * @param modelElement the base UML element * @param settings settings that control rendering of the text * @return the string written in the correct notation */ public String toString(Object modelElement, NotationSettings settings) { return toString(modelElement, Collections.emptyMap()); } /** * Initialise the appropriate model change listeners * for the given modelelement to the given listener. * Overrule this when you need more than * listening to all events from the base modelelement. * * @param listener the given listener * @param modelElement the modelelement that we provide * notation for */ public void initialiseListener(PropertyChangeListener listener, Object modelElement) { addElementListener(listener, modelElement); } /** * Clean out the listeners registered before. * The default implementation is to remove all listeners * that were remembered by the utility functions below. * * @param listener the given listener * @param modelElement the modelelement that we provide * notation for */ public void cleanListener(final PropertyChangeListener listener, final Object modelElement) { removeAllElementListeners(listener); } /** * Update the set of listeners based on the given event. <p> * * The default implementation just removes all listeners, and then * re-initialises completely - this is method 1. * A more efficient way would be to dissect * the propertyChangeEvent, and only adapt the listeners * that need to be adapted - this is method 2. <p> * * Method 2 is explained by the code below that is commented out. * Method 1 is the easiest to implement, since at every arrival of an event, * we just remove all old listeners, and then inspect the current model, * and add listeners where we need them. I.e. the advantage is * that we only need to traverse the model structure in one location, i.e. * the initialiseListener() method. * * @param listener the given listener * @param modelElement the modelelement that we provide * notation for * @param pce the received event, that we base the changes on */ public void updateListener(final PropertyChangeListener listener, Object modelElement, PropertyChangeEvent pce) { // e.g. for an operation: // if pce.getSource() == modelElement // && event.propertyName = "parameter" // if event instanceof AddAssociationEvent // Get the parameter instance from event.newValue // Call model to add listener on parameter on change // of "name", "type" // else if event instanceof RemoveAssociationEvent // Get the parameter instance from event.oldValue // Call model to remove listener on parameter on change // of "name", "type" // end if // end if if (Model.getUmlFactory().isRemoved(modelElement)) { LOG.warn("Encountered deleted object during delete of " + modelElement); return; } cleanListener(listener, modelElement); initialiseListener(listener, modelElement); } /* * Add an element listener and remember the registration. * * @param element * element to listen for changes on * @see org.argouml.model.ModelEventPump#addModelEventListener(PropertyChangeListener, Object, String) */ protected final void addElementListener(PropertyChangeListener listener, Object element) { if (Model.getUmlFactory().isRemoved(element)) { LOG.warn("Encountered deleted object during delete of " + element); return; } Object[] entry = new Object[] {element, null}; if (!listeners.contains(entry)) { listeners.add(entry); Model.getPump().addModelEventListener(listener, element); } else { LOG.warn("Attempted duplicate registration of event listener" + " - Element: " + element + " Listener: " + listener); } } /* * Utility function to add a listener for a given property name * and remember the registration. * * @param element * element to listen for changes on * @param property * name of property to listen for changes of * @see org.argouml.model.ModelEventPump#addModelEventListener(PropertyChangeListener, * Object, String) */ protected final void addElementListener(PropertyChangeListener listener, Object element, String property) { if (Model.getUmlFactory().isRemoved(element)) { LOG.warn("Encountered deleted object during delete of " + element); return; } Object[] entry = new Object[] {element, property}; if (!listeners.contains(entry)) { listeners.add(entry); Model.getPump().addModelEventListener(listener, element, property); } else { LOG.debug("Attempted duplicate registration of event listener" + " - Element: " + element + " Listener: " + listener); } } /* * Utility function to add a listener for an array of property names * and remember the registration. * * @param element * element to listen for changes on * @param property * array of property names (Strings) to listen for changes of * @see org.argouml.model.ModelEventPump#addModelEventListener(PropertyChangeListener, * Object, String) */ protected final void addElementListener(PropertyChangeListener listener, Object element, String[] property) { if (Model.getUmlFactory().isRemoved(element)) { LOG.warn("Encountered deleted object during delete of " + element); return; } Object[] entry = new Object[] {element, property}; if (!listeners.contains(entry)) { listeners.add(entry); Model.getPump().addModelEventListener(listener, element, property); } else { LOG.debug("Attempted duplicate registration of event listener" + " - Element: " + element + " Listener: " + listener); } } /* * Utility function to remove an element listener * and adapt the remembered list of registration. * * @param element * element to listen for changes on * @see org.argouml.model.ModelEventPump#addModelEventListener(PropertyChangeListener, Object, String) */ protected final void removeElementListener(PropertyChangeListener listener, Object element) { listeners.remove(new Object[] {element, null}); Model.getPump().removeModelEventListener(listener, element); } /* * Utility function to unregister all listeners * registered through addElementListener. * * @see #addElementListener(Object, String) */ protected final void removeAllElementListeners( PropertyChangeListener listener) { for (Object[] lis : listeners) { Object property = lis[1]; if (property == null) { Model.getPump().removeModelEventListener(listener, lis[0]); } else if (property instanceof String[]) { Model.getPump().removeModelEventListener(listener, lis[0], (String[]) property); } else if (property instanceof String) { Model.getPump().removeModelEventListener(listener, lis[0], (String) property); } else { throw new RuntimeException( "Internal error in removeAllElementListeners"); } } listeners.clear(); } protected StringBuilder formatNameList(Collection modelElements) { return formatNameList(modelElements, LIST_SEPARATOR); } protected StringBuilder formatNameList(Collection modelElements, String separator) { StringBuilder result = new StringBuilder(); for (Object element : modelElements) { String name = Model.getFacade().getName(element); // TODO: Any special handling for null names? append will use "null" result.append(name).append(separator); } if (result.length() >= separator.length()) { result.delete(result.length() - separator.length(), result.length()); } return result; } }