001/* An actor that assembles multiple inputs to a RecordToken.
002
003 Copyright (c) 1998-2015 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.HashSet;
031import java.util.Map;
032import java.util.Map.Entry;
033import java.util.Set;
034import java.util.TreeMap;
035
036import ptolemy.actor.Manager;
037import ptolemy.actor.TypedAtomicActor;
038import ptolemy.actor.TypedIOPort;
039import ptolemy.actor.util.ConstructAssociativeType;
040import ptolemy.actor.util.ExtractFieldType;
041import ptolemy.data.RecordToken;
042import ptolemy.data.Token;
043import ptolemy.data.type.RecordType;
044import ptolemy.graph.Inequality;
045import ptolemy.kernel.CompositeEntity;
046import ptolemy.kernel.util.IllegalActionException;
047import ptolemy.kernel.util.NameDuplicationException;
048import ptolemy.kernel.util.NamedObj;
049import ptolemy.kernel.util.Workspace;
050
051///////////////////////////////////////////////////////////////////
052//// RecordAssembler
053
054/**
055 On each firing, read one token from each connected input port and assemble
056 them into a RecordToken. Disconnected input ports are ignored. The labels for
057 the RecordToken much match the names of the input ports. This is achieved
058 using two type constraints:
059
060 <ul>
061 <li><tt>output &ge; {x = typeOf(inputPortX), y = typeOf(inputPortY), ..}
062 </tt>, which requires the types of the input ports to be compatible
063 with the corresponding types in the output record.
064 </li>
065 <li><tt>each input &ge; the type of the corresponding field inside the
066 output record</tt>, which together with the first constraint forces
067 the input types to be exactly equal to the types of the corresponding
068 fields in the output record. This constraint is intended to back-
069 propagate type information upstream, not to assure type compatibility.
070 Therefore, this constraint is only set up for input ports that do not
071 already have a type declared.</li>
072 </ul>
073 Note that the output record is not required to contain a corresponding
074 field for every input, as downstream actors might require fewer fields
075 in the record they accept for input.
076 <p>
077 To use this class, instantiate it, and then add input ports
078 (instances of TypedIOPort).  This actor is polymorphic. The type constraint
079 is that the type of each record field is no less than the type of the
080 corresponding input port.
081 </p>
082 <p>Note that if the display name of a port is set, display name is used in
083 the type constraints instead of name. This is useful in case fields to
084 add to the record contain a period, because periods are not allowed in
085 port names.</p>
086
087
088 @author Yuhong Xiong, Marten Lohstroh
089 @version $Id$
090 @since Ptolemy II 1.0
091 @Pt.ProposedRating Yellow (yuhong)
092 @Pt.AcceptedRating Yellow (cxh)
093 @see RecordDisassembler
094 */
095public class RecordAssembler extends TypedAtomicActor {
096
097    /** Construct a RecordAssembler with the given container and name.
098     *  @param container The container.
099     *  @param name The name of this actor.
100     *  @exception IllegalActionException If this actor cannot be contained
101     *   by the proposed container.
102     *  @exception NameDuplicationException If the container already has an
103     *   actor with this name.
104     */
105    public RecordAssembler(CompositeEntity container, String name)
106            throws NameDuplicationException, IllegalActionException {
107        super(container, name);
108
109        output = new TypedIOPort(this, "output", false, true);
110
111        _attachText("_iconDescription",
112                "<svg>\n" + "<rect x=\"0\" y=\"0\" width=\"6\" "
113                        + "height=\"40\" style=\"fill:red\"/>\n" + "</svg>\n");
114    }
115
116    ///////////////////////////////////////////////////////////////////
117    ////                     ports and parameters                  ////
118
119    /** The output port. */
120    public TypedIOPort output;
121
122    ///////////////////////////////////////////////////////////////////
123    ////                         public methods                    ////
124
125    /** Clone the actor into the specified workspace.
126     *  @param workspace The workspace for the new object.
127     *  @return A new actor.
128     *  @exception CloneNotSupportedException If a derived class contains
129     *   an attribute that cannot be cloned.
130     */
131    @Override
132    public Object clone(Workspace workspace) throws CloneNotSupportedException {
133        RecordAssembler newObject = (RecordAssembler) super.clone(workspace);
134        newObject._portMap = _newPortMap();
135        return newObject;
136    }
137
138    /** Read one token from each input port, assemble them into a RecordToken,
139     *  and send the RecordToken to the output.
140     *  @exception IllegalActionException If there is no director.
141     */
142    @Override
143    public void fire() throws IllegalActionException {
144        super.fire();
145
146        int i = 0;
147        Set<Entry<String, TypedIOPort>> entries = _portMap.entrySet();
148        String[] labels = new String[entries.size()];
149        Token[] values = new Token[entries.size()];
150
151        for (Entry<String, TypedIOPort> entry : entries) {
152            labels[i] = entry.getKey();
153            values[i] = entry.getValue().get(0);
154            i++;
155        }
156
157        RecordToken result = new RecordToken(labels, values);
158
159        output.send(0, result);
160    }
161
162    /** React to a name change of contained ports. Update the internal
163     *  mapping from names and aliases to port objects, and invalidate
164     *  the resolved types.
165     *  @param object The object that changed.
166     */
167    @Override
168    public void notifyOfNameChange(NamedObj object) {
169        if (object instanceof TypedIOPort) {
170            _mapPorts();
171        }
172    }
173
174    /** Return true if all connected input ports have tokens, false if some
175     *  connected input ports do not have a token.
176     *  @return True if all connected input ports have tokens and the
177     *  parent method returns true.
178     *  @exception IllegalActionException If the hasToken() call to the
179     *   input port throws it.
180     *  @see ptolemy.actor.IOPort#hasToken(int)
181     */
182    @Override
183    public boolean prefire() throws IllegalActionException {
184        boolean superReturnValue = super.prefire();
185        for (TypedIOPort port : _portMap.values()) {
186            if (!port.hasToken(0)) {
187                if (_debugging) {
188                    _debug("Port " + port.getName()
189                            + " does not have a token, prefire()"
190                            + " will return false.");
191                }
192                return false;
193            }
194        }
195
196        return true && superReturnValue;
197    }
198
199    ///////////////////////////////////////////////////////////////////
200    ////                         protected methods                 ////
201
202    /** Set up and return two type constraints.
203     *  <ul>
204     *  <li><tt>output &ge; {x = typeOf(inputPortX), y = typeOf(inputPortY), ..}
205     *  </tt>, which requires the types of the input ports to be compatible
206     *  with the corresponding types in the output record.
207     *  </li>
208     *  <li><tt>each input &ge; the type of the corresponding field inside the
209     *  output record</tt>, which together with the first constraint forces
210     *  the input types to be exactly equal to the types of the corresponding
211     *  fields in the output record. This constraint is intended to back-
212     *  propagate type information upstream, not to assure type compatibility.
213     *  Therefore, this constraint is only set up for input ports that do not
214     *  already have a type declared.</li>
215     *  </ul>
216     *  Note that the output record is not required to contain a corresponding
217     *  field for every input, as downstream actors might require fewer fields
218     *  in the record they accept for input.
219     *  @return A set of type constraints
220     *  @see ConstructAssociativeType
221     *  @see ExtractFieldType
222     */
223    @Override
224    protected Set<Inequality> _customTypeConstraints() {
225        Set<Inequality> result = new HashSet<Inequality>();
226
227        // make sure the ports are mapped
228        _mapPorts();
229
230        // constrain the type of every input to be greater than or equal to
231        // the resolved type of the corresponding field in the output record
232        for (Entry<String, TypedIOPort> entry : _portMap.entrySet()) {
233            String inputName = entry.getKey();
234            TypedIOPort input = entry.getValue();
235            // only include ports that have no type declared
236            if (input.getTypeTerm().isSettable()) {
237                result.add(
238                        new Inequality(new ExtractFieldType(output, inputName),
239                                input.getTypeTerm()));
240            }
241        }
242
243        // constrain the fields in the output record to be greater than or
244        // equal to the declared or resolved types of the input ports:
245        // output >= {x = typeOf(outputPortX), y = typeOf(outputPortY), ..}
246        result.add(new Inequality(new ConstructAssociativeType(
247                _portMap.values(), RecordType.class), output.getTypeTerm()));
248
249        return result;
250    }
251
252    /** Do not establish the usual default type constraints.
253     *  @return null
254     */
255    @Override
256    protected Set<Inequality> _defaultTypeConstraints() {
257        return null;
258    }
259
260    /** Return a new _portMap, which is a map between
261     *  port names and strings.  Derived classes
262     *  like OrderedRecordAssembler would return
263     *  a map with a different ordering.
264     *  @return a Map from port names to TypedIOPorts.
265     */
266    protected Map<String, TypedIOPort> _newPortMap() {
267        // RecordToken._initializeStorage() should probably
268        // use a similar Collection class.
269        return new TreeMap<String, TypedIOPort>();
270    }
271
272    /** Map port names or aliases to port objects. If the mapping
273     *  has changed, then invalidate the resolved types, which
274     *  forces new type constraints with appropriate field names
275     *  to be generated.
276     */
277    protected void _mapPorts() {
278        // Retrieve the manager.
279        Manager manager = this.getManager();
280
281        // Generate a new mapping from names/aliases to ports.
282        Map<String, TypedIOPort> oldMap = _portMap;
283        _portMap = _newPortMap();
284        for (TypedIOPort p : this.inputPortList()) {
285            String name = p.getName();
286            String alias = p.getDisplayName();
287            // ignore unconnected ports
288            if (p.numberOfSources() < 1) {
289                continue;
290            }
291            if (alias == null || alias.equals("")) {
292                _portMap.put(name, p);
293            } else {
294                _portMap.put(alias, p);
295            }
296        }
297
298        // Only invalidate resolved types if there actually was a name change.
299        // As a result, new type constraints will be generated.
300        if (manager != null && (oldMap == null || !_portMap.equals(oldMap))) {
301            manager.invalidateResolvedTypes();
302        }
303    }
304
305    ///////////////////////////////////////////////////////////////////
306    ////                         protected variables               ////
307
308    /** Keeps track of which name or alias is associated with which port. */
309    protected Map<String, TypedIOPort> _portMap;
310
311}