View Javadoc
1   package org.djutils.stats.summarizers.event;
2   
3   import java.util.Calendar;
4   
5   import org.djutils.event.Event;
6   import org.djutils.event.EventListener;
7   import org.djutils.event.EventListenerMap;
8   import org.djutils.event.EventProducer;
9   import org.djutils.event.LocalEventProducer;
10  import org.djutils.event.TimedEvent;
11  import org.djutils.exceptions.Throw;
12  import org.djutils.stats.summarizers.TimestampWeightedTally;
13  
14  /**
15   * The TimestampWeightedTally class defines a time-weighted tally based on timestamped data. The difference with a normal
16   * time-weighed tally is that the weight of a value is only known at the occurrence of the next timestamp. Furthermore, a last
17   * timestamp needs to be specified to determine the weight of the last value. This EventBased version of the tally can be
18   * notified with timestamps and values using the EventListenerInterface. It also produces events when values are tallied and
19   * when the tally is initialized. Timestamps can be Number based or Calendar based.
20   * <p>
21   * Copyright (c) 2020-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
22   * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
23   * project is distributed under a three-clause BSD-style license, which can be found at
24   * <a href="https://simulation.tudelft.nl/dsol/3.0/license.html" target="_blank">
25   * https://simulation.tudelft.nl/dsol/3.0/license.html</a>. <br>
26   * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank"> Alexander Verbraeck</a>
27   */
28  public class EventBasedTimestampWeightedTally extends TimestampWeightedTally implements EventProducer, EventListener
29  {
30      /** The embedded EventProducer. */
31      private EventProducer eventProducer = null;
32  
33      /**
34       * constructs a new EventBasedTimestampWeightedTally with a description.
35       * @param description the description of this EventBasedTimestampWeightedTally
36       */
37      public EventBasedTimestampWeightedTally(final String description)
38      {
39          this(description, new LocalEventProducer());
40      }
41  
42      /**
43       * Construct a new EventBasedTimestampWeightedTally with a description.
44       * @param description the description of this WeightedTally
45       * @param eventProducer the EventProducer to embed and use in this statistic
46       */
47      public EventBasedTimestampWeightedTally(final String description, final EventProducer eventProducer)
48      {
49          super(description);
50          Throw.whenNull(eventProducer, "eventProducer cannot be null");
51          this.eventProducer = eventProducer;
52      }
53  
54      @Override
55      public EventListenerMap getEventListenerMap()
56      {
57          return this.eventProducer.getEventListenerMap();
58      }
59  
60      @Override
61      public void initialize()
62      {
63          super.initialize();
64          if (this.eventProducer != null)
65          {
66              this.eventProducer.fireEvent(StatisticsEvents.INITIALIZED_EVENT);
67          }
68      }
69  
70      @Override
71      public void notify(final Event event)
72      {
73          if (event instanceof TimedEvent<?>)
74          {
75              TimedEvent<?> timedEvent = (TimedEvent<?>) event;
76              double value = 0.0;
77              if (event.getContent() instanceof Number)
78              {
79                  value = ((Number) event.getContent()).doubleValue();
80              }
81              else
82              {
83                  throw new IllegalArgumentException(
84                          "EventBasedTimestampWeightedTally.notify: Content " + event.getContent() + " should be a Number");
85              }
86              Object timestamp = timedEvent.getTimeStamp();
87              if (timestamp instanceof Number)
88              {
89                  register(((Number) timestamp).doubleValue(), value);
90              }
91              else if (timestamp instanceof Calendar)
92              {
93                  register(((Calendar) timestamp).getTimeInMillis(), value);
94              }
95              else
96              {
97                  throw new IllegalArgumentException(
98                          "EventBasedTimestampWeightedTally.notify: timestamp should be a Number or Calendar");
99              }
100         }
101         else
102         {
103             throw new IllegalArgumentException("EventBasedTimestampWeightedTally.notify: Event should be a TimedEvent");
104         }
105     }
106 
107     /**
108      * Process one observed Calender-based value. The time used will be the Calendar's time in milliseconds. Silently ignore
109      * when a value is registered, but tally is not active, i.e. when endObservations() has been called.
110      * @param timestamp the Calendar object representing the timestamp
111      * @param value the value to process
112      * @return the value
113      * @throws NullPointerException when timestamp is null
114      * @throws IllegalArgumentException when value is NaN
115      * @throws IllegalArgumentException when given timestamp is before last timestamp
116      */
117     @Override
118     public double register(final Calendar timestamp, final double value)
119     {
120         super.register(timestamp, value);
121         if (hasListeners())
122         {
123             this.eventProducer.fireEvent(StatisticsEvents.TIMESTAMPED_OBSERVATION_ADDED_EVENT, new Object[] {timestamp, value});
124             fireEvents(timestamp);
125         }
126         return value;
127     }
128 
129     /**
130      * Process one observed Number-based value. Silently ignore when a value is registered, but tally is not active, i.e. when
131      * endObservations() has been called.
132      * @param timestamp the object representing the timestamp
133      * @param value the value to process
134      * @return the value
135      * @throws NullPointerException when timestamp is null
136      * @throws IllegalArgumentException when value is NaN or timestamp is NaN
137      * @throws IllegalArgumentException when given timestamp is before last timestamp
138      */
139     @Override
140     public double register(final Number timestamp, final double value)
141     {
142         super.register(timestamp, value);
143         if (hasListeners())
144         {
145             this.eventProducer.fireEvent(StatisticsEvents.TIMESTAMPED_OBSERVATION_ADDED_EVENT, new Object[] {timestamp, value});
146             fireEvents(timestamp);
147         }
148         return value;
149     }
150 
151     /**
152      * Explicit;y override the double value method signature of WeightedTally to call the right method.<br>
153      * Process one observed double value. Silently ignore when a value is registered, but tally is not active, i.e. when
154      * endObservations() has been called.
155      * @param timestamp the object representing the timestamp
156      * @param value the value to process
157      * @return the value
158      * @throws NullPointerException when timestamp is null
159      * @throws IllegalArgumentException when value is NaN or timestamp is NaN
160      * @throws IllegalArgumentException when given timestamp is before last timestamp
161      */
162     @Override
163     public double register(final double timestamp, final double value)
164     {
165         // cast to avoid that compiler unboxes Double to double and causes stack overflow
166         return register((Number) Double.valueOf(timestamp), value);
167     }
168 
169     /**
170      * Method that can be overridden to fire own events or additional events when registering an observation. This method takes
171      * an Object because Number itself is NOT comparable.
172      * @param <T> a type for the timestamp that is Comparable
173      * @param timestamp the timestamp to use in the TimedEvents
174      */
175     protected <T extends Comparable<T>> void fireEvents(final Object timestamp)
176     {
177         // Note that All implementations of Number are Comparable. So is Calendar.
178         @SuppressWarnings("unchecked")
179         T castedTimestamp = (T) timestamp;
180         fireTimedEvent(StatisticsEvents.TIMED_N_EVENT, getN(), castedTimestamp);
181         fireTimedEvent(StatisticsEvents.TIMED_MIN_EVENT, getMin(), castedTimestamp);
182         fireTimedEvent(StatisticsEvents.TIMED_MAX_EVENT, getMax(), castedTimestamp);
183         fireTimedEvent(StatisticsEvents.TIMED_WEIGHTED_POPULATION_MEAN_EVENT, getWeightedPopulationMean(), castedTimestamp);
184         fireTimedEvent(StatisticsEvents.TIMED_WEIGHTED_POPULATION_VARIANCE_EVENT, getWeightedPopulationVariance(),
185                 castedTimestamp);
186         fireTimedEvent(StatisticsEvents.TIMED_WEIGHTED_POPULATION_STDEV_EVENT, getWeightedPopulationStDev(), castedTimestamp);
187         fireTimedEvent(StatisticsEvents.TIMED_WEIGHTED_SUM_EVENT, getWeightedSum(), castedTimestamp);
188         fireTimedEvent(StatisticsEvents.TIMED_WEIGHTED_SAMPLE_MEAN_EVENT, getWeightedSampleMean(), castedTimestamp);
189         fireTimedEvent(StatisticsEvents.TIMED_WEIGHTED_SAMPLE_VARIANCE_EVENT, getWeightedSampleVariance(), castedTimestamp);
190         fireTimedEvent(StatisticsEvents.TIMED_WEIGHTED_SAMPLE_STDEV_EVENT, getWeightedSampleStDev(), castedTimestamp);
191     }
192 
193     @Override
194     @SuppressWarnings("checkstyle:designforextension")
195     public String toString()
196     {
197         return "EventBasedWeightedTally" + super.toString().substring("WeightedTally".length());
198     }
199 
200 }