001 /*
002 Copyright (c) 1996-2011, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 package org.hd.d.pg2k.svrCore.vars;
030
031 import java.io.InvalidObjectException;
032 import java.io.ObjectInputValidation;
033 import java.io.Serializable;
034 import java.util.BitSet;
035 import java.util.EnumMap;
036 import java.util.EnumSet;
037 import java.util.Set;
038
039
040 /**Current and historical entries for all periods/intervals one event forming one "set".
041 * This is mutable and used to hold live event stats in memory.
042 * <p>
043 * This maintains state incrementally on each new event, for example,
044 * shuffling out and discarding old historical event values if need be.
045 * <p>
046 * Thread-safe.
047 * <p>
048 * Mutator calls are synchronized,
049 * so that serialisation can take place on an instance
050 * providing that a lock is held on that instance.
051 * <p>
052 * Serializable for direct persistence if required,
053 * though an XML format rather than a binary one would probably be more robust.
054 * <p>
055 * Notes the last time an event was add()ed or a whole slot value was "set"
056 * so as to allow an observer to tell quickly when this instance may need a save.
057 */
058 public final class EventVariableValueSet implements Serializable,
059 ObjectInputValidation
060 {
061 /**Construct a single event variable value set (for all periods/intervals).
062 *
063 * @param def the variable definition; never null and must be an event
064 *
065 * @throws IllegalArgumentException if the arguments are invalid
066 */
067 public EventVariableValueSet(final SimpleVariableDefinition def)
068 throws IllegalArgumentException
069 {
070 this.def = def;
071
072 // Create an entry for each available period
073 // to a "period row" for that period,
074 // unless a subset has been requested in which case just do those specified.
075 final Set<EventPeriod> evPeriodSubset = def.getEvPeriodSubset();
076 // If no subset is specified, then use the full set of periods/intervals.
077 final Set<EventPeriod> _evPeriodSubset = (evPeriodSubset != null) ? evPeriodSubset :
078 EnumSet.allOf(EventPeriod.class);
079 byPeriod = new EnumMap<EventPeriod, EventVariableValuePeriodRow>(EventPeriod.class);
080 for(final EventPeriod ep : _evPeriodSubset)
081 { byPeriod.put(ep, new EventVariableValuePeriodRow(def, ep)); }
082
083 try { validateObject(); }
084 catch(final InvalidObjectException e)
085 { throw new IllegalArgumentException(e.getMessage()); }
086 }
087
088
089
090 /**The variable definition; never null. */
091 private final SimpleVariableDefinition def;
092
093 /**Get the variable definition; never null. */
094 public SimpleVariableDefinition getDef() { return(def); }
095
096
097 /**Non-empty Map from period to period row; never null.
098 * Logically immutable once created (and thus thread-safe),
099 * though the values remain mutable.
100 */
101 private final EnumMap<EventPeriod, EventVariableValuePeriodRow> byPeriod;
102
103
104 /**Time last event was added, or zero if none recorded or instance just deserialised.
105 * Is transient (not retained though serialisation).
106 * <p>
107 * Is volatile to allow access without a lock.
108 */
109 private transient volatile long lastEventTime;
110
111 /**Get time last event was added, or zero if none recorded or instance just deserialised. */
112 public long getLastEventTime() { return(lastEventTime); }
113
114
115 /**Record/add event to the tallies for each period.
116 * Note that the event must match the definition that this instance is constructed with
117 * or an IllegalArgumentException will be thrown.
118 * <p>
119 * Note that for some types, not all periods/intervals are selected,
120 * ie we record data for some periods and not others.
121 * <p>
122 * Updates the lastEventTime to the current time.
123 *
124 * @param svv event to be recorded; must be non-null and of event type
125 */
126 public synchronized void addEvent(final SimpleVariableValue svv)
127 {
128 if((svv == null) || !def.equals(svv.getDef()))
129 { throw new IllegalArgumentException(); }
130
131 // Add event to each period row that we have.
132 for(final EventVariableValuePeriodRow pr : byPeriod.values())
133 { pr.addEvent(svv); }
134
135 // Take the latest possible view of when all the event setting was finished...
136 lastEventTime = System.currentTimeMillis();
137 }
138
139
140 /**Get the specified event sets for the specified intervals; never null.
141 * This allows retrieval of zero or more event sets for the specified
142 * interval size.
143 * <p>
144 * Requests for more than SystemVariables.EVENT_SAMPLES_RETAINED in the
145 * past (or for the future!) cannot be satisfied and data will not be
146 * returned for them.
147 * <p>
148 * If data is requested for an interval/period excluded in the definition,
149 * an empty array is returned.
150 * <p>
151 * Usually not more than SystemVariables.EVENT_SAMPLES_RETAINED samples
152 * will be returned in response to any one request as a safety measure.
153 *
154 * @param intervalSelector never null
155 * @param intervalNumber the number of the first interval
156 * for which data is potentially required;
157 * if too far in the past or future then possibly no data
158 * will be available
159 * @param whichValues each true bit represents a slot for which data is
160 * required, bit 0 indicating data from the slot within which
161 * firstIntervalTime is located, bit 1 the previous slot, etc
162 *
163 * @return as many of the requested values as available,
164 * at least long enough to return all the available values,
165 * with [0] corresponding to bit 0 in the BitSet;
166 * may contain nulls or be zero-length but is never null
167 */
168 EventVariableValue[] getEventValues(final EventPeriod intervalSelector,
169 final long intervalNumber,
170 final BitSet whichValues)
171 {
172 final EventVariableValuePeriodRow eventVariableValuePeriodRow = byPeriod.get(intervalSelector);
173 if(eventVariableValuePeriodRow == null) { return(EventVariableValuePeriodRow._NO_EVENT_VALUES); }
174 return(eventVariableValuePeriodRow.getEventValues(intervalNumber, whichValues));
175 }
176
177 /**Set/override the specified event sets for the specified intervals; never null.
178 * This is used to set values that have been fetched from upstream
179 * (ie closer to the master data store).
180 * <p>
181 * This may ignore the call,
182 * and certainly will if the intervalNumber is outside the current time window it holds.
183 * <p>
184 * This will generally accept the value and store it if:
185 * <ul>
186 * <li>The intervalNumber is within the current window.
187 * <li>The intervalSelector is a period for which this event has data.
188 * <li>The supplied value is "better" than any current one held, ie:
189 * <ul>
190 * <li>We hold a null and evv is non-null.
191 * <li>We hold a non-authoritative value and evv is authoritative.
192 * <li>The authoritative-ness is the same bu we hold an entry
193 * with a lower total count than evv.
194 * <ul>
195 * </ul>
196 * </ul>
197 *
198 * @param evv non-null event value
199 */
200 public synchronized void setEventValue(final EventVariableValue evv)
201 {
202 if(evv == null)
203 { throw new IllegalArgumentException(); }
204 final SimpleVariableDefinition def = evv.getDef();
205 if(!def.equals(this.def))
206 { throw new IllegalArgumentException("mismatched def: "+def+" vs "+this.def); }
207
208 final EventVariableValuePeriodRow eventVariableValuePeriodRow =
209 byPeriod.get(evv.getPeriod());
210 // If data is not maintained for the specified period
211 // then return immediately...
212 if(eventVariableValuePeriodRow == null) { return; }
213
214 eventVariableValuePeriodRow.setEventValue(evv);
215
216 // Mark the set as needing a save since we probably updated it...
217 lastEventTime = System.currentTimeMillis();
218 }
219
220
221
222
223 /**Constructor for defensive copying on deserialisation.
224 *
225 * @param def the variable definition; never null and must be an event
226 * @param byPeriod the extant mapping from period to "period row" entries; never null
227 *
228 * @throws IllegalArgumentException if the arguments are invalid
229 */
230 private EventVariableValueSet(final SimpleVariableDefinition def,
231 final EnumMap<EventPeriod, EventVariableValuePeriodRow> byPeriod)
232 throws IllegalArgumentException
233 {
234 this.def = def;
235
236 // Defensive copy for safety.
237 // FIXME: probably this should be a deep copy, since the map values are mutable...
238 this.byPeriod = new EnumMap<EventPeriod, EventVariableValuePeriodRow>(byPeriod);
239
240 try { validateObject(); }
241 catch(final InvalidObjectException e)
242 { throw new IllegalArgumentException(e.getMessage()); }
243 }
244
245 /**Deserialise: use constructor for validation, defensive copying, etc. */
246 protected Object readResolve()
247 // throws ObjectStreamException
248 {
249 return(new EventVariableValueSet(def, byPeriod));
250 }
251
252
253 /**Check that the object state is consistent and legal. */
254 public void validateObject()
255 throws InvalidObjectException
256 {
257 if(def == null)
258 { throw new InvalidObjectException("bad object: def must not be null"); }
259 if(!def.isEvent())
260 { throw new InvalidObjectException("bad object: def not an event type"); }
261
262 if(byPeriod == null)
263 { throw new InvalidObjectException("bad object: byPeriod map must not be null"); }
264 if(byPeriod.isEmpty())
265 { throw new InvalidObjectException("bad object: byPeriod map must not be empty"); }
266 final Set<EventPeriod> evPeriodSubset = def.getEvPeriodSubset();
267 // If no subset is specified, then use the full set of periods/intervals.
268 final Set<EventPeriod> _evPeriodSubset = (evPeriodSubset != null) ? evPeriodSubset :
269 EnumSet.allOf(EventPeriod.class);
270 if(_evPeriodSubset.size() != byPeriod.size())
271 { throw new InvalidObjectException("bad object: byPeriod map size is wrong"); }
272 if(!_evPeriodSubset.containsAll(byPeriod.keySet()))
273 { throw new InvalidObjectException("bad object: byPeriod contains extra keys"); }
274 if(!byPeriod.keySet().containsAll(_evPeriodSubset))
275 { throw new InvalidObjectException("bad object: byPeriod missing at least one key"); }
276 for(final Object v : byPeriod.values())
277 {
278 if(!(v instanceof EventVariableValuePeriodRow))
279 { throw new InvalidObjectException("bad object: byPeriod map has missing/invalid value"); }
280 }
281 }
282
283 /**Unique Serialisation class ID. */
284 private static final long serialVersionUID = -1932690647089333134L;
285 }