/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.core.notification; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Singleton; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; /** * Dispatchers events to listeners. Usage example: * <pre> * EventService bus = new EventService(); * bus.subscribe(new EventSubscriber<MyEvent>() { * @Override * public void onEvent(MyEvent event) { * // do something with event * } * }); * bus.publish(new MyEvent()); * </pre> * * @author andrew00x */ @Singleton public class EventService { private static final Logger LOG = LoggerFactory.getLogger(EventService.class); private static final int CACHE_NUM = 1 << 2; private static final int CACHE_MASK = CACHE_NUM - 1; private static final int SEG_SIZE = 32; private final LoadingCache<Class<?>, Set<Class<?>>>[] typeCache; private final ConcurrentMap<Class<?>, Set<EventSubscriber>> subscribersByEventType; @SuppressWarnings("unchecked") public EventService() { subscribersByEventType = new ConcurrentHashMap<>(); typeCache = new LoadingCache[CACHE_NUM]; for (int i = 0; i < CACHE_NUM; i++) { typeCache[i] = CacheBuilder.newBuilder().concurrencyLevel(SEG_SIZE).build( new CacheLoader<Class<?>, Set<Class<?>>>() { @Override public Set<Class<?>> load(Class<?> eventClass) { LinkedList<Class<?>> parents = new LinkedList<>(); Set<Class<?>> classes = new HashSet<>(); parents.add(eventClass); while (!parents.isEmpty()) { Class<?> clazz = parents.pop(); classes.add(clazz); Class<?> parent = clazz.getSuperclass(); if (parent != null) { parents.add(parent); } Class<?>[] interfaces = clazz.getInterfaces(); if (interfaces.length > 0) { Collections.addAll(parents, interfaces); } } return classes; } }); } } /** * Publish event {@code event}. * * @param event * event * @return published event */ @SuppressWarnings("unchecked") public <T> T publish(T event) { if (event == null) { throw new IllegalArgumentException("Null event."); } final Class<?> eventClass = event.getClass(); for (Class<?> clazz : typeCache[eventClass.hashCode() & CACHE_MASK].getUnchecked(eventClass)) { final Set<EventSubscriber> eventSubscribers = subscribersByEventType.get(clazz); if (eventSubscribers != null && !eventSubscribers.isEmpty()) { for (EventSubscriber eventSubscriber : eventSubscribers) { try { LOG.debug("Publish event {} for {}", event, eventSubscriber); eventSubscriber.onEvent(event); } catch (RuntimeException e) { LOG.error(e.getMessage(), e); } } } } return event; } /** * Subscribe event listener. The event to subscribe to is inferred by checking the generic type arguments of the * given subscriber. * * @param subscriber * event subscriber */ public void subscribe(EventSubscriber<?> subscriber) { final Class<?> eventType = getEventType(subscriber); doSubscribe(subscriber, eventType); } /** * Subscribe to an event. The given subscriber will be called whenever an instance of the specified event is * published. * * @param subscriber The subscriber to call when an event is published. * @param eventType The event to subscribe to. */ public <T> void subscribe(EventSubscriber<? extends T> subscriber, Class<T> eventType) { doSubscribe(subscriber, eventType); } private void doSubscribe(EventSubscriber<?> subscriber, Class<?> eventType) { Set<EventSubscriber> entries = subscribersByEventType.get(eventType); if (entries == null) { Set<EventSubscriber> newEntries = new CopyOnWriteArraySet<>(); entries = subscribersByEventType.putIfAbsent(eventType, newEntries); if (entries == null) { entries = newEntries; } } entries.add(subscriber); } /** * Unsubscribe event listener. * * @param subscriber * event subscriber */ public void unsubscribe(EventSubscriber<?> subscriber) { final Class<?> eventType = getEventType(subscriber); doUnsubscribe(subscriber, eventType); } public <T> void unsubscribe(EventSubscriber<T> subscriber, Class<T> eventType) { doUnsubscribe(subscriber, eventType); } private void doUnsubscribe(EventSubscriber<?> subscriber, Class<?> eventType) { final Set<EventSubscriber> entries = subscribersByEventType.get(eventType); if (entries != null && !entries.isEmpty()) { boolean changed = entries.remove(subscriber); if (changed) { if (entries.isEmpty()) { subscribersByEventType.remove(eventType); } } } } private Class<?> getEventType(EventSubscriber<?> subscriber) { Class<?> eventType = null; Class<?> clazz = subscriber.getClass(); while (clazz != null && eventType == null) { for (Type type : clazz.getGenericInterfaces()) { if (type instanceof ParameterizedType) { final ParameterizedType parameterizedType = (ParameterizedType)type; final Type rawType = parameterizedType.getRawType(); if (EventSubscriber.class == rawType) { final Type[] typeArguments = parameterizedType.getActualTypeArguments(); if (typeArguments.length == 1) { if (typeArguments[0] instanceof Class) { eventType = (Class)typeArguments[0]; } } } } } clazz = clazz.getSuperclass(); } if (eventType == null) { throw new IllegalArgumentException(String.format("Unable determine type of events processed by %s", subscriber)); } return eventType; } }