001/* An actor that updates fields in a RecordToken.
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.HashMap;
031import java.util.HashSet;
032import java.util.Iterator;
033import java.util.LinkedList;
034import java.util.List;
035import java.util.Map;
036import java.util.Set;
037
038import ptolemy.actor.Director;
039import ptolemy.actor.TypedAtomicActor;
040import ptolemy.actor.TypedIOPort;
041import ptolemy.data.RecordToken;
042import ptolemy.data.Token;
043import ptolemy.data.type.BaseType;
044import ptolemy.data.type.MonotonicFunction;
045import ptolemy.data.type.RecordType;
046import ptolemy.data.type.Type;
047import ptolemy.graph.Inequality;
048import ptolemy.graph.InequalityTerm;
049import ptolemy.kernel.CompositeEntity;
050import ptolemy.kernel.util.IllegalActionException;
051import ptolemy.kernel.util.NameDuplicationException;
052import ptolemy.kernel.util.Workspace;
053
054///////////////////////////////////////////////////////////////////
055//// RecordUpdater
056
057/**
058 On each firing, read one token from each input port and assemble them
059 into a RecordToken that contains the union of the original input record
060 and each of the update ports.  To use this class, instantiate it, and
061 then add input ports (instances of TypedIOPort).  This actor is polymorphic.
062 The type constraint is that the output record contains all the labels in
063 the input record plus the names of added input ports. The type of a field
064 in the output is the same as the type of the added input port, if that field
065 is updated by an added input port. If a field in the output is not updated
066 by an input port, its type is the same as the corresponding field in the
067 input record. For example, if the input record has type
068 {item: string, value: int}, and this actor has two added input ports with
069 name/type: value/double and id/int, then the output record will have type
070 {item: string, value: double, id: int}
071
072 @author Michael Shilman, Steve Neuendorffer, Marten Lohstroh
073 @version $Id$
074 @since Ptolemy II 1.0
075 @Pt.ProposedRating Red (yuhong)
076 @Pt.AcceptedRating Red (cxh)
077 @see RecordAssembler
078 */
079public class RecordUpdater extends TypedAtomicActor {
080    /** Construct a RecordUpdater with the given container and name.
081     *  @param container The container.
082     *  @param name The name of this actor.
083     *  @exception IllegalActionException If this actor cannot be contained
084     *   by the proposed container.
085     *  @exception NameDuplicationException If the container already has an
086     *   actor with this name.
087     */
088    public RecordUpdater(CompositeEntity container, String name)
089            throws NameDuplicationException, IllegalActionException {
090        super(container, name);
091
092        output = new TypedIOPort(this, "output", false, true);
093        input = new TypedIOPort(this, "input", true, false);
094
095        _attachText("_iconDescription",
096                "<svg>\n" + "<rect x=\"0\" y=\"0\" width=\"6\" "
097                        + "height=\"40\" style=\"fill:red\"/>\n" + "</svg>\n");
098    }
099
100    ///////////////////////////////////////////////////////////////////
101    ////                     ports and parameters                  ////
102
103    /** The output port. Its type is constrained to be a RecordType. */
104    public TypedIOPort output;
105
106    /** The input port. Its type is constrained to be a RecordType. */
107    public TypedIOPort input;
108
109    ///////////////////////////////////////////////////////////////////
110    ////                         public methods                    ////
111
112    /** Clone the actor into the specified workspace. This calls the
113     *  base class and then sets the type constraints.
114     *  @param workspace The workspace for the new object.
115     *  @return A new actor.
116     *  @exception CloneNotSupportedException If a derived class has
117     *   an attribute that cannot be cloned.
118     */
119    @Override
120    public Object clone(Workspace workspace) throws CloneNotSupportedException {
121        RecordUpdater newObject = (RecordUpdater) super.clone(workspace);
122        return newObject;
123    }
124
125    /** Read one token from each input port, assemble them into a
126     *  RecordToken that contains the union of the original input record
127     *  and each of the update ports.
128     *  @exception IllegalActionException If there is no director.
129     */
130    @Override
131    public void fire() throws IllegalActionException {
132        super.fire();
133        Director director = getDirector();
134
135        if (director == null) {
136            throw new IllegalActionException(this, "No director!");
137        }
138
139        // Pack a HashMap with all of the record entries from
140        // the original record and all of the updating ports.
141        HashMap outputMap = new HashMap();
142
143        RecordToken record = (RecordToken) input.get(0);
144        Set recordLabels = record.labelSet();
145
146        for (Iterator i = recordLabels.iterator(); i.hasNext();) {
147            String name = (String) i.next();
148            Token value = record.get(name);
149            outputMap.put(name, value);
150        }
151
152        List inputPorts = inputPortList();
153        Iterator inputPortsIterator = inputPorts.iterator();
154
155        while (inputPortsIterator.hasNext()) {
156            TypedIOPort inputPort = (TypedIOPort) inputPortsIterator.next();
157
158            if (inputPort != input) {
159                outputMap.put(inputPort.getName(), inputPort.get(0));
160            }
161        }
162
163        // Construct a RecordToken and fill it with the values
164        // in the HashMap.
165        String[] labels = new String[outputMap.size()];
166        Token[] values = new Token[outputMap.size()];
167
168        int j = 0;
169
170        for (Iterator i = outputMap.entrySet().iterator(); i.hasNext();) {
171            Map.Entry entry = (Map.Entry) i.next();
172            labels[j] = (String) entry.getKey();
173            values[j] = (Token) entry.getValue();
174            j++;
175        }
176
177        RecordToken result = new RecordToken(labels, values);
178        output.send(0, result);
179    }
180
181    /** Return true if all input ports have tokens, false if some input
182     *  ports do not have a token.
183     *  @return True if all input ports have tokens.
184     *  @exception IllegalActionException If the hasToken() call to the
185     *   input port throws it.
186     *  @see ptolemy.actor.IOPort#hasToken(int)
187     */
188    @Override
189    public boolean prefire() throws IllegalActionException {
190        if (!super.prefire()) {
191            return false;
192        }
193        for (TypedIOPort port : inputPortList()) {
194            if (!port.hasToken(0)) {
195                return false;
196            }
197        }
198        return true;
199    }
200
201    ///////////////////////////////////////////////////////////////////
202    ////                         protected methods                 ////
203
204    /** Return the type constraints of this actor. The type constraint is
205     *  that the type of the output port is no less than the type of the
206     *  input port, and contains additional fields for each input port.
207     *  @return a list of type constraints
208     */
209    @Override
210    protected Set<Inequality> _customTypeConstraints() {
211        Set<Inequality> result = new HashSet<Inequality>();
212        result.add(
213                new Inequality(new MinimalOutputTerm(), output.getTypeTerm()));
214        return result;
215    }
216
217    /** Do not establish the usual default type constraints.
218     *  @return null
219     */
220    @Override
221    protected Set<Inequality> _defaultTypeConstraints() {
222        return null;
223    }
224
225    ///////////////////////////////////////////////////////////////////
226    ////                         inner classes                     ////
227    // This class implements a monotonic function of the input port
228    // types. The value of the function is a record type that contains
229    // all the labels in the input record, plus the names of the added
230    // input ports. The type of a field in the function value is the
231    // same as the type of an added input port, if the label of that
232    // field is the same as that input port, or, the type of a field
233    // is the same as that of the corresponding field in the input
234    // record.
235    // To ensure that this function is monotonic, the value of the function
236    // is bottom if the type of the port with name "input" is bottom. If
237    // the type of this port is not bottom (it must be a record), the value
238    // of the function is computed as described above.
239    private class MinimalOutputTerm extends MonotonicFunction {
240        ///////////////////////////////////////////////////////////////
241        ////                       public inner methods            ////
242
243        /** Return the function result.
244         *  @return A Type.
245         */
246        @Override
247        public Object getValue() throws IllegalActionException {
248
249            String label;
250            RecordType inputType;
251            Iterator<String> labels;
252
253            Map<String, Type> fields = new HashMap<String, Type>();
254
255            // FIXME
256            if (!(input.getType() instanceof RecordType)) {
257                return BaseType.UNKNOWN;
258            }
259
260            inputType = (RecordType) input.getType();
261
262            // fetch the types for each label found in the
263            // RecordType on the main input
264            for (labels = inputType.labelSet().iterator(); labels.hasNext();) {
265                label = labels.next();
266                fields.put(label, inputType.get(label));
267            }
268
269            List<TypedIOPort> inputPorts = inputPortList();
270
271            // fetch the type for each of the additional input ports
272            for (TypedIOPort port : inputPorts) {
273                if (port != input) {
274                    label = port.getName();
275                    if (fields.containsKey(label)) {
276                        // only update type if compatible
277                        if (port.getType().isCompatible(fields.get(label))) {
278                            fields.put(label, port.getType());
279                        }
280                    } else {
281                        fields.put(label, port.getType());
282                    }
283                }
284            }
285
286            // Construct the RecordType
287            return new RecordType(fields.keySet().toArray(new String[0]),
288                    fields.values().toArray(new Type[0]));
289        }
290
291        /** Return all the InequalityTerms for all input ports in an array.
292         *  @return An array of InequalityTerm.
293         */
294        @Override
295        public InequalityTerm[] getVariables() {
296            Iterator<TypedIOPort> inputPorts = inputPortList().iterator();
297            LinkedList<InequalityTerm> result = new LinkedList<InequalityTerm>();
298            while (inputPorts.hasNext()) {
299                TypedIOPort port = inputPorts.next();
300                InequalityTerm term = port.getTypeTerm();
301                if (term.isSettable()) {
302                    result.add(term);
303                }
304            }
305            return result.toArray(new InequalityTerm[0]);
306        }
307    }
308}