/******************************************************************************* * Copyright (c) 2011 Wind River Systems, Inc. and others. 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: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tm.te.runtime.events; import java.util.ArrayList; import java.util.EventListener; import java.util.EventObject; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.tm.te.runtime.activator.CoreBundleActivator; import org.eclipse.tm.te.runtime.interfaces.events.IEventFireDelegate; import org.eclipse.tm.te.runtime.interfaces.events.IEventListener; import org.eclipse.tm.te.runtime.interfaces.tracing.ITraceIds; import org.osgi.framework.Bundle; /** * The event manager implementation. */ public final class EventManager { // Flag to remember if the extension point has been processed. private boolean extensionPointProcessed; // The list of registered listeners. private final List<ListenerListEntry> listeners = new ArrayList<ListenerListEntry>(); /** * Runnable implementation to fire a given event to a given listener. */ protected static class FireRunnable implements Runnable { private final IEventListener listener; private final EventObject event; /** * Constructor. * * @param listener The event listener. Must not be <code>null</code>. * @param event The event. Must not be <code>null</code>. */ public FireRunnable(IEventListener listener, EventObject event) { Assert.isNotNull(listener); Assert.isNotNull(event); this.listener = listener; this.event = event; } /* (non-Javadoc) * @see java.lang.Runnable#run() */ @Override public void run() { listener.eventFired(event); } } /** * Listener list entry. * <p> * Each entry contains a reference to the listener and a list of valid source classes. * If an event source can be casted to one of the classes the listener is invoked. */ private class ListenerListEntry { private final IEventListener listener; private final Object[] eventSources; private final Class<?>[] eventTypes; /** * Constructor. * * @param listener The listener. * @param eventType The event type the listener is interested in. * @param eventSource The source type for which events should be fired to the listener. */ protected ListenerListEntry(IEventListener listener, Class<?> eventType, Object eventSource) { this(listener, eventType == null ? null : new Class[] { eventType }, eventSource == null ? null : new Object[] { eventSource }); } /** * Constructor. * * @param listener The listener. * @param eventTypes The event types the listener is interested in. * @param eventSources The source types for which events should be fired to the listener. */ protected ListenerListEntry(IEventListener listener, Class<?>[] eventTypes, Object[] eventSources) { this.listener = listener; if (eventTypes == null || eventTypes.length == 0) { this.eventTypes = null; } else { this.eventTypes = eventTypes; } if (eventSources == null || eventSources.length == 0) { this.eventSources = null; } else { this.eventSources = eventSources; } } /** * Get the listener of this entry. */ protected EventListener getListener() { return listener; } /** * Check whether the listener wants to be called for changes of the source. * The check is made through <code>instanceof</code>. * * @param source The source of the event. * @return True, if the source can be casted to one of the registered event source types * or no event sources are registered. */ protected boolean listensTo(EventObject event) { boolean types = (eventTypes == null || eventTypes.length == 0); boolean sources = (eventSources == null || eventSources.length == 0); int t = 0; while (!types && eventTypes != null && t < eventTypes.length) { types = eventTypes[t].isInstance(event); t++; } int s = 0; while (!sources && eventSources != null && s < eventSources.length) { Object eventSource = eventSources[s]; if (eventSource instanceof Class<?>) { Class<?> eventSourceClass = (Class<?>)eventSource; sources = eventSourceClass.isInstance(event.getSource()); } else { sources = eventSource == event.getSource(); } s++; } return types && sources; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj != null && obj instanceof ListenerListEntry) { ListenerListEntry other = (ListenerListEntry)obj; return this.getListener() == other.getListener(); } return false; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return getClass().getName() + "{" + //$NON-NLS-1$ "listener=" + listener + //$NON-NLS-1$ ",eventTypes=" + eventTypes + //$NON-NLS-1$ ",eventSources=" + eventSources + //$NON-NLS-1$ "}"; //$NON-NLS-1$ } } /* * Thread save singleton instance creation. */ private static class LazyInstance { public static EventManager instance = new EventManager(); } /** * Private Constructor. */ EventManager() { extensionPointProcessed = false; } /** * Returns the singleton instance for the event manager. */ public static EventManager getInstance() { return LazyInstance.instance; } /** * Add a change listener to listen to a single event. * * @param listener The listener to add. * @param eventType The event type this listeners wants to be invoked. */ public void addEventListener(IEventListener listener, Class<?> eventType) { addEventListener(listener, eventType != null ? new Class[] { eventType } : null, null); } /** * Add a change listener to listen to multiple events. * * @param listener The listener to add. * @param eventTypes The event types this listeners wants to be invoked. */ public void addEventListener(IEventListener listener, Class<?>[] eventTypes) { addEventListener(listener, eventTypes, null); } /** * Add a change listener to listen to event from the specified event * source. If the listener instance had been registered already, the listener * event sources are updated * * @param listener The listener to add. * @param eventType The event type this listeners wants to be invoked. * @param eventSource The event source type this listeners wants to be invoked. */ public void addEventListener(IEventListener listener, Class<?> eventType, Object eventSource) { addEventListener(listener, eventType != null ? new Class[] { eventType } : null, eventSource != null ? new Object[] { eventSource } : null); } /** * Add a change listener to listen to events from the specified event * sources. If the listener instance had been registered already, the listener * event sources are updated * * @param listener The listener to add. * @param eventType The event type this listeners wants to be invoked. * @param eventSources The event sources type this listeners wants to be invoked. */ public void addEventListener(IEventListener listener, Class<?> eventType, Object[] eventSources) { addEventListener(listener, eventType != null ? new Class[] { eventType } : null, eventSources); } /** * Add a change listener to listen to event from the specified event * sources. If the listener instance had been registered already, the listener * event sources are updated * * @param listener The listener to add. * @param eventTypes The event types this listeners wants to be invoked. * @param eventSources The event source types this listeners wants to be invoked. */ public void addEventListener(IEventListener listener, Class<?>[] eventTypes, Object[] eventSources) { ListenerListEntry listEntry = new ListenerListEntry(listener, eventTypes, eventSources); // We must assure that the existing list entries can _never_ change! synchronized (listeners) { if (listeners.contains(listEntry)) { listeners.remove(listEntry); } listeners.add(listEntry); } } /** * Remove a change listener for all event types and sources. * * @param listener The listener to remove. */ public void removeEventListener(IEventListener listener) { ListenerListEntry listEntry = new ListenerListEntry(listener, (Class<?>)null, (Object)null); listeners.remove(listEntry); } /** * Remove all change listeners for all event types and sources. */ public void clear() { listeners.clear(); extensionPointProcessed = false; } /** * Notify all registered listeners. * * @param event The event. Must not be <code>null</code> */ public void fireEvent(final EventObject event) { Assert.isNotNull(event); synchronized (this) { // if the extension point has not been processed till here, now we have to do if (!extensionPointProcessed) { addExtensionPointNotificationListeners(); extensionPointProcessed = true; } } // Based on the current listener listener list, compile a list of event // listeners to where this event would have been send to in a synchronous invocation scheme. List<ListenerListEntry> affected = new ArrayList<ListenerListEntry>(); // Get the array of registered event listeners. ListenerListEntry[] registered = listeners.toArray(new ListenerListEntry[listeners.size()]); for (ListenerListEntry listEntry : registered) { // ignore listeners not listening to the event type and source if (listEntry.listensTo(event)) { affected.add(listEntry); } } // If no current listener is affected, return now immediately if (affected.size() == 0) { return; } // Loop over the list of affected listeners and fire the event. // If the affected listener is a fire delegate -> use it itself to fire the event for (ListenerListEntry listEntry : affected) { if (!(listEntry.getListener() instanceof IEventListener)) { continue; } // Create the runnable to use for executing the event firing Runnable runnable = new FireRunnable((IEventListener)listEntry.getListener(), event); // Check on how to fire the runnable if (listEntry.getListener() instanceof IEventFireDelegate) { // The listener is a fire delegate -> use it itself to fire the runnable ((IEventFireDelegate)listEntry.getListener()).fire(runnable); } else { // Listener isn't a fire delegate -> fire the runnable directly runnable.run(); } } } /* * Register change listeners defined by extension. */ private void addExtensionPointNotificationListeners() { IExtensionPoint ep = Platform.getExtensionRegistry().getExtensionPoint("org.eclipse.tm.te.runtime.eventListeners"); //$NON-NLS-1$ if (ep != null) { IExtension[] extensions = ep.getExtensions(); if (extensions != null && extensions.length > 0) { for (IExtension extension : extensions) { IConfigurationElement[] configElements = extension.getConfigurationElements(); if (configElements != null && configElements.length > 0) { for (IConfigurationElement configElement : configElements) { String name = configElement.getName(); if ("eventListener".equals(name)) { //$NON-NLS-1$ // try to read the "eventType" and "eventSourceType" configuration elements if any. List<Class<?>> eventTypes = new ArrayList<Class<?>>(); List<Class<?>> eventSourceTypes = new ArrayList<Class<?>>(); IConfigurationElement[] children = configElement.getChildren(); for (IConfigurationElement child : children) { if ("eventType".equals(child.getName())) { //$NON-NLS-1$ // The event types, we have to instantiate here as we need the class object! try { // First we try to instantiate the class using our own local class loader. // This trick can avoid activating the contributing plugin if we can load // the class ourself. // First we try to instantiate the class using our own context String className = child.getAttribute("class"); //$NON-NLS-1$ if (className == null || className.trim().length() == 0) { continue; } String bundleId = child.getAttribute("bundleId"); //$NON-NLS-1$ // If a bundle id got specified, use the specified bundle to load the service class Bundle bundle = bundleId != null ? bundle = Platform.getBundle(bundleId) : null; // If we don't have a bundle to load from yet, fallback to the declaring bundle if (bundle == null) bundle = Platform.getBundle(child.getDeclaringExtension().getNamespaceIdentifier()); // And finally, use our own bundle to load the class. This fallback is expected // to never be used. if (bundle == null) bundle = CoreBundleActivator.getContext().getBundle(); // Try to load the event type class now Class<?> eventType = bundle != null ? bundle.loadClass(className) : Class.forName(className); if (!eventTypes.contains(eventType)) { eventTypes.add(eventType); } } catch (Exception ex) { if (isTracingEnabled()) CoreBundleActivator.getTraceHandler().trace("Error instantiating event listener event type object instance: " + child.getAttribute("class"), //$NON-NLS-1$ //$NON-NLS-2$ 0, ITraceIds.TRACE_EVENTS, IStatus.ERROR, this); } } if ("eventSourceType".equals(child.getName())) { //$NON-NLS-1$ // The event source types, we have to instantiate here as we need the class object! try { // First we try to instantiate the class using our own local class loader. // This trick can avoid activating the contributing plugin if we can load // the class ourself. // First we try to instantiate the class using our own context String className = child.getAttribute("class"); //$NON-NLS-1$ if (className == null || className.trim().length() == 0) { continue; } String bundleId = child.getAttribute("bundleId"); //$NON-NLS-1$ // If a bundle id got specified, use the specified bundle to load the service class Bundle bundle = bundleId != null ? bundle = Platform.getBundle(bundleId) : null; // If we don't have a bundle to load from yet, fallback to the declaring bundle if (bundle == null) bundle = Platform.getBundle(child.getDeclaringExtension().getNamespaceIdentifier()); // And finally, use our own bundle to load the class. This fallback is expected // to never be used. if (bundle == null) bundle = CoreBundleActivator.getContext().getBundle(); // Try to load the event source type class now Class<?> eventSourceType = bundle != null ? bundle.loadClass(className) : Class.forName(className); if (!eventSourceTypes.contains(eventSourceType)) { eventSourceTypes.add(eventSourceType); } } catch (Exception ex) { if (isTracingEnabled()) CoreBundleActivator.getTraceHandler().trace("Error instantiating event listener event source type object instance: " + child.getAttribute("class"), //$NON-NLS-1$ //$NON-NLS-2$ 0, ITraceIds.TRACE_EVENTS, IStatus.ERROR, this); } } } // For extension point contributed event listeners, we use delegating // event listener instances IEventListener listener = new EventListenerProxy(configElement); addEventListener(listener, !eventTypes.isEmpty() ? eventTypes.toArray(new Class[eventTypes.size()]) : null, !eventSourceTypes.isEmpty() ? eventSourceTypes.toArray(new Class[eventSourceTypes.size()]) : null ); if (isTracingEnabled()) CoreBundleActivator.getTraceHandler().trace("Add extension point change listener: " + configElement.getAttribute("class"), //$NON-NLS-1$ //$NON-NLS-2$ 0, ITraceIds.TRACE_EVENTS, IStatus.INFO, this); } } } } } } } /** * Internal class used to delay the instantiation and plugin activation of * event listeners which are contributed via extension point till they * are really fired. */ private class EventListenerProxy implements IEventListener, IEventFireDelegate { private final IConfigurationElement configElement; private IEventListener delegate; /** * Constructor. * * @param configElement The contributing configuration element of the encapsulated event listener. * Must not be <code>null</code>. */ public EventListenerProxy(IConfigurationElement configElement) { Assert.isNotNull(configElement); this.configElement = configElement; delegate = null; } /** * Returns the event listener delegate and instantiate the delegate * if not yet done. * * @return The event listener delegate or <code>null</code> if the instantiation fails. */ private IEventListener getDelegate() { if (delegate == null) { // Check the contributing plug-in state boolean forcePluginActivation = Boolean.parseBoolean(configElement.getAttribute("forcePluginActivation")); //$NON-NLS-1$ if (!forcePluginActivation) { Bundle bundle = Platform.getBundle(configElement.getContributor().getName()); forcePluginActivation = bundle != null ? bundle.getState() == Bundle.ACTIVE : false; } // Load the event listener implementation class if plugin activations is allowed. if (forcePluginActivation) { try { Object executable = configElement.createExecutableExtension("class"); //$NON-NLS-1$ if (executable instanceof IEventListener) { delegate = (IEventListener)executable; } } catch (Exception ex) { if (isTracingEnabled()) CoreBundleActivator.getTraceHandler().trace("Error instantiating extension point event listener: " + configElement.getAttribute("class") //$NON-NLS-1$ //$NON-NLS-2$ + "(Possible Cause: " + ex.getLocalizedMessage() + ")", //$NON-NLS-1$ //$NON-NLS-2$ 0, ITraceIds.TRACE_EVENTS, IStatus.ERROR, this); } } } return delegate; } /* (non-Javadoc) * @see org.eclipse.tm.te.runtime.interfaces.events.IEventListener#eventFired(java.util.EventObject) */ @Override public void eventFired(EventObject event) { Assert.isNotNull(event); // Get the delegate (may force instantiation) IEventListener delegate = getDelegate(); // And pass on the event to the delegate if we got a valid delegate if (delegate != null) delegate.eventFired(event); } /* (non-Javadoc) * @see org.eclipse.tm.te.runtime.interfaces.events.IEventFireDelegate#fire(java.lang.Runnable) */ @Override public void fire(Runnable runnable) { Assert.isNotNull(runnable); // Pass on to the delegate if the delegate itself is an fire delegate, if (getDelegate() instanceof IEventFireDelegate) { ((IEventFireDelegate)getDelegate()).fire(runnable); } else { runnable.run(); } } } /** * Return <code>true</code> if the tracing mode is enabled for the * event manager and trace messages shall be printed to the debug console. */ public static boolean isTracingEnabled() { return CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_EVENTS); } }