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}