/*******************************************************************************
* Copyright (c) 2012-2015 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 org.eclipse.che.commons.lang.cache.Cache;
import org.eclipse.che.commons.lang.cache.LoadingValueSLRUCache;
import org.eclipse.che.commons.lang.cache.SynchronizedCache;
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 Cache<Class<?>, Set<Class<?>>>[] typeCache;
private final ConcurrentMap<Class<?>, Set<EventSubscriber>> subscribersByEventType;
@SuppressWarnings("unchecked")
public EventService() {
subscribersByEventType = new ConcurrentHashMap<>();
typeCache = new Cache[CACHE_NUM];
for (int i = 0; i < CACHE_NUM; i++) {
typeCache[i] = new SynchronizedCache<>(new LoadingValueSLRUCache<Class<?>, Set<Class<?>>>(SEG_SIZE, SEG_SIZE) {
@Override
protected Set<Class<?>> loadValue(Class<?> eventClass) throws RuntimeException {
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
*/
@SuppressWarnings("unchecked")
public void publish(Object event) {
if (event == null) {
throw new IllegalArgumentException("Null event.");
}
final Class<?> eventClass = event.getClass();
for (Class<?> clazz : typeCache[eventClass.hashCode() & CACHE_MASK].get(eventClass)) {
final Set<EventSubscriber> eventSubscribers = subscribersByEventType.get(clazz);
if (eventSubscribers != null && !eventSubscribers.isEmpty()) {
for (EventSubscriber eventSubscriber : eventSubscribers) {
try {
eventSubscriber.onEvent(event);
} catch (RuntimeException e) {
LOG.error(e.getMessage(), e);
}
}
}
}
}
/**
* Subscribe event listener.
*
* @param subscriber
* event subscriber
*/
public void subscribe(EventSubscriber<?> subscriber) {
final Class<?> eventType = getEventType(subscriber);
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);
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;
}
}