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        }