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 }