| /******************************************************************************* |
| * Copyright (c) 2009, 2017 Ericsson and others |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Francois Chouinard - Initial API and implementation |
| * Bernd Hufmann - Update register methods |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.tmf.core.signal; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.NonNullByDefault; |
| import org.eclipse.tracecompass.internal.tmf.core.Activator; |
| import org.eclipse.tracecompass.internal.tmf.core.TmfCoreTracer; |
| |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.Multimap; |
| |
| /** |
| * This class manages the set of signal listeners and the signals they are |
| * interested in. When a signal is broadcasted, the appropriate listeners signal |
| * handlers are invoked. |
| * |
| * @version 1.0 |
| * @author Francois Chouinard |
| */ |
| public class TmfSignalManager { |
| |
| // The set of event listeners and their corresponding handler methods. |
| // Note: listeners could be restricted to ITmfComponents but there is no |
| // harm in letting anyone use this since it is not tied to anything but |
| // the signal data type. |
| private static Map<Object, Method[]> fListeners = new HashMap<>(); |
| private static Map<Object, Method[]> fVIPListeners = new HashMap<>(); |
| private static Map<Object, Throwable> fContexts = new HashMap<>(); |
| |
| /** The outbound blacklist of pair <source, signal> */ |
| private static Multimap<@NonNull Object, @NonNull Class<? extends TmfSignal>> fOutboundSignalBlacklist = HashMultimap.create(); |
| /** The inbound blacklist of pair <listener, signal> */ |
| private static Multimap<@NonNull Object, @NonNull Class<? extends TmfSignal>> fInboundSignalBlacklist = HashMultimap.create(); |
| |
| // The signal executor for asynchronous signals |
| private static final ExecutorService fExecutor = Executors.newSingleThreadExecutor(); |
| |
| // If requested, add universal signal tracer |
| // TODO: Temporary solution: should be enabled/disabled dynamically |
| private static boolean fTraceIsActive = false; |
| private static TmfSignalTracer fSignalTracer; |
| |
| static { |
| if (fTraceIsActive) { |
| fSignalTracer = TmfSignalTracer.getInstance(); |
| register(fSignalTracer); |
| } |
| } |
| |
| /** |
| * Register an object to the signal manager. This object can then implement |
| * handler methods, marked with @TmfSignalHandler and with the expected |
| * signal type as parameter. |
| * |
| * @param listener |
| * The object that will be notified of new signals |
| */ |
| public static synchronized void register(Object listener) { |
| deregister(listener); // make sure that listener is only registered once |
| Method[] methods = getSignalHandlerMethods(listener); |
| if (methods.length > 0) { |
| fContexts.put(listener, new Throwable()); |
| fListeners.put(listener, methods); |
| } |
| } |
| |
| /** |
| * Ignore the outbound signal type from the specified source. |
| * One can ignore all signals by passing TmfSignal.class as the signal class. |
| * |
| * @param source |
| * The source object |
| * @param signal |
| * The signal class to ignore |
| * @since 3.2 |
| */ |
| @NonNullByDefault |
| public static synchronized void addIgnoredOutboundSignal(Object source, Class<? extends TmfSignal> signal) { |
| fOutboundSignalBlacklist.put(source, signal); |
| } |
| |
| /** |
| * Ignore the inbound signal type for the specified listener. |
| * All signals can be ignored by passing TmfSignal.class. |
| * |
| * @param listener |
| * The listener object for which the signal must be ignored |
| * @param signal |
| * The signal class to ignore |
| * @since 3.2 |
| */ |
| @NonNullByDefault |
| public static synchronized void addIgnoredInboundSignal(Object listener, Class<? extends TmfSignal> signal) { |
| fInboundSignalBlacklist.put(listener, signal); |
| } |
| |
| /** |
| * Remove the signal from the list of ignored outbound signal for the |
| * specified source if present. |
| * |
| * @param source |
| * The source object |
| * @param signal |
| * The signal class to remove from the ignore list |
| * @since 3.2 |
| */ |
| public static synchronized void removeIgnoredOutboundSignal(Object source, Class<? extends TmfSignal> signal) { |
| fOutboundSignalBlacklist.remove(source, signal); |
| } |
| |
| /** |
| * Remove the signal from the list of inbound ignored signals for the |
| * specified listener if present. |
| * |
| * @param listener |
| * The listener object |
| * @param signal |
| * The signal class to remove from the ignore list |
| * @since 3.2 |
| */ |
| public static synchronized void removeIgnoredInboundSignal(Object listener, Class<? extends TmfSignal> signal) { |
| fInboundSignalBlacklist.remove(listener, signal); |
| } |
| |
| |
| /** |
| * Clear the list of ignored outbound signals for the source. |
| * |
| * @param source |
| * The source object |
| * @since 3.2 |
| */ |
| public static synchronized void clearIgnoredOutboundSignalList(Object source) { |
| fOutboundSignalBlacklist.removeAll(source); |
| } |
| |
| /** |
| * Clear the list of ignored inbound signals for the listener. |
| * |
| * @param listener |
| * The listener object |
| * @since 3.2 |
| */ |
| public static synchronized void clearIgnoredInboundSignalList(Object listener) { |
| fInboundSignalBlacklist.removeAll(listener); |
| } |
| |
| /** |
| * Register an object to the signal manager as a "VIP" listener. All VIP |
| * listeners will all receive the signal before the manager moves on to the |
| * lowly, non-VIP listeners. |
| * |
| * @param listener |
| * The object that will be notified of new signals |
| */ |
| public static synchronized void registerVIP(Object listener) { |
| deregister(listener); // make sure that listener is only registered once |
| Method[] methods = getSignalHandlerMethods(listener); |
| if (methods.length > 0) { |
| fContexts.put(listener, new Throwable()); |
| fVIPListeners.put(listener, methods); |
| } |
| } |
| |
| /** |
| * De-register a listener object from the signal manager. This means that |
| * its @TmfSignalHandler methods will no longer be called. |
| * |
| * @param listener |
| * The object to de-register |
| */ |
| public static synchronized void deregister(Object listener) { |
| fVIPListeners.remove(listener); |
| fListeners.remove(listener); |
| fContexts.remove(listener); |
| fInboundSignalBlacklist.removeAll(listener); |
| fOutboundSignalBlacklist.removeAll(listener); |
| } |
| |
| /** |
| * Returns the list of signal handlers in the listener. Signal handler name |
| * is irrelevant; only the annotation (@TmfSignalHandler) is important. |
| * |
| * @param listener |
| * @return |
| */ |
| private static Method[] getSignalHandlerMethods(Object listener) { |
| List<Method> handlers = new ArrayList<>(); |
| Method[] methods = listener.getClass().getMethods(); |
| for (Method method : methods) { |
| if (method.isAnnotationPresent(TmfSignalHandler.class)) { |
| handlers.add(method); |
| } |
| } |
| return handlers.toArray(new Method[handlers.size()]); |
| } |
| |
| static int fSignalId = 0; |
| |
| /** |
| * Invokes the handling methods that listens to signals of a given type in |
| * the current thread. |
| * |
| * The list of handlers is built on-the-fly to allow for the dynamic |
| * creation/deletion of signal handlers. Since the number of signal handlers |
| * shouldn't be too high, this is not a big performance issue to pay for the |
| * flexibility. |
| * |
| * For synchronization purposes, the signal is bracketed by two synch |
| * signals. |
| * |
| * @param signal |
| * the signal to dispatch |
| */ |
| public static synchronized void dispatchSignal(TmfSignal signal) { |
| |
| /* Check if the source,signal tuple is blacklisted */ |
| Object source = signal.getSource(); |
| if (source != null) { |
| for (Class<? extends TmfSignal> signalClass : fOutboundSignalBlacklist.get(source)) { |
| if (signalClass.isAssignableFrom(signal.getClass())) { |
| return; |
| } |
| } |
| } |
| |
| int signalId = fSignalId++; |
| sendSignal(new TmfStartSynchSignal(signalId)); |
| signal.setReference(signalId); |
| sendSignal(signal); |
| sendSignal(new TmfEndSynchSignal(signalId)); |
| } |
| |
| /** |
| * Invokes the handling methods that listens to signals of a given type in a |
| * separate thread which will call |
| * {@link TmfSignalManager#dispatchSignal(TmfSignal)}. |
| * |
| * If a signal is already processed the signal will be queued and dispatched |
| * after the ongoing signal finishes. |
| * |
| * @param signal |
| * the signal to dispatch |
| */ |
| public static void dispatchSignalAsync(final TmfSignal signal) { |
| if (!fExecutor.isShutdown()) { |
| fExecutor.execute(() -> dispatchSignal(signal)); |
| } |
| } |
| |
| /** |
| * Disposes the signal manager |
| */ |
| public static synchronized void dispose() { |
| for (Entry<Object, Throwable> entry : fContexts.entrySet()) { |
| System.err.println(getWarningMessage(entry.getKey())); |
| StackTraceElement[] stackTrace = entry.getValue().getStackTrace(); |
| for (StackTraceElement elem : stackTrace) { |
| System.err.println("\t" + elem); //$NON-NLS-1$ |
| } |
| } |
| fExecutor.shutdown(); |
| } |
| |
| private static String getWarningMessage(Object listener) { |
| return "Resource leak: " + listener + " was not deregistered."; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| private static void sendSignal(TmfSignal signal) { |
| sendSignal(fVIPListeners, signal); |
| sendSignal(fListeners, signal); |
| } |
| |
| private static void sendSignal(Map<Object, Method[]> listeners, TmfSignal signal) { |
| |
| if (TmfCoreTracer.isSignalTraced()) { |
| TmfCoreTracer.traceSignal(signal, "(start)"); //$NON-NLS-1$ |
| } |
| |
| // Build the list of listener methods that are registered for this |
| // signal |
| Class<?> signalClass = signal.getClass(); |
| |
| Map<Object, List<Method>> targets = new HashMap<>(); |
| for (Map.Entry<Object, Method[]> entry : listeners.entrySet()) { |
| List<Method> matchingMethods = new ArrayList<>(); |
| |
| for (Method method : entry.getValue()) { |
| Class<?> classParam = method.getParameterTypes()[0]; |
| if (classParam.isAssignableFrom(signalClass)) { |
| /* Check if any of the ignore rule apply to the signal */ |
| boolean isBlackListed = false; |
| for (Class<? extends TmfSignal> signalClazz : fInboundSignalBlacklist.get(entry.getKey())) { |
| if (signalClazz.isAssignableFrom(classParam)) { |
| isBlackListed = true; |
| break; |
| } |
| } |
| |
| /* No rules apply, add it */ |
| if (!isBlackListed) { |
| matchingMethods.add(method); |
| } |
| } |
| } |
| if (!matchingMethods.isEmpty()) { |
| targets.put(entry.getKey(), matchingMethods); |
| } |
| } |
| |
| // Call the signal handlers |
| for (Map.Entry<Object, List<Method>> entry : targets.entrySet()) { |
| for (Method method : entry.getValue()) { |
| try { |
| method.invoke(entry.getKey(), signal); |
| if (TmfCoreTracer.isSignalTraced()) { |
| Object key = entry.getKey(); |
| String hash = String.format("%1$08X", entry.getKey().hashCode()); //$NON-NLS-1$ |
| String target = "[" + hash + "] " + key.getClass().getSimpleName() + ":" + method.getName(); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ |
| TmfCoreTracer.traceSignal(signal, target); |
| } |
| } catch (IllegalArgumentException e) { |
| Activator.logError("Exception handling signal " + signal + " in method " + method, e); //$NON-NLS-1$ //$NON-NLS-2$ |
| } catch (IllegalAccessException e) { |
| Activator.logError("Exception handling signal " + signal + " in method " + method, e); //$NON-NLS-1$ //$NON-NLS-2$ |
| } catch (InvocationTargetException e) { |
| Activator.logError("Exception handling signal " + signal + " in method " + method, e); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| } |
| |
| if (TmfCoreTracer.isSignalTraced()) { |
| TmfCoreTracer.traceSignal(signal, "(end)"); //$NON-NLS-1$ |
| } |
| } |
| |
| } |