Merge remote-tracking branch 'origin/GT-3360-dragonmacher-event-manager-synchronization'

This commit is contained in:
ghidravore
2019-11-29 17:34:05 -05:00
@@ -15,38 +15,39 @@
*/ */
package ghidra.framework.plugintool.mgr; package ghidra.framework.plugintool.mgr;
import java.lang.reflect.InvocationTargetException;
import java.util.*; import java.util.*;
import javax.swing.SwingUtilities; import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.map.LazyMap;
import ghidra.framework.model.ToolListener; import ghidra.framework.model.ToolListener;
import ghidra.framework.plugintool.PluginEvent; import ghidra.framework.plugintool.PluginEvent;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginEventListener; import ghidra.framework.plugintool.util.PluginEventListener;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.Swing;
/** /**
* Helper class to manage the events that plugins consume and produce. * Helper class to manage the events that plugins consume and produce. This class keeps
* This class keeps track of the last events that went out so that when * track of the last events that went out so that when a plugin is added, it receives those events.
* a plugin is added, it receives those events.
*
*/ */
public class EventManager { public class EventManager {
private ArrayList<ToolListener> toolListeners = new ArrayList<>(); private List<ToolListener> toolListeners = new ArrayList<>();
private HashMap<Class<? extends PluginEvent>, Set<PluginEventListener>> pluginListenerMap = private Map<Class<? extends PluginEvent>, Set<PluginEventListener>> listenersByEventType =
new HashMap<>(); LazyMap.lazyMap(new HashMap<>(), clazz -> new HashSet<>());
private HashMap<String, Counter> producerMap = new HashMap<>(); private Map<String, Counter> producerMap =
private HashMap<String, Counter> consumerMap = new HashMap<>(); LazyMap.lazyMap(new HashMap<>(), name -> new Counter());
private LinkedHashMap<Class<? extends PluginEvent>, PluginEvent> lastEvents = private Map<String, Counter> consumerMap =
LazyMap.lazyMap(new HashMap<>(), name -> new Counter());
private LinkedHashMap<Class<? extends PluginEvent>, PluginEvent> lastEventsByType =
new LinkedHashMap<>(); new LinkedHashMap<>();
private LinkedList<PluginEvent> eventQ = new LinkedList<>(); private LinkedList<PluginEvent> eventQ = new LinkedList<>();
private Set<PluginEventListener> allEventListeners = new HashSet<>(); private Set<PluginEventListener> allEventListeners = new HashSet<>();
private PluginEvent currentEvent;
private Runnable sendEventsRunnable;
private PluginTool tool; private PluginTool tool;
private boolean sendingToolEvent; private PluginEvent currentEvent;
private final Runnable sendEventsRunnable = () -> sendEvents();
private volatile boolean sendingToolEvent;
/** /**
* Construct a new EventManager. * Construct a new EventManager.
@@ -54,8 +55,6 @@ public class EventManager {
*/ */
public EventManager(PluginTool tool) { public EventManager(PluginTool tool) {
this.tool = tool; this.tool = tool;
sendEventsRunnable = () -> sendEvents();
} }
/** /**
@@ -66,18 +65,11 @@ public class EventManager {
*/ */
public void addEventListener(Class<? extends PluginEvent> eventClass, public void addEventListener(Class<? extends PluginEvent> eventClass,
PluginEventListener listener) { PluginEventListener listener) {
Set<PluginEventListener> set = pluginListenerMap.get(eventClass); Set<PluginEventListener> set = listenersByEventType.get(eventClass);
if (set == null) { if (set.isEmpty()) {
set = new HashSet<>();
pluginListenerMap.put(eventClass, set);
String name = PluginEvent.lookupToolEventName(eventClass); String name = PluginEvent.lookupToolEventName(eventClass);
if (name != null) { if (name != null) {
Counter counter = consumerMap.get(name); consumerMap.get(name).count++;
if (counter == null) {
counter = new Counter();
consumerMap.put(name, counter);
}
counter.count++;
} }
} }
set.add(listener); set.add(listener);
@@ -99,25 +91,27 @@ public class EventManager {
*/ */
public void removeEventListener(Class<? extends PluginEvent> eventClass, public void removeEventListener(Class<? extends PluginEvent> eventClass,
PluginEventListener listener) { PluginEventListener listener) {
Set<PluginEventListener> set = pluginListenerMap.get(eventClass); Set<PluginEventListener> set = listenersByEventType.get(eventClass);
if (set != null) {
set.remove(listener); set.remove(listener);
if (set.size() == 0) { if (set.isEmpty()) {
pluginListenerMap.remove(eventClass); eventConsumerRemoved(eventClass);
}
}
private void eventConsumerRemoved(Class<? extends PluginEvent> eventClass) {
String name = PluginEvent.lookupToolEventName(eventClass); String name = PluginEvent.lookupToolEventName(eventClass);
if (name != null) { if (name == null) {
return;
}
Counter counter = consumerMap.get(name); Counter counter = consumerMap.get(name);
if (counter != null && --counter.count == 0) { if (--counter.count == 0) {
consumerMap.remove(name); consumerMap.remove(name);
} }
} }
}
}
}
/** /**
* Add the given tool listener to a list of tool listeners notified * Add the given tool listener to be notified notified when tool events are generated
* when tool events are generated.
* @param listener listener to add * @param listener listener to add
*/ */
public void addToolListener(ToolListener listener) { public void addToolListener(ToolListener listener) {
@@ -125,7 +119,7 @@ public class EventManager {
} }
/** /**
* Remove the given tool listener from the list of tool listeners. * Remove the given tool listener from the list of tool listeners
* @param listener listener to remove * @param listener listener to remove
*/ */
public void removeToolListener(ToolListener listener) { public void removeToolListener(ToolListener listener) {
@@ -133,25 +127,22 @@ public class EventManager {
} }
/** /**
* Return whether there are any registered tool listeners for the * Return whether there are any registered tool listeners for the tool associated with class
* tool associated with this EventManager. *
* @return true if there are any listeners
*/ */
public boolean hasToolListeners() { public boolean hasToolListeners() {
return !toolListeners.isEmpty(); return !toolListeners.isEmpty();
} }
/** /**
* Add the class for the PluginEvent that a plugin will produce. * Add the class for the PluginEvent that a plugin will produce
* @param eventClass class for the PluginEvent * @param eventClass class for the PluginEvent
*/ */
public void addEventProducer(Class<? extends PluginEvent> eventClass) { public void addEventProducer(Class<? extends PluginEvent> eventClass) {
String name = PluginEvent.lookupToolEventName(eventClass); String name = PluginEvent.lookupToolEventName(eventClass);
if (name != null) { if (name != null) {
Counter counter = producerMap.get(name); Counter counter = producerMap.get(name);
if (counter == null) {
counter = new Counter();
producerMap.put(name, counter);
}
counter.count++; counter.count++;
} }
} }
@@ -162,11 +153,13 @@ public class EventManager {
*/ */
public void removeEventProducer(Class<? extends PluginEvent> eventClass) { public void removeEventProducer(Class<? extends PluginEvent> eventClass) {
String name = PluginEvent.lookupToolEventName(eventClass); String name = PluginEvent.lookupToolEventName(eventClass);
if (name != null) { if (name == null) {
Counter counter = producerMap.get(name); return;
if (counter != null && --counter.count == 0) {
producerMap.remove(name);
} }
Counter counter = producerMap.get(name);
if (--counter.count == 0) {
producerMap.remove(name);
} }
} }
@@ -195,70 +188,77 @@ public class EventManager {
synchronized (eventQ) { synchronized (eventQ) {
if (currentEvent != null) { if (currentEvent != null) {
if (validateEventChain(event)) { if (validateEventChain(currentEvent, event)) {
// note: it is a bit odd that we assume any event passed to this method is
// triggered by that event. This may not be the case if we are on a
// background thread.
event.setTriggerEvent(currentEvent); event.setTriggerEvent(currentEvent);
eventQ.add(event); eventQ.add(event);
} }
return;
return; // allow the current event processing to finish
} }
currentEvent = event;
} }
if (SwingUtilities.isEventDispatchThread()) { // no event processing running right now; start it
sendEvents(); eventQ.add(event);
Swing.runNow(sendEventsRunnable);
} }
else {
try { private boolean validateEventChain(PluginEvent startEvent, PluginEvent newEvent) {
SwingUtilities.invokeAndWait(sendEventsRunnable); while (startEvent != null) {
} if (startEvent.getClass().isAssignableFrom(newEvent.getClass()) &&
catch (InterruptedException e) { startEvent.getEventName().equals(newEvent.getEventName())) {
} return false;
catch (InvocationTargetException e) {
} }
startEvent = startEvent.getTriggerEvent();
} }
return true;
} }
/** /**
* Convert the given tool event to a plugin event, and notify the * Convert the given tool event to a plugin event; notify the appropriate plugin listeners.
* appropriate plugin event listeners. * This method allows one tool's event manager to send events to another connected tool.
* @param event tool event * @param event tool event
*/ */
public void processToolEvent(PluginEvent event) { public void processToolEvent(PluginEvent event) {
// only process the event if we are the receiving tool
if (!sendingToolEvent) { if (!sendingToolEvent) {
fireEvent(event); fireEvent(event);
} }
} }
/** /**
* Clear the list of last plugin events fired. * Clear the list of last plugin events fired
*
*/ */
public void clearLastEvents() { public void clearLastEvents() {
lastEvents.clear(); lastEventsByType.clear();
} }
/** /**
* Return an array of the last plugin events fired. EventManager * Return an array of the last plugin events fired. EventManager maps the event class to the
* maps the event class to the last event fired. * last event fired.
*
* @return array of plugin events * @return array of plugin events
*/ */
public PluginEvent[] getLastEvents() { public PluginEvent[] getLastEvents() {
return lastEvents.values().toArray(new PluginEvent[lastEvents.size()]); return lastEventsByType.values().toArray(new PluginEvent[lastEventsByType.size()]);
} }
/**
* Send all events on the queue
*/
private void sendEvents() { private void sendEvents() {
Swing.assertThisIsTheSwingThread("Events must be sent on the Swing thread");
synchronized (eventQ) {
currentEvent = eventQ.poll();
}
while (currentEvent != null) { while (currentEvent != null) {
Class<? extends PluginEvent> eventClass = currentEvent.getClass(); Class<? extends PluginEvent> eventClass = currentEvent.getClass();
lastEvents.remove(eventClass); lastEventsByType.put(eventClass, currentEvent);
lastEvents.put(eventClass, currentEvent);
Set<PluginEventListener> set = pluginListenerMap.get(eventClass); for (PluginEventListener listener : getListeners(eventClass)) {
if (set != null) {
for (PluginEventListener listener : set) {
try { try {
listener.eventSent(currentEvent); listener.eventSent(currentEvent);
} }
@@ -267,46 +267,51 @@ public class EventManager {
"Error in plugin event listener", t); "Error in plugin event listener", t);
} }
} }
}
for (PluginEventListener pluginEventListener : allEventListeners) { sendToolEvent(currentEvent);
pluginEventListener.eventSent(currentEvent);
}
sendToolEvent();
synchronized (eventQ) { synchronized (eventQ) {
currentEvent = eventQ.isEmpty() ? null : (PluginEvent) eventQ.removeFirst(); currentEvent = eventQ.poll();
} }
} }
tool.contextChanged(null); tool.contextChanged(null);
} }
private void sendToolEvent() { private Iterable<PluginEventListener> getListeners(Class<? extends PluginEvent> eventClass) {
if (!toolListeners.isEmpty() && currentEvent.isToolEvent()) { Set<PluginEventListener> specificListeners = listenersByEventType.get(eventClass);
return IterableUtils.chainedIterable(specificListeners, allEventListeners);
}
// note: this is expected to be on the Swing thread, called from sendEvent()
private void sendToolEvent(PluginEvent event) {
if (toolListeners.isEmpty()) {
return;
}
if (!event.isToolEvent()) {
return;
}
sendingToolEvent = true; sendingToolEvent = true;
try { try {
currentEvent.setSourceName(PluginEvent.EXTERNAL_SOURCE_NAME); event.setSourceName(PluginEvent.EXTERNAL_SOURCE_NAME);
currentEvent.setTriggerEvent(null); event.setTriggerEvent(null);
for (int i = 0; i < toolListeners.size(); i++) { for (int i = 0; i < toolListeners.size(); i++) {
ToolListener tl = toolListeners.get(i); ToolListener tl = toolListeners.get(i);
tl.processToolEvent(currentEvent);
try {
tl.processToolEvent(event);
}
catch (Throwable t) {
Msg.showError(this, tool.getToolFrame(), "Plugin Event Error",
"Error sending event to connected tool", t);
}
} }
} }
finally { finally {
sendingToolEvent = false; sendingToolEvent = false;
} }
} }
}
private boolean validateEventChain(PluginEvent event) {
PluginEvent tempEvent = currentEvent;
while (tempEvent != null) {
if (tempEvent.getClass().isAssignableFrom(event.getClass()) &&
tempEvent.getEventName().equals(event.getEventName())) {
return false;
}
tempEvent = tempEvent.getTriggerEvent();
}
return true;
}
/** /**
* Remove the event listener by className; the plugin registered for * Remove the event listener by className; the plugin registered for
@@ -314,35 +319,29 @@ public class EventManager {
* @param className class name of the plugin (event listener) * @param className class name of the plugin (event listener)
*/ */
public void removeEventListener(String className) { public void removeEventListener(String className) {
ArrayList<Class<? extends PluginEvent>> unusedList =
new ArrayList<>();
Iterator<Class<? extends PluginEvent>> iter = pluginListenerMap.keySet().iterator(); List<Class<? extends PluginEvent>> unusedList = new ArrayList<>();
Iterator<Class<? extends PluginEvent>> iter = listenersByEventType.keySet().iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Class<? extends PluginEvent> eventClass = iter.next(); Class<? extends PluginEvent> eventClass = iter.next();
Set<PluginEventListener> set = pluginListenerMap.get(eventClass); Set<PluginEventListener> set = listenersByEventType.get(eventClass);
Iterator<PluginEventListener> iterator = set.iterator(); Iterator<PluginEventListener> it = set.iterator();
for (; iterator.hasNext();) { while (it.hasNext()) {
PluginEventListener listener = iterator.next(); PluginEventListener listener = it.next();
if (listener.getClass().getName().equals(className)) { if (listener.getClass().getName().equals(className)) {
iterator.remove(); it.remove();
if (set.size() == 0) { if (set.isEmpty()) {
unusedList.add(eventClass); unusedList.add(eventClass);
} }
break; break;
} }
} }
} }
for (int i = 0; i < unusedList.size(); i++) { for (int i = 0; i < unusedList.size(); i++) {
Class<? extends PluginEvent> eventClass = unusedList.get(i); Class<? extends PluginEvent> eventClass = unusedList.get(i);
pluginListenerMap.remove(eventClass); eventConsumerRemoved(eventClass);
String name = PluginEvent.lookupToolEventName(eventClass);
if (name != null) {
Counter counter = consumerMap.get(name);
if (counter != null && --counter.count == 0) {
consumerMap.remove(name);
}
}
} }
} }