001/* An actor that disassembles a RecordToken to multiple outputs.
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.HashMap;
032import java.util.HashSet;
033import java.util.Iterator;
034import java.util.Map;
035import java.util.Map.Entry;
036import java.util.Set;
037
038import ptolemy.actor.Director;
039import ptolemy.actor.Manager;
040import ptolemy.actor.TypedAtomicActor;
041import ptolemy.actor.TypedIOPort;
042import ptolemy.actor.util.ConstructAssociativeType;
043import ptolemy.actor.util.ExtractFieldType;
044import ptolemy.data.RecordToken;
045import ptolemy.data.Token;
046import ptolemy.data.type.BaseType;
047import ptolemy.data.type.RecordType;
048import ptolemy.data.type.Type;
049import ptolemy.data.type.TypeConstant;
050import ptolemy.graph.Inequality;
051import ptolemy.kernel.CompositeEntity;
052import ptolemy.kernel.util.IllegalActionException;
053import ptolemy.kernel.util.NameDuplicationException;
054import ptolemy.kernel.util.NamedObj;
055import ptolemy.kernel.util.Workspace;
056
057///////////////////////////////////////////////////////////////////
058//// RecordDisassembler
059
060/**
061 On each firing, read one RecordToken from the input port and send out
062 the fields of the RecordToken to multiple output ports.
063 The labels for the RecordToken must match the names of the output ports.
064 This is achieved using three type constraints:
065 <ul>
066 <li><tt>input &ge; {x = typeOf(outputPortX), y = typeOf(outputPortY), ..}
067 </tt>, which requires the types of the fields in the input record to be
068 compatible with the types of the corresponding output ports.
069 </li>
070 <li><tt>input &le; {x = GENERAL, y = GENERAL, ..}</tt>, which requires the
071 input record to contain a corresponding field for each output port.
072 </li>
073 <li><tt>each output &ge; the type of the corresponding field inside the input
074 record</tt>, which is similar to the usual default constraints, however
075 this constraint establishes a dependency between fields inside the input
076 record and the outputs of this actor, instead of just between its inputs
077 and outputs.
078 </li>
079 </ul>
080
081 <p>If the received Token contains more fields than the output ports, the extra
082 fields are ignored.</p>
083
084 <p>To use this class, instantiate it, and then add output ports (instances
085 of TypedIOPort).  This actor is polymorphic. The type constraint is that
086 the type of each output port is no less than the type of the corresponding
087 record field.</p>
088
089 <p>Note that if the display name of a port is set, display name is used in
090 the type constraints instead of name. This is useful in case fields to
091 extract from the record contain a period, because periods are not allowed
092 in port names.</p>
093
094 @author Yuhong Xiong, Steve Neuendorffer, Edward A. Lee, Marten Lohstroh
095 @version $Id$
096 @since Ptolemy II 1.0
097 @Pt.ProposedRating Yellow (yuhong)
098 @Pt.AcceptedRating Yellow (cxh)
099 @see RecordAssembler
100 */
101public class RecordDisassembler extends TypedAtomicActor {
102    /** Construct a RecordDisassembler with the given container and name.
103     *  @param container The container.
104     *  @param name The name of this actor.
105     *  @exception IllegalActionException If this actor cannot be contained
106     *   by the proposed container.
107     *  @exception NameDuplicationException If the container already has an
108     *   actor with this name.
109     */
110    public RecordDisassembler(CompositeEntity container, String name)
111            throws NameDuplicationException, IllegalActionException {
112        super(container, name);
113
114        input = new TypedIOPort(this, "input", true, false);
115        _attachText("_iconDescription",
116                "<svg>\n" + "<rect x=\"0\" y=\"0\" width=\"6\" "
117                        + "height=\"40\" style=\"fill:red\"/>\n" + "</svg>\n");
118    }
119
120    ///////////////////////////////////////////////////////////////////
121    ////                     ports and parameters                  ////
122
123    /** The input port. */
124    public TypedIOPort input;
125
126    ///////////////////////////////////////////////////////////////////
127    ////                         public methods                    ////
128
129    /** Clone the actor into the specified workspace.
130     *  @param workspace The workspace for the new object.
131     *  @return A new actor.
132     *  @exception CloneNotSupportedException If a derived class contains
133     *   an attribute that cannot be cloned.
134     */
135    @Override
136    public Object clone(Workspace workspace) throws CloneNotSupportedException {
137        RecordDisassembler newObject = (RecordDisassembler) super.clone(
138                workspace);
139        newObject._portMap = new HashMap<String, TypedIOPort>();
140        return newObject;
141    }
142
143    /** Read one RecordToken from the input port and send its fields
144     *  to the output ports.
145     *  If the input does not have a token, suspend firing and return.
146     *  @exception IllegalActionException If there is no director.
147     */
148    @Override
149    public void fire() throws IllegalActionException {
150        super.fire();
151        Director director = getDirector();
152
153        if (director == null) {
154            throw new IllegalActionException(this, "No director!");
155        }
156
157        if (input.hasToken(0)) {
158            RecordToken record = (RecordToken) input.get(0);
159            Iterator<?> labels = record.labelSet().iterator();
160
161            while (labels.hasNext()) {
162                String label = (String) labels.next();
163                Token value = record.get(label);
164                TypedIOPort port = _portMap.get(label);
165
166                // since the record received may contain more fields than the
167                // output ports, some fields may not have a corresponding
168                // output port.
169                if (port != null) {
170                    port.send(0, value);
171                }
172            }
173        }
174    }
175
176    /** React to a name change of contained ports. Update the internal
177     *  mapping from names and aliases to port objects, and invalidate
178     *  the resolved types.
179     *  @param object The object that changed.
180     */
181    @Override
182    public void notifyOfNameChange(NamedObj object) {
183        if (object instanceof TypedIOPort) {
184            _mapPorts();
185        }
186    }
187
188    ///////////////////////////////////////////////////////////////////
189    ////                         protected methods                 ////
190
191    /** Set up and return three type constraints.
192     *  <ul>
193     *  <li><tt>input &ge; {x = typeOf(outputPortX), y = typeOf(outputPortY), ..}
194     *  </tt>, which requires the types of the fields in the input record to be
195     *  compatible with the types of the corresponding output ports.
196     *  </li>
197     *  <li><tt>input &le; {x = GENERAL, y = GENERAL, ..}</tt>, which requires
198     *  the input record to contain a corresponding field for each output port.
199     *  </li>
200     *  <li><tt>each output &ge; the type of the corresponding field inside the
201     *  input record</tt>, which is similar to the usual default constraints,
202     *  however this constraint establishes a dependency between fields inside
203     *  the input record and the outputs of this actor, instead of just between
204     *  its inputs and outputs.
205     *  </li>
206     *  </ul>
207     *  @return A set of Inequality instances
208     *  @see ConstructAssociativeType
209     *  @see ExtractFieldType
210     */
211    @Override
212    protected Set<Inequality> _customTypeConstraints() {
213        Set<Inequality> result = new HashSet<Inequality>();
214        ArrayList<String> labels = new ArrayList<String>();
215        ArrayList<Type> types = new ArrayList<Type>();
216
217        // make sure the ports are mapped
218        _mapPorts();
219
220        // constrain the fields in the input record to be greater than or
221        // equal to the declared or resolved types of the output ports:
222        // input >= {x = typeOf(outputPortX), y = typeOf(outputPortY), ..}
223        result.add(new Inequality(new ConstructAssociativeType(
224                _portMap.values(), RecordType.class), input.getTypeTerm()));
225
226        for (Entry<String, TypedIOPort> entry : _portMap.entrySet()) {
227            String outputName = entry.getKey();
228            TypedIOPort output = entry.getValue();
229
230            labels.add(outputName);
231            types.add(BaseType.GENERAL);
232
233            // constrain each output to be >= the type of the corresponding
234            // field inside the input record
235            result.add(new Inequality(new ExtractFieldType(input, outputName),
236                    output.getTypeTerm()));
237        }
238        // constrain the input record to have the required fields:
239        // input <= {x = GENERAL, y = GENERAL}
240        result.add(new Inequality(input.getTypeTerm(),
241                new TypeConstant(new RecordType(
242                        labels.toArray(new String[labels.size()]),
243                        types.toArray(new Type[types.size()])))));
244
245        // NOTE: refrain from using port.setTypeAtMost() or
246        // port.setTypeAtLeast(), because after removing an output port, the
247        // constraint referring to this removed port will remain to exist in
248        // the input port, which will result in type errors.
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    ///////////////////////////////////////////////////////////////////
261    ////                         private methods                   ////
262
263    /** Map port names or aliases to port objects. If the mapping
264     *  has changed, then invalidate the resolved types, which
265     *  forces new type constraints with appropriate field names
266     *  to be generated.
267     */
268    private void _mapPorts() {
269        // Retrieve the manager.
270        Manager manager = this.getManager();
271
272        // Generate a new mapping from names/aliases to ports.
273        Map<String, TypedIOPort> oldMap = _portMap;
274        _portMap = new HashMap<String, TypedIOPort>();
275        for (TypedIOPort p : this.outputPortList()) {
276            String name = p.getName();
277            String alias = p.getDisplayName();
278            // ignore unconnected ports
279            if (p.numberOfSinks() < 1) {
280                continue;
281            }
282            if (alias == null || alias.equals("")) {
283                _portMap.put(name, p);
284            } else {
285                _portMap.put(alias, p);
286            }
287        }
288
289        // Only invalidate resolved types if there actually was a name change.
290        // As a result, new type constraints will be generated.
291        if (manager != null && (oldMap == null || !_portMap.equals(oldMap))) {
292            manager.invalidateResolvedTypes();
293        }
294    }
295
296    ///////////////////////////////////////////////////////////////////
297    ////                         private variables                 ////
298
299    /** Keeps track of which name or alias is associated with which port. */
300    private Map<String, TypedIOPort> _portMap;
301
302}