001/* Record all input tokens for later querying.
002
003 Copyright (c) 1998-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.actor.lib;
029
030import java.util.ArrayList;
031import java.util.Collections;
032import java.util.Enumeration;
033import java.util.Iterator;
034import java.util.LinkedList;
035import java.util.List;
036
037import ptolemy.data.IntToken;
038import ptolemy.data.StringToken;
039import ptolemy.data.Token;
040import ptolemy.data.expr.Parameter;
041import ptolemy.data.type.BaseType;
042import ptolemy.kernel.CompositeEntity;
043import ptolemy.kernel.util.IllegalActionException;
044import ptolemy.kernel.util.NameDuplicationException;
045
046///////////////////////////////////////////////////////////////////
047//// Recorder
048
049/**
050 <p>Record all input tokens for later querying.  This actor can be used for
051 testing configurations of actors.  It can also be used in programs that
052 invoke Ptolemy models and wish to query the results after the model
053 is run.  The input tokens are read in the postfire() method so that
054 in domains with fixed-point semantics, only the final, settled value
055 is recorded.  The current time is also recorded for each value.
056 </p><p>
057 The <i>capacity</i> parameter limits the size of the record.
058 If the capacity is set to zero, then no tokens are recorded, but
059 the total number of input tokens is counted.  You can access
060 the count via the getCount() method.  If the capacity is 1,
061 then only the most recently seen token on each channel is recorded.
062 If the capacity is negative (the default), then the capacity
063 is infinite.</p>
064
065 @author Edward A. Lee
066 @version $Id$
067 @since Ptolemy II 0.3
068 @Pt.ProposedRating Green (eal)
069 @Pt.AcceptedRating Green (bilung)
070 */
071public class Recorder extends Sink {
072    /** Construct an actor with an input multiport that can accept any
073     *  Token.
074     *  @param container The container.
075     *  @param name The name of this actor.
076     *  @exception IllegalActionException If the entity cannot be contained
077     *   by the proposed container.
078     *  @exception NameDuplicationException If the container already has an
079     *   actor with this name.
080     */
081    public Recorder(CompositeEntity container, String name)
082            throws NameDuplicationException, IllegalActionException {
083        super(container, name);
084
085        capacity = new Parameter(this, "capacity", new IntToken(-1));
086        capacity.setTypeEquals(BaseType.INT);
087    }
088
089    ///////////////////////////////////////////////////////////////////
090    ////                     ports and parameters                  ////
091
092    /** The capacity of the record for each channel.
093     *  This parameter must contain an IntToken.
094     */
095    public Parameter capacity;
096
097    ///////////////////////////////////////////////////////////////////
098    ////                         public methods                    ////
099
100    /** Get the total number of events seen so far.
101     *  @return The total number of events seen so far.
102     */
103    public int getCount() {
104        return _count;
105    }
106
107    /** Get the history for the specified channel number.  If in any
108     *  firing there is no such channel, or no token was read on that
109     *  channel, then a string token with value "_" is returned in the
110     *  position of the list corresponding to that firing.
111     *  If nothing has been recorded (there have been no firings),
112     *  then return an empty list.
113     *  @param channel The input channel for which the history is desired.
114     *  @return A list of Token objects.
115     */
116    public List getHistory(int channel) {
117        ArrayList result = new ArrayList();
118
119        if (_records != null) {
120            result.ensureCapacity(_records.size());
121
122            Iterator firings = _records.iterator();
123
124            while (firings.hasNext()) {
125                Token[] record = (Token[]) firings.next();
126
127                if (channel < record.length) {
128                    if (record[channel] != null) {
129                        result.add(record[channel]);
130                        continue;
131                    }
132                }
133
134                result.add(_bottom);
135            }
136        }
137
138        return result;
139    }
140
141    /** Get the latest input for the specified channel.
142     *  If there has been no record yet for the specified channel,
143     *  then return the string token "_", representing "bottom".
144     *  @param channel The input channel for the record is desired.
145     *  @return The latest input token.
146     */
147    public Token getLatest(int channel) {
148        if (_latest == null || channel >= _latest.length
149                || _latest[channel] == null) {
150            return _bottom;
151        }
152
153        return _latest[channel];
154    }
155
156    /** Get the record for the specified channel number.  If in any
157     *  firing there is no such channel, or no token was read on that
158     *  channel, then a string token with value "_" is returned.
159     *  If nothing has been recorded (there have been no firings),
160     *  then return an empty enumeration.
161     *  @param channel The input channel for the record is desired.
162     *  @return An enumeration of Token objects.
163     *  @deprecated This method is deprecated. Use getHistory().
164     */
165    @Deprecated
166    public Enumeration getRecord(int channel) {
167        return Collections.enumeration(getHistory(channel));
168    }
169
170    /** Get the history of the time of each invocation of postfire().
171     *  @return A list of Double objects.
172     */
173    public List getTimeHistory() {
174        return _timeRecord;
175    }
176
177    /** Get the record of the current time of each invocation of postfire().
178     *  @return An enumeration of Double objects.
179     *  @deprecated This method is deprecated. Use getTimeHistory().
180     */
181    @Deprecated
182    public Enumeration getTimeRecord() {
183        return Collections.enumeration(_timeRecord);
184    }
185
186    /** Initialize the lists used to record input data.
187     *  @exception IllegalActionException If the parent class throws it.
188     */
189    @Override
190    public void initialize() throws IllegalActionException {
191        super.initialize();
192        _records = new LinkedList();
193        _timeRecord = new LinkedList();
194        _latest = null;
195        _count = 0;
196    }
197
198    /** Read at most one token from each input channel and record its value.
199     *  @exception IllegalActionException If there is no director.
200     */
201    @Override
202    public boolean postfire() throws IllegalActionException {
203        if (!super.postfire()) {
204            return false;
205        }
206        int width = input.getWidth();
207        Token[] record = new Token[width];
208
209        for (int i = 0; i < width; i++) {
210            if (input.hasToken(i)) {
211                Token token = input.get(i);
212                record[i] = token;
213                _count++;
214            }
215        }
216
217        int capacityValue = ((IntToken) capacity.getToken()).intValue();
218
219        if (capacityValue != 0) {
220            _records.add(record);
221            _timeRecord.add(Double
222                    .valueOf(getDirector().getModelTime().getDoubleValue()));
223
224            if (capacityValue > 0 && _records.size() > capacityValue) {
225                // Remove the first element.
226                _records.remove(0);
227                _timeRecord.remove(0);
228            }
229        }
230
231        _latest = record;
232        return true;
233    }
234
235    //     public void wrapup() throws IllegalActionException {
236    //         super.wrapup();
237    //         for (int channel = 0; channel < input.getWidth(); channel++) {
238    //             List history = getHistory(channel);
239    //             Iterator tokens = history.iterator();
240    //             while (tokens.hasNext()) {
241    //                 Token token = (Token)tokens.next();
242    //                 System.out.println(getFullName() + channel + ": " + token);
243    //             }
244    //         }
245    //     }
246
247    ///////////////////////////////////////////////////////////////////
248    ////                         private variables                 ////
249    // Count of events seen.
250    private int _count = 0;
251
252    // A linked list of arrays.
253    private List _records;
254
255    // The most recent set of inputs.
256    Token[] _latest;
257
258    // A linked list of Double objects, which are times.
259    private List _timeRecord;
260
261    // A token to indicate absence.
262    private static Token _bottom = new StringToken("_");
263}