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