Package ch.cmbntr.eventbus

Source Code of ch.cmbntr.eventbus.Handlers$EventAndTargets

package ch.cmbntr.eventbus;

import static com.google.common.base.Functions.compose;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.FluentIterable.from;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newArrayListWithCapacity;
import static java.lang.String.format;
import static java.lang.invoke.MethodHandleProxies.asInterfaceInstance;
import static java.lang.reflect.Modifier.isSynchronized;
import static java.util.Collections.newSetFromMap;
import static java.util.Collections.singleton;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.CheckForNull;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.UncheckedExecutionException;

/**
* Event handling.
*
* @author Michael Locher <cmbntr@gmail.com>
*/
public class Handlers {

  /**
   * Tests if a method is a {@link Subscribe} handler.
   */
  public static final Predicate<Method> IS_SUBSCRIBE_METHOD = isHandlerMarkedWith(Subscribe.class);

  private static final Predicate<Method> CONCURRENT_DELIVERY = new Predicate<Method>() {
    @Override
    public boolean apply(final Method method) {
      assert method != null;
      return method.getAnnotation(AllowConcurrentEvents.class) != null || isSynchronized(method.getModifiers());
    }
  };

  private static final Function<Class<?>, List<Method>> GET_METHODS = new Function<Class<?>, List<Method>>() {
    @Override
    public List<Method> apply(final Class<?> clazz) {
      assert clazz != null;
      return Arrays.asList(clazz.getMethods());
    }
  };

  private static final LoadingCache<Class<?>, Set<Class<?>>> HIERARCHIES = weakKeyCache(new CacheLoader<Class<?>, Set<Class<?>>>() {
    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Set<Class<?>> load(final Class<?> concreteClass) {
      return (Set) TypeToken.of(concreteClass).getTypes().rawTypes();
    }
  });

  private static final int WRITE_CONCURRENCY = 1;

  private Handlers() {
    super();
  }

  private static <K, V> LoadingCache<K, V> weakKeyCache(final CacheLoader<? super K, V> loader) {
    return CacheBuilder.newBuilder().weakKeys().concurrencyLevel(WRITE_CONCURRENCY).build(loader);
  }

  /**
   * Creates a new event {@link Handler} cache.
   *
   * @param isHandler determines if a method is a handler
   * @param builder the factory for handlers
   * @return a new handler cache
   */
  public static LoadingCache<Class<?>, Set<Handler>> newHandlerCache(final Predicate<? super Method> isHandler,
      final Function<? super Method, Handler> builder) {
    return weakKeyCache(CacheLoader.from(compose(filterAndBuild(isHandler, builder), GET_METHODS)));
  }

  private static Function<List<Method>, Set<Handler>> filterAndBuild(final Predicate<? super Method> isHandler,
      final Function<? super Method, Handler> builder) {
    final Function<Method, Handler> cachedBuilder = weakKeyCache(CacheLoader.from(builder));
    return new Function<List<Method>, Set<Handler>>() {
      @Override
      public Set<Handler> apply(final List<Method> methods) {
        assert methods != null;
        return ImmutableSet.copyOf(from(methods).filter(isHandler).transform(cachedBuilder));
      }
    };
  }

  /**
   * Event delivery.
   */
  public static interface EventDelivery {
    /**
     * Delivers an event.
     *
     * @param target the target
     * @param event the event
     */
    public void deliverEvent(Object target, Object event);
  }

  /**
   * Creates a new delivery for the given handler method.
   *
   * @param handler the handler
   * @param concurrent if {@code true}, concurrent deliveries are allowed on a single target
   * @return the delivery
   */
  public static EventDelivery createDelivery(final Method handler, final boolean concurrent) {
    try {
      return createDelivery(MethodHandles.publicLookup(), handler, concurrent);
    } catch (final IllegalAccessException e) {
      throw Throwables.propagate(e);
    }
  }

  private static EventDelivery createDelivery(final Lookup lookup, final Method h, final boolean concurrent)
      throws IllegalAccessException {
    h.setAccessible(true);
    final EventDelivery handler = asInterfaceInstance(EventDelivery.class, lookup.unreflect(h));
    return concurrent ? handler : new SynchronizedDelivery(handler);
  }

  /**
   * Only allows one delivery per target at any given time.
   */
  private static class SynchronizedDelivery implements EventDelivery {

    private static final LoadingCache<Object, Object> SYNCS_BY_TARGET = weakKeyCache(new CacheLoader<Object, Object>() {
      @Override
      public Object load(final Object key) throws Exception {
        return new Object();
      }
    });

    private final EventDelivery delivery;

    /**
     * Creates this.
     *
     * @param delivery the unsynchronized delivery
     */
    public SynchronizedDelivery(final EventDelivery delivery) {
      this.delivery = delivery;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deliverEvent(final Object target, final Object event) {
      synchronized (SynchronizedDelivery.SYNCS_BY_TARGET.getUnchecked(target)) {
        this.delivery.deliverEvent(target, event);
      }
    }

  }

  /**
   * Creates a handler factory.
   *
   * @param identifier a bus identifier
   * @param deliveryExecutor provides executors for the handler methods
   * @param batchDelivery determines if handler invocations are batched
   * @return a new handler factory
   */
  public static Function<Method, Handler> handlerBuilder(final String identifier,
      final Function<? super Method, ? extends Executor> deliveryExecutor, final Predicate<? super Method> batchDelivery) {
    final Logger logger = Logger.getLogger(EventHandler.class.getName() + "." + checkNotNull(identifier));
    return new Function<Method, Handler>() {
      @Override
      public Handler apply(final Method handler) {
        assert handler != null;
        final Executor exec = deliveryExecutor.apply(handler);
        final boolean batching = batchDelivery.apply(handler);
        final boolean threadSafe = CONCURRENT_DELIVERY.apply(handler);
        final EventDelivery delivery = createDelivery(handler, threadSafe);
        return EventHandler.create(logger, delivery, batching, exec);
      }
    };
  }

  /**
   * An event handler.
   */
  public static final class EventHandler implements Handler, Runnable {

    private final Logger logger;

    private final Queue<EventAndTargets> pending = Queues.newConcurrentLinkedQueue();

    private final Executor deliveryExecutor;

    private final EventDelivery delivery;

    private final boolean batchDelivery;

    @CheckForNull
    private Set<Object> strongTargets;

    @CheckForNull
    private Set<Object> weakTargets;

    /**
     * Creates this.
     *
     * @param logger the logger to use
     * @param delivery the delivery to perform
     * @param batchDelivery whether to perform batched deliveries
     * @param deliveryExecutor the executor to use for deliveries
     */
    protected EventHandler(final Logger logger, final EventDelivery delivery, final boolean batchDelivery,
        final Executor deliveryExecutor) {
      this.logger = logger;
      this.delivery = delivery;
      this.batchDelivery = batchDelivery;
      this.deliveryExecutor = deliveryExecutor;
    }

    /**
     * Creates this a handler.
     *
     * @param logger the logger to use
     * @param delivery the delivery to perform
     * @param batchDelivery whether to perform batched deliveries
     * @param deliveryExecutor the executor to use for deliveries
     * @return the handler
     */
    public static EventHandler create(final Logger logger, final EventDelivery delivery, final boolean batchDelivery,
        final Executor deliveryExecutor) {
      return new EventHandler(logger, delivery, batchDelivery, deliveryExecutor);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void run() {
      final EventAndTargets next = this.pending.poll();
      if (next == null) {
        return;
      }
      final Object event = next.event;
      for (final Object target : next.targets) {
        try {
          this.delivery.deliverEvent(target, event);
        } catch (final RuntimeException e) {
          this.logger.log(Level.SEVERE, "Could not dispatch event", e);
        }
      }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized boolean dispatch(final Object event) {
      final Collection<Object> targets = getTargets();
      if (targets == null || targets.isEmpty()) {
        return false;
      }
      if (this.batchDelivery) {
        deliverBatchMode(event, targets);
      } else {
        deliverSingleMode(event, targets);
      }
      return true;
    }

    private void deliverSingleMode(final Object event, final Collection<Object> targets) {
      for (final Object target : targets) {
        this.pending.add(new EventAndTargets(event, singleton(target)));
        this.deliveryExecutor.execute(this);
      }
    }

    private void deliverBatchMode(final Object event, final Collection<Object> targets) {
      this.pending.add(new EventAndTargets(event, targets));
      this.deliveryExecutor.execute(this);
    }

    @CheckForNull
    private synchronized Collection<Object> getTargets() {
      final Set<Object> s = this.strongTargets;
      final Set<Object> w = this.weakTargets;

      if (s == null && w == null) {
        return null;
      } else if (s == null) {
        // take a snapshot
        final Collection<Object> targets = newArrayList(w);
        if (targets.isEmpty()) {
          // all weak references are gone
          this.weakTargets = null;
          return null;
        }
        return targets;
      } else if (w == null) {
        // take a snapshot
        return newArrayList(s);
      } else {
        // take a snapshot and remove possible duplicates
        final int count = s.size() + w.size();
        final Collection<Object> snapshot = count < 2 ? newArrayListWithCapacity(1) : newStrongSet(count);
        snapshot.addAll(s);
        snapshot.addAll(w);
        return snapshot;
      }
    }

    private static Set<Object> newStrongSet() {
      return newSetFromMap(Maps.<Object, Boolean> newIdentityHashMap());
    }

    private static Set<Object> newStrongSet(final int expectedMaxSize) {
      return newSetFromMap(new IdentityHashMap<Object, Boolean>(expectedMaxSize));
    }

    private static Set<Object> newWeakSet() {
      return newSetFromMap(new MapMaker().concurrencyLevel(WRITE_CONCURRENCY).weakKeys().<Object, Boolean> makeMap());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void register(final Object target) {
      if (this.strongTargets == null) {
        this.strongTargets = newStrongSet();
      }
      this.strongTargets.add(target);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void registerWeak(final Object target) {
      if (this.weakTargets == null) {
        this.weakTargets = newWeakSet();
      }
      this.weakTargets.add(target);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void unregister(final Object target) {
      if (this.strongTargets != null) {
        this.strongTargets.remove(target);
        if (this.strongTargets.isEmpty()) {
          this.strongTargets = null;
        }
      }

      if (this.weakTargets != null) {
        this.weakTargets.remove(target);
        if (this.weakTargets.isEmpty()) {
          this.weakTargets = null;
        }
      }
    }

  }

  /**
   * Pair of an event and targets.
   */
  public static final class EventAndTargets {

    public final Object event;
    public final Iterable<Object> targets;

    /**
     * Creates this.
     *
     * @param event the event
     * @param targets the targets
     * @return the created instance
     */
    public EventAndTargets(final Object event, final Iterable<Object> targets) {
      super();
      this.event = event;
      this.targets = targets;
    }

  }

  /**
   * Checks if a method is a valid handler method.
   *
   * @param marker the marker to identify handlers
   * @return the predicate, which returns {@code true} if a method is a handler method
   */
  public static Predicate<Method> isHandlerMarkedWith(final Class<? extends Annotation> marker) {
    return new Predicate<Method>() {
      @Override
      public boolean apply(final Method method) {
        assert method != null;
        for (final Class<?> c : TypeToken.of(method.getDeclaringClass()).getTypes().rawTypes()) {
          try {
            final Method m = c.getMethod(method.getName(), method.getParameterTypes());
            if (m.isAnnotationPresent(marker)) {
              checkSingleArgument(method);
              return true;
            }
          } catch (final NoSuchMethodException ignored) {
            continue;
          }
        }
        return false;
      }

      private void checkSingleArgument(final Method method) {
        final int paramCount = method.getParameterTypes().length;
        if (paramCount != 1) {
          final String msg = format(
              "Method %s has @%s annotation, but requires %d arguments.  Event handler methods must require a single argument.",
              method, marker.getSimpleName(), paramCount);
          throw new IllegalArgumentException(msg);
        }
      }
    };
  }

  /**
   * Linearizes a class's type hierarchy into a set of Class objects. The set will include all superclasses
   * (transitively), and all interfaces implemented by these superclasses.
   *
   * @param concreteClass the type whose hierarchy will be processed
   * @return the complete type hierarchy, flattened and uniqued
   */
  public static Set<Class<?>> linearizeHierarchy(final Class<?> concreteClass) {
    try {
      return HIERARCHIES.getUnchecked(concreteClass);
    } catch (final UncheckedExecutionException e) {
      throw Throwables.propagate(e.getCause());
    }
  }

}
TOP

Related Classes of ch.cmbntr.eventbus.Handlers$EventAndTargets

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.