blob: e817b8724ae2f42fd2ebda2a2aa7abfc7a7f60d8 [file] [log] [blame]
/*******************************************************************************
* 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$
}
}
}