001/* An event that can be inserted in a CalendarQueue using an instance of Time
002 as a sort key.
003
004 Copyright (c) 1998-2014 The Regents of the University of California.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION_2
026 COPYRIGHTENDKEY
027
028 */
029package ptolemy.actor.util;
030
031///////////////////////////////////////////////////////////////////
032//// TimedEvent
033
034/**
035 This class aggregates an instance of Time and an Object, and provides a CQComparator
036 as an inner class.
037
038 @author Edward A. Lee and Haiyang Zheng
039 @version $Id$
040 @since Ptolemy II 0.4
041 @Pt.ProposedRating Yellow (eal)
042 @Pt.AcceptedRating Red (liuj)
043 @see CQComparator
044 @see Time
045 */
046public class TimedEvent implements Comparable<TimedEvent> {
047    /** Construct an event with the specified time stamp and contents.
048     *  @param time The time stamp.
049     *  @param obj The contents.
050     */
051    public TimedEvent(Time time, Object obj) {
052        timeStamp = time;
053        contents = obj;
054    }
055
056    ///////////////////////////////////////////////////////////////////
057    ////                         public members                    ////
058
059    /** The time stamp. */
060    public Time timeStamp;
061
062    /** The event object. */
063    public Object contents;
064
065    ///////////////////////////////////////////////////////////////////
066    ////                         public methods                    ////
067
068    /** Display timeStamp and contents. */
069    @Override
070    public String toString() {
071        return "timeStamp: " + timeStamp + ", contents: " + contents;
072    }
073
074    /** Return true if this TimedEvent object has the same
075     *  timeStamp and eventObject  as the given TimedEvent object.
076     *  @param timedEvent The TimedEvent object that this
077     *  TimedEvent object is compared to.
078     *  @return True if the two TimedEvent objects have the same time
079     *  stamp and event object.
080     */
081    @Override
082    public boolean equals(Object timedEvent) {
083        // See http://www.technofundo.com/tech/java/equalhash.html
084
085        /* FindBugs says that TimedEvent "defined
086         * compareTo(Object) and uses Object.equals()"
087         * http://findbugs.sourceforge.net/bugDescriptions.html#EQ_COMPARETO_USE_OBJECT_EQUALS
088         * says: "This class defines a compareTo(...) method but
089         * inherits its equals() method from
090         * java.lang.Object. Generally, the value of compareTo should
091         * return zero if and only if equals returns true. If this is
092         * violated, weird and unpredictable failures will occur in
093         * classes such as PriorityQueue. In Java 5 the
094         * PriorityQueue.remove method uses the compareTo method,
095         * while in Java 6 it uses the equals method.
096         *
097         *  From the JavaDoc for the compareTo method in the
098         *  Comparable interface:
099         *
100         * It is strongly recommended, but not strictly required that
101         * (x.compareTo(y)==0) == (x.equals(y)). Generally speaking,
102         * any class that implements the Comparable interface and
103         * violates this condition should clearly indicate this
104         * fact. The recommended language is "Note: this class has a
105         * natural ordering that is inconsistent with equals." "
106         */
107        if (timedEvent == this) {
108            return true;
109        }
110        if (timedEvent == null || timedEvent.getClass() != getClass()) {
111            return false;
112        } else {
113            TimedEvent event = (TimedEvent) timedEvent;
114            if (compareTo(event) == 0 && contents.equals(event.contents)) {
115                return true;
116            }
117        }
118        return false;
119    }
120
121    /** Compare two TimedEvents by comparing their timestamps.
122     *  @param timedEvent The event to compare against.
123     *  @return The integer -1, 0, or 1 if this is less than, equal to, or
124     *   greater than the argument.
125     */
126    @Override
127    public int compareTo(TimedEvent timedEvent) {
128        return timeStamp.compareTo(timedEvent.timeStamp);
129    }
130
131    /** Return the hash code for the TimedEvent object. If two
132     *  TimedEvent objects contains the same timestamp,
133     *  and event object, then they will have the same hashCode.
134     *  @return The hash code for this TimedEvent object.
135     */
136    @Override
137    public int hashCode() {
138        int hashCode = 21;
139        if (timeStamp != null) {
140            hashCode = 31 * hashCode + timeStamp.hashCode();
141        }
142        if (contents != null) {
143            hashCode = 31 * hashCode + contents.hashCode();
144        }
145        return hashCode;
146    }
147
148    ///////////////////////////////////////////////////////////////////
149    ////                         inner classes                     ////
150
151    ///////////////////////////////////////////////////////////////////
152    //// TimeComparator
153
154    /**
155     * This class implements the CQComparator interface. It compares instances
156     * of TimedEvent. Therefore, all arguments passed to its methods have
157     * to be of type TimedEvent (or TimedEvent[] for the getBinWidth() method).
158     * If this is violated, ClassCastException will be thrown.
159     */
160    public static class TimeComparator implements CQComparator {
161        /** Construct a TimeComparator object.
162         */
163        public TimeComparator() {
164            _binWidth = 1;
165            _zeroReference = 0.0;
166        }
167
168        ///////////////////////////////////////////////////////////////////
169        ////                         public methods                    ////
170
171        /** Compare the two arguments. Return a negative integer,
172         *  zero, or a positive integer depending on whether
173         *  the first argument is less than,
174         *  equal to, or greater than the second.
175         *  Both arguments have to be instances of TimedEvent, otherwise a
176         *  ClassCastException will be thrown.
177         *  @param object1 The first event.
178         *  @param object2 The second event.
179         *  @return -1, 0, or +1 depending on whether the first
180         *   argument is less than, equal to, or greater than the second.
181         *  @exception ClassCastException If either argument is not an instance
182         *   of TimedEvent.
183         */
184        @Override
185        public int compare(Object object1, Object object2) {
186            TimedEvent a = (TimedEvent) object1;
187            TimedEvent b = (TimedEvent) object2;
188            return a.timeStamp.compareTo(b.timeStamp);
189        }
190
191        /** Given an entry, return a virtual bin number for the entry.
192         *  The calculation performed is:
193         *  <p>
194         *  <i>(entry.timeStamp - zeroReference) / binWidth</i>,
195         *  </p>
196         *  with the result cast to long.
197         *  If the arguments are not instances of TimedEvent, then a
198         *  ClassCastException will be thrown.
199         *  If the bin number is larger than what can be represented
200         *  in a long, then the low-order 64 bits will be returned.
201         *  Note that this could change the sign of the result, but
202         *  the way this is used in the CalendarQueue class, this is OK.
203         *  It is converted to a bin number by masking some number of
204         *  low-order bits, so the result will be unaffected by the
205         *  sign error.
206         *  @param entry The entry.
207         *  @return The virtual bin number for the entry, according to the
208         *   current zero reference and the bin width.
209         *  @exception ClassCastException If the arguments are not instances of
210         *   TimedEvent.
211         */
212        @Override
213        public long getVirtualBinNumber(Object entry) {
214            // Note: The longValue() method will only
215            // returns the low-order 64 bits of the result.
216            // If it is larger than what can be represented
217            // in 64 bits, then the returned result will be wrapped.
218            long value = (long) (((TimedEvent) entry).timeStamp
219                    .subtract(_zeroReference).getLongValue() / _binWidth);
220            if (value != Long.MAX_VALUE) {
221                return value;
222            } else {
223                return Long.MAX_VALUE - 1;
224            }
225        }
226
227        /** Given an array of TimedEvent objects, find the appropriate bin
228         *  width. By 'appropriate', we mean that
229         *  the bin width is chosen such that on average
230         *  the number of entries in all non-empty bins is equal to one.
231         *  If the argument is null, return the default bin width,
232         *  which is 1.0 for this implementation.
233         *  Otherwise, the statistics of the elements of the array
234         *  are analyzed to determine a reasonable bin width.
235         *
236         *  @param entryArray An array of TimedEvent objects.
237         *  @exception ClassCastException If one of the array elements is not
238         *   an instance of TimedEvent.
239         */
240        @Override
241        public void setBinWidth(Object[] entryArray) {
242            if (entryArray == null) {
243                // Reset to default.
244                _binWidth = 1;
245                _zeroReference = 0.0;
246                return;
247            }
248
249            double[] diff = new double[entryArray.length - 1];
250
251            Time firstEntryTime = ((TimedEvent) entryArray[0]).timeStamp;
252            Time lastEntryTime = ((TimedEvent) entryArray[entryArray.length
253                    - 1]).timeStamp;
254
255            if (firstEntryTime.isInfinite()
256                    && firstEntryTime.equals(lastEntryTime)) {
257                // To avoid setting NaN or 0.0
258                // for the width, apparently due to simultaneous events,
259                // we leave it unchanged instead.
260                return;
261            }
262
263            double average = lastEntryTime.subtract(firstEntryTime)
264                    .getDoubleValue();
265            average = average / (entryArray.length - 1);
266
267            double effAverage = 0.0;
268            int nEffSamples = 0;
269
270            for (int i = 1; i < entryArray.length; ++i) {
271                if (diff[i - 1] < 2 * average) {
272                    nEffSamples++;
273                    effAverage = effAverage + diff[i - 1];
274                }
275            }
276
277            // To avoid returning NaN or 0.0 for the width, if this is
278            // the result, leave the bin width unchanged.
279            if (effAverage == 0 || nEffSamples == 0) {
280                return;
281            }
282
283            effAverage = effAverage / nEffSamples;
284            _binWidth = effAverage * 3;
285        }
286
287        /** Set the zero reference, to be used in calculating the virtual
288         *  bin number.
289         *  @exception ClassCastException If the argument is not an instance
290         *   of TimedEvent.
291         */
292        @Override
293        public void setZeroReference(Object zeroReference) {
294            _zeroReference = ((TimedEvent) zeroReference).timeStamp
295                    .getDoubleValue();
296        }
297
298        ///////////////////////////////////////////////////////////////////
299        ////                         private members                   ////
300
301        // The bin width.
302        private double _binWidth;
303
304        // The zero reference.
305        private double _zeroReference;
306    }
307
308}