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