@Override
  @SuppressWarnings("unchecked")
  public <E extends Event<?>> void route(
      Object key,
      E event,
      List<Registration<Object, ? extends BiConsumer<Object, ? extends Event<?>>>> consumers,
      Consumer<E> completionConsumer,
      Consumer<Throwable> errorConsumer) {
    if (null != consumers && !consumers.isEmpty()) {
      List<Registration<Object, ? extends BiConsumer<Object, ? extends Event<?>>>> regs =
          filter.filter(consumers, key);
      int size = regs.size();
      // old-school for loop is much more efficient than using an iterator
      for (int i = 0; i < size; i++) {
        Registration<Object, ? extends BiConsumer<Object, ? extends Event<?>>> reg = regs.get(i);

        if (null == reg || reg.isCancelled() || reg.isPaused()) {
          continue;
        }
        try {
          ((BiConsumer<Object, E>) reg.getObject()).accept(key, event);
        } catch (Exceptions.CancelException cancel) {
          reg.cancel();
        } catch (Throwable t) {
          if (null != errorConsumer) {
            errorConsumer.accept(Exceptions.addValueAsLastCause(t, event));
          } else {
            logger.error("Event routing failed for {}: {}", reg.getObject(), t.getMessage(), t);
            if (RuntimeException.class.isInstance(t)) {
              throw (RuntimeException) t;
            } else {
              throw new IllegalStateException(t);
            }
          }
        } finally {
          if (reg.isCancelAfterUse()) {
            reg.cancel();
          }
        }
      }
    }
    if (null != completionConsumer) {
      try {
        completionConsumer.accept(event);
      } catch (Throwable t) {
        if (null != errorConsumer) {
          errorConsumer.accept(Exceptions.addValueAsLastCause(t, event));
        } else {
          logger.error("Completion Consumer {} failed: {}", completionConsumer, t.getMessage(), t);
        }
      }
    }
  }
/**
 * An {@link Router} that {@link Filter#filter filters} consumers before routing events to them.
 *
 * @author Andy Wilkinson
 * @author Stephane Maldini
 */
public class ConsumerFilteringRouter implements Router<Object, Event<?>> {

  private final Logger logger = Logger.getLogger(getClass());
  private final Filter filter;

  /**
   * Creates a new {@code ConsumerFilteringEventRouter} that will use the {@code filter} to filter
   * consumers.
   *
   * @param filter The filter to use. Must not be {@code null}.
   * @throws IllegalArgumentException if {@code filter} or {@code consumerInvoker} is null.
   */
  public ConsumerFilteringRouter(Filter filter) {
    Assert.notNull(filter, "filter must not be null");

    this.filter = filter;
  }

  @Override
  @SuppressWarnings("unchecked")
  public <E extends Event<?>> void route(
      Object key,
      E event,
      List<Registration<Object, ? extends BiConsumer<Object, ? extends Event<?>>>> consumers,
      Consumer<E> completionConsumer,
      Consumer<Throwable> errorConsumer) {
    if (null != consumers && !consumers.isEmpty()) {
      List<Registration<Object, ? extends BiConsumer<Object, ? extends Event<?>>>> regs =
          filter.filter(consumers, key);
      int size = regs.size();
      // old-school for loop is much more efficient than using an iterator
      for (int i = 0; i < size; i++) {
        Registration<Object, ? extends BiConsumer<Object, ? extends Event<?>>> reg = regs.get(i);

        if (null == reg || reg.isCancelled() || reg.isPaused()) {
          continue;
        }
        try {
          ((BiConsumer<Object, E>) reg.getObject()).accept(key, event);
        } catch (Exceptions.CancelException cancel) {
          reg.cancel();
        } catch (Throwable t) {
          if (null != errorConsumer) {
            errorConsumer.accept(Exceptions.addValueAsLastCause(t, event));
          } else {
            logger.error("Event routing failed for {}: {}", reg.getObject(), t.getMessage(), t);
            if (RuntimeException.class.isInstance(t)) {
              throw (RuntimeException) t;
            } else {
              throw new IllegalStateException(t);
            }
          }
        } finally {
          if (reg.isCancelAfterUse()) {
            reg.cancel();
          }
        }
      }
    }
    if (null != completionConsumer) {
      try {
        completionConsumer.accept(event);
      } catch (Throwable t) {
        if (null != errorConsumer) {
          errorConsumer.accept(Exceptions.addValueAsLastCause(t, event));
        } else {
          logger.error("Completion Consumer {} failed: {}", completionConsumer, t.getMessage(), t);
        }
      }
    }
  }

  /**
   * Returns the {@code Filter} being used
   *
   * @return The {@code Filter}.
   */
  public Filter getFilter() {
    return filter;
  }
}