/* $Id: ArgoEventPump.java 17749 2010-01-11 18:49:17Z linus $
*******************************************************************************
* Copyright (c) 2009 Contributors - see below
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* tfmorris
*******************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 1996-2008 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.application.events;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingUtilities;
import org.apache.log4j.Logger;
import org.argouml.application.api.ArgoEventListener;
/**
* ArgoEventPump is an event dispatcher which handles events that are global
* in nature for the entire application.
* <p>
* TODO: DiagramAppearance and Notation events are not application-wide and will
* be moved from here to someplace more specific in the future so that they can
* be managed on a per-project or per-diagram basis.
*/
public final class ArgoEventPump {
/**
* Logger.
*/
private static final Logger LOG = Logger.getLogger(ArgoEventPump.class);
/**
* <code>listeners</code> contains the list of register listeners.
*
* It is a list of {@link Pair}.
*/
private List<Pair> listeners;
/**
* The singleton.
*/
static final ArgoEventPump SINGLETON = new ArgoEventPump();
/**
* @return the singleton
*/
public static ArgoEventPump getInstance() {
return SINGLETON;
}
/**
* Constructor.
*/
private ArgoEventPump() {
}
/**
* @param listener The listener to be added.
*/
public static void addListener(ArgoEventListener listener) {
SINGLETON.doAddListener(ArgoEventTypes.ANY_EVENT, listener);
}
/**
* @param event the event-type to what the listener will listen
* @param listener the listener to be added
*/
public static void addListener(int event, ArgoEventListener listener) {
SINGLETON.doAddListener(event, listener);
}
/**
* @param listener the listener to be removed
*/
public static void removeListener(ArgoEventListener listener) {
SINGLETON.doRemoveListener(ArgoEventTypes.ANY_EVENT, listener);
}
/**
* @param event the event to which the listener will not listen any more
* @param listener the listener to be removed
*/
public static void removeListener(int event, ArgoEventListener listener) {
SINGLETON.doRemoveListener(event, listener);
}
/**
* @param event the event to what the listener will listen (?)
* @param listener the listener to be added
*/
protected void doAddListener(int event, ArgoEventListener listener) {
if (listeners == null) {
listeners = new ArrayList<Pair>();
}
synchronized (listeners) {
listeners.add(new Pair(event, listener));
}
}
/**
* Removes a listener, eventtype pair from the listener list.
*
* TODO: replace the listener implementation with a EventListenerList
* for better performance
*
* @param event the event to which the listener will not listen any more
* @param listener the listener to be removed
*/
protected void doRemoveListener(int event, ArgoEventListener listener) {
if (listeners == null) {
return;
}
synchronized (listeners) {
List<Pair> removeList = new ArrayList<Pair>();
if (event == ArgoEventTypes.ANY_EVENT) {
// TODO: This is a linear search of a list that contain many
// thousands of items (one for every Fig in the entire project)
for (Pair p : listeners) {
if (p.listener == listener) {
removeList.add(p);
}
}
} else {
Pair test = new Pair(event, listener);
// TODO: This is a linear search of a list that contain many
// thousands of items (one for every Fig in the entire project)
for (Pair p : listeners) {
if (p.equals(test)) {
removeList.add(p);
}
}
}
listeners.removeAll(removeList);
}
}
/**
* Handle firing a notation event.
* <p>
* TODO: This needs to be managed on a per-diagram or per-project basis.
*
* @param event The event to be fired.
* @param listener The listener.
*/
private void handleFireNotationEvent(
final ArgoNotationEvent event,
final ArgoNotationEventListener listener) {
// Notation events are likely to cause GEF/Swing operations, so we
// dispatch them on the Swing event thread as a convenience so that
// the receiving notationChanged() methods don't need to deal with it
if (SwingUtilities.isEventDispatchThread()) {
fireNotationEventInternal(event, listener);
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
fireNotationEventInternal(event, listener);
}
});
}
}
private void fireNotationEventInternal(ArgoNotationEvent event,
ArgoNotationEventListener listener) {
switch (event.getEventType()) {
case ArgoEventTypes.NOTATION_CHANGED :
listener.notationChanged(event);
/* Remark: The code in
* ProjectSettings.init() currently presumes
* that nobody is using this event. */
break;
case ArgoEventTypes.NOTATION_ADDED :
listener.notationAdded(event);
break;
case ArgoEventTypes.NOTATION_REMOVED :
listener.notationRemoved(event);
break;
case ArgoEventTypes.NOTATION_PROVIDER_ADDED :
listener.notationProviderAdded(event);
break;
case ArgoEventTypes.NOTATION_PROVIDER_REMOVED :
listener.notationProviderRemoved(event);
break;
default :
LOG.error("Invalid event:" + event.getEventType());
break;
}
}
/**
* Handle firing a diagram appearance event.
* <p>
* TODO: This needs to be managed on a per-diagram or per-project basis.
*
* @param event The event to be fired.
* @param listener The listener.
*/
private void handleFireDiagramAppearanceEvent(
final ArgoDiagramAppearanceEvent event,
final ArgoDiagramAppearanceEventListener listener) {
if (SwingUtilities.isEventDispatchThread()) {
fireDiagramAppearanceEventInternal(event, listener);
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
fireDiagramAppearanceEventInternal(event, listener);
}
});
}
}
private void fireDiagramAppearanceEventInternal(
final ArgoDiagramAppearanceEvent event,
final ArgoDiagramAppearanceEventListener listener) {
switch (event.getEventType()) {
case ArgoEventTypes.DIAGRAM_FONT_CHANGED :
listener.diagramFontChanged(event);
break;
default :
LOG.error("Invalid event:" + event.getEventType());
break;
}
}
/**
* Handle firing a help text event.
*
* @param event The event to be fired.
* @param listener The listener.
*/
private void handleFireHelpEvent(
ArgoHelpEvent event,
ArgoHelpEventListener listener) {
switch (event.getEventType()) {
case ArgoEventTypes.HELP_CHANGED :
listener.helpChanged(event);
break;
case ArgoEventTypes.HELP_REMOVED :
listener.helpRemoved(event);
break;
default :
LOG.error("Invalid event:" + event.getEventType());
break;
}
}
/**
* Handle firing a status text event.
*
* @param event The event to be fired.
* @param listener The listener.
*/
private void handleFireStatusEvent(
ArgoStatusEvent event,
ArgoStatusEventListener listener) {
switch (event.getEventType()) {
case ArgoEventTypes.STATUS_TEXT :
listener.statusText(event);
break;
case ArgoEventTypes.STATUS_CLEARED :
listener.statusCleared(event);
break;
case ArgoEventTypes.STATUS_PROJECT_SAVED :
listener.projectSaved(event);
break;
case ArgoEventTypes.STATUS_PROJECT_LOADED :
listener.projectLoaded(event);
break;
case ArgoEventTypes.STATUS_PROJECT_MODIFIED :
listener.projectModified(event);
break;
default :
LOG.error("Invalid event:" + event.getEventType());
break;
}
}
/**
* Handle firing a profile event.
*
* @param event The event to be fired.
* @param listener The listener.
*/
private void handleFireProfileEvent(
ArgoProfileEvent event,
ArgoProfileEventListener listener) {
switch (event.getEventType()) {
case ArgoEventTypes.PROFILE_ADDED:
listener.profileAdded(event);
break;
case ArgoEventTypes.PROFILE_REMOVED:
listener.profileRemoved(event);
break;
default:
LOG.error("Invalid event:" + event.getEventType());
break;
}
}
/**
* Handle firing a generator event.
*
* @param event The event to be fired.
* @param listener The listener.
*/
private void handleFireGeneratorEvent(
ArgoGeneratorEvent event,
ArgoGeneratorEventListener listener) {
switch (event.getEventType()) {
case ArgoEventTypes.GENERATOR_CHANGED:
listener.generatorChanged(event);
break;
case ArgoEventTypes.GENERATOR_ADDED:
listener.generatorAdded(event);
break;
case ArgoEventTypes.GENERATOR_REMOVED:
listener.generatorRemoved(event);
break;
default:
LOG.error("Invalid event:" + event.getEventType());
break;
}
}
private void handleFireEvent(ArgoEvent event, ArgoEventListener listener) {
if (event.getEventType() == ArgoEventTypes.ANY_EVENT) {
if (listener instanceof ArgoNotationEventListener) {
handleFireNotationEvent((ArgoNotationEvent) event,
(ArgoNotationEventListener) listener);
}
if (listener instanceof ArgoHelpEventListener) {
handleFireHelpEvent((ArgoHelpEvent) event,
(ArgoHelpEventListener) listener);
}
if (listener instanceof ArgoStatusEventListener) {
handleFireStatusEvent((ArgoStatusEvent) event,
(ArgoStatusEventListener) listener);
}
} else {
if (event.getEventType() >= ArgoEventTypes.ANY_NOTATION_EVENT
&& event.getEventType() < ArgoEventTypes.LAST_NOTATION_EVENT) {
if (listener instanceof ArgoNotationEventListener) {
handleFireNotationEvent((ArgoNotationEvent) event,
(ArgoNotationEventListener) listener);
}
}
if (event.getEventType() >= ArgoEventTypes
.ANY_DIAGRAM_APPEARANCE_EVENT
&& event.getEventType() < ArgoEventTypes
.LAST_DIAGRAM_APPEARANCE_EVENT) {
if (listener instanceof ArgoDiagramAppearanceEventListener) {
handleFireDiagramAppearanceEvent(
(ArgoDiagramAppearanceEvent) event,
(ArgoDiagramAppearanceEventListener) listener);
}
}
if (event.getEventType() >= ArgoEventTypes.ANY_HELP_EVENT
&& event.getEventType() < ArgoEventTypes.LAST_HELP_EVENT) {
if (listener instanceof ArgoHelpEventListener) {
handleFireHelpEvent((ArgoHelpEvent) event,
(ArgoHelpEventListener) listener);
}
}
if (event.getEventType() >= ArgoEventTypes.ANY_GENERATOR_EVENT
&& event.getEventType() < ArgoEventTypes.LAST_GENERATOR_EVENT) {
if (listener instanceof ArgoGeneratorEventListener) {
handleFireGeneratorEvent((ArgoGeneratorEvent) event,
(ArgoGeneratorEventListener) listener);
}
}
if (event.getEventType() >= ArgoEventTypes.ANY_STATUS_EVENT
&& event.getEventType() < ArgoEventTypes
.LAST_STATUS_EVENT) {
if (listener instanceof ArgoStatusEventListener) {
handleFireStatusEvent((ArgoStatusEvent) event,
(ArgoStatusEventListener) listener);
}
}
if (event.getEventType() >= ArgoEventTypes.ANY_PROFILE_EVENT
&& event.getEventType() < ArgoEventTypes
.LAST_PROFILE_EVENT) {
if (listener instanceof ArgoProfileEventListener) {
handleFireProfileEvent((ArgoProfileEvent) event,
(ArgoProfileEventListener) listener);
}
}
}
}
/**
* @param event the event to be fired
*/
public static void fireEvent(ArgoEvent event) {
SINGLETON.doFireEvent(event);
}
/**
* @param event the event to be fired
*/
protected void doFireEvent(ArgoEvent event) {
if (listeners == null) {
return;
}
// Make a read-only copy of the listeners list so that reentrant calls
// back to add/removeListener won't mess us up.
// TODO: Potential performance issue, but we need the correctness - tfm
List<Pair> readOnlyListeners;
synchronized (listeners) {
readOnlyListeners = new ArrayList<Pair>(listeners);
}
for (Pair pair : readOnlyListeners) {
if (pair.getEventType() == ArgoEventTypes.ANY_EVENT) {
handleFireEvent(event, pair.getListener());
} else if (pair.getEventType() == event.getEventStartRange()
|| pair.getEventType() == event.getEventType()) {
handleFireEvent(event, pair.getListener());
}
}
}
/**
* Data structure handling listener registrations.
*/
static class Pair {
private int eventType;
private ArgoEventListener listener;
/**
* Constructor.
*
* @param myEventType The event type.
* @param myListener The listener.
*/
Pair(int myEventType, ArgoEventListener myListener) {
eventType = myEventType;
listener = myListener;
}
/**
* @return The event type.
*/
int getEventType() {
return eventType;
}
/**
* @return The listener.
*/
ArgoEventListener getListener() {
return listener;
}
@Override
public String toString() {
return "{Pair(" + eventType + "," + listener + ")}";
}
@Override
public int hashCode() {
if (listener != null) {
return eventType + listener.hashCode();
}
return eventType;
}
@Override
public boolean equals(Object o) {
if (o instanceof Pair) {
Pair p = (Pair) o;
if (p.eventType == eventType && p.listener == listener) {
return true;
}
}
return false;
}
}
}