// Path: core/src/main/java/com/github/jeluard/stone/spi/Dispatcher.java
// public abstract class Dispatcher {
// 
//   /**
//    * Intercept {@link Exception} thrown when a {@code value} is published.
//    */
//   public interface ExceptionHandler {
// 
//     /**
//      * Invoked when an {@link Exception} is thrown during accumulation/persistency process.
//      *
//      * @param exception
//      */
//     void onException(Exception exception);
// 
//   }
// 
//   static final Logger LOGGER = Loggers.create("dispatcher");
// 
//   private final ExceptionHandler exceptionHandler;
//   protected static final ExceptionHandler DEFAULT_EXCEPTION_HANDLER = new ExceptionHandler() {
//     @Override
//     public void onException(final Exception exception) {
//       if (Dispatcher.LOGGER.isLoggable(Level.WARNING)) {
//         Dispatcher.LOGGER.log(Level.WARNING, "Got exception while publishing", exception);
//       }
//     }
//   };
// 
//   /**
//    * @param exceptionHandler 
//    */
//   public Dispatcher(final ExceptionHandler exceptionHandler) {
//     this.exceptionHandler = Preconditions.checkNotNull(exceptionHandler, "null exceptionHandler");
//   }
// 
//   /**
//    * Safely execute {@link ExceptionHandler#onException(java.lang.Exception)} 
//    *
//    * @param exception 
//    */
//   protected final void notifyExceptionHandler(final Exception exception) {
//     try {
//       this.exceptionHandler.onException(exception);
//     } catch (Exception e) {
//       if (Dispatcher.LOGGER.isLoggable(Level.WARNING)) {
//         Dispatcher.LOGGER.log(Level.WARNING, "Got exception while executing "+ExceptionHandler.class.getSimpleName()+" <"+this.exceptionHandler+">", e);
//       }
//     }
//   }
// 
//   /**
//    * Dispatch {@link Listener}s executions.
//    *
//    * @param previousTimestamp {@linkplain com.github.jeluard.stone.api.TimeSeries#DEFAULT_LATEST_TIMESTAMP} if does not exist
//    * @param currentTimestamp
//    * @param value
//    * @param listeners
//    * @return true if dispatch has been accepted; false if rejected
//    */
//    public abstract boolean dispatch(long previousTimestamp, long currentTimestamp, int value, Listener[] listeners);
// 
// }


import javax.annotation.concurrent.NotThreadSafe;
import com.github.jeluard.guayaba.annotation.Idempotent;
import com.github.jeluard.guayaba.lang.Identifiable;
import com.github.jeluard.stone.spi.Dispatcher;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
  }

  /**
   * Publish a {@code timestamp}/{@code value} pair triggering accumulation/persistency process.
   * <br>
   * {@code timestamp}s must be monotonic (i.e. each timestamp must be strictly greate than the previous one).
   * <br>
   * {@code timestamp}s are assumed to be in milliseconds. No timezone information is considered so {@code timestamp} values must be normalized before being published.
   *
   * @param timestamp
   * @param value
   * @return true if {@code value} has been considered for accumulation (i.e. it is not a value in the past)
   */
  public final boolean publish(final long timestamp, final int value) {
    final boolean isAfter = isAfterPreviousTimestamp(timestamp, this.granularity);
    if (!isAfter) {
      //timestamp is older that what can be accepted; return early
      return false;
    }

    final long previousTimestamp = recordTimestamp(timestamp);

    //TODO propagate result. Using enum?
    this.dispatcher.dispatch(previousTimestamp, timestamp, value, this.listeners);

    return true;
  }

  @Idempotent
  @Override
// Path: core/src/main/java/com/github/jeluard/stone/spi/Dispatcher.java
// public abstract class Dispatcher {
// 
//   /**
//    * Intercept {@link Exception} thrown when a {@code value} is published.
//    */
//   public interface ExceptionHandler {
// 
//     /**
//      * Invoked when an {@link Exception} is thrown during accumulation/persistency process.
//      *
//      * @param exception
//      */
//     void onException(Exception exception);
// 
//   }
// 
//   static final Logger LOGGER = Loggers.create("dispatcher");
// 
//   private final ExceptionHandler exceptionHandler;
//   protected static final ExceptionHandler DEFAULT_EXCEPTION_HANDLER = new ExceptionHandler() {
//     @Override
//     public void onException(final Exception exception) {
//       if (Dispatcher.LOGGER.isLoggable(Level.WARNING)) {
//         Dispatcher.LOGGER.log(Level.WARNING, "Got exception while publishing", exception);
//       }
//     }
//   };
// 
//   /**
//    * @param exceptionHandler 
//    */
//   public Dispatcher(final ExceptionHandler exceptionHandler) {
//     this.exceptionHandler = Preconditions.checkNotNull(exceptionHandler, "null exceptionHandler");
//   }
// 
//   /**
//    * Safely execute {@link ExceptionHandler#onException(java.lang.Exception)} 
//    *
//    * @param exception 
//    */
//   protected final void notifyExceptionHandler(final Exception exception) {
//     try {
//       this.exceptionHandler.onException(exception);
//     } catch (Exception e) {
//       if (Dispatcher.LOGGER.isLoggable(Level.WARNING)) {
//         Dispatcher.LOGGER.log(Level.WARNING, "Got exception while executing "+ExceptionHandler.class.getSimpleName()+" <"+this.exceptionHandler+">", e);
//       }
//     }
//   }
// 
//   /**
//    * Dispatch {@link Listener}s executions.
//    *
//    * @param previousTimestamp {@linkplain com.github.jeluard.stone.api.TimeSeries#DEFAULT_LATEST_TIMESTAMP} if does not exist
//    * @param currentTimestamp
//    * @param value
//    * @param listeners
//    * @return true if dispatch has been accepted; false if rejected
//    */
//    public abstract boolean dispatch(long previousTimestamp, long currentTimestamp, int value, Listener[] listeners);
// 
// }


// Path: core/src/main/java/com/github/jeluard/stone/api/TimeSeries.java
import javax.annotation.concurrent.NotThreadSafe;
import com.github.jeluard.guayaba.annotation.Idempotent;
import com.github.jeluard.guayaba.lang.Identifiable;
import com.github.jeluard.stone.spi.Dispatcher;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

  }

  /**
   * Publish a {@code timestamp}/{@code value} pair triggering accumulation/persistency process.
   * <br>
   * {@code timestamp}s must be monotonic (i.e. each timestamp must be strictly greate than the previous one).
   * <br>
   * {@code timestamp}s are assumed to be in milliseconds. No timezone information is considered so {@code timestamp} values must be normalized before being published.
   *
   * @param timestamp
   * @param value
   * @return true if {@code value} has been considered for accumulation (i.e. it is not a value in the past)
   */
  public final boolean publish(final long timestamp, final int value) {
    final boolean isAfter = isAfterPreviousTimestamp(timestamp, this.granularity);
    if (!isAfter) {
      //timestamp is older that what can be accepted; return early
      return false;
    }

    final long previousTimestamp = recordTimestamp(timestamp);

    //TODO propagate result. Using enum?
    this.dispatcher.dispatch(previousTimestamp, timestamp, value, this.listeners);

    return true;
  }

  @Idempotent
  @Override
  public final void close() throws IOException {