001/* An actor that assembles multiple inputs to a RecordToken. 002 003 Copyright (c) 1998-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.HashSet; 031import java.util.Map; 032import java.util.Map.Entry; 033import java.util.Set; 034import java.util.TreeMap; 035 036import ptolemy.actor.Manager; 037import ptolemy.actor.TypedAtomicActor; 038import ptolemy.actor.TypedIOPort; 039import ptolemy.actor.util.ConstructAssociativeType; 040import ptolemy.actor.util.ExtractFieldType; 041import ptolemy.data.RecordToken; 042import ptolemy.data.Token; 043import ptolemy.data.type.RecordType; 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//// RecordAssembler 053 054/** 055 On each firing, read one token from each connected input port and assemble 056 them into a RecordToken. Disconnected input ports are ignored. The labels for 057 the RecordToken much match the names of the input ports. This is achieved 058 using two type constraints: 059 060 <ul> 061 <li><tt>output ≥ {x = typeOf(inputPortX), y = typeOf(inputPortY), ..} 062 </tt>, which requires the types of the input ports to be compatible 063 with the corresponding types in the output record. 064 </li> 065 <li><tt>each input ≥ the type of the corresponding field inside the 066 output record</tt>, which together with the first constraint forces 067 the input types to be exactly equal to the types of the corresponding 068 fields in the output record. This constraint is intended to back- 069 propagate type information upstream, not to assure type compatibility. 070 Therefore, this constraint is only set up for input ports that do not 071 already have a type declared.</li> 072 </ul> 073 Note that the output record is not required to contain a corresponding 074 field for every input, as downstream actors might require fewer fields 075 in the record they accept for input. 076 <p> 077 To use this class, instantiate it, and then add input ports 078 (instances of TypedIOPort). This actor is polymorphic. The type constraint 079 is that the type of each record field is no less than the type of the 080 corresponding input port. 081 </p> 082 <p>Note that if the display name of a port is set, display name is used in 083 the type constraints instead of name. This is useful in case fields to 084 add to the record contain a period, because periods are not allowed in 085 port names.</p> 086 087 088 @author Yuhong Xiong, Marten Lohstroh 089 @version $Id$ 090 @since Ptolemy II 1.0 091 @Pt.ProposedRating Yellow (yuhong) 092 @Pt.AcceptedRating Yellow (cxh) 093 @see RecordDisassembler 094 */ 095public class RecordAssembler extends TypedAtomicActor { 096 097 /** Construct a RecordAssembler with the given container and name. 098 * @param container The container. 099 * @param name The name of this actor. 100 * @exception IllegalActionException If this actor cannot be contained 101 * by the proposed container. 102 * @exception NameDuplicationException If the container already has an 103 * actor with this name. 104 */ 105 public RecordAssembler(CompositeEntity container, String name) 106 throws NameDuplicationException, IllegalActionException { 107 super(container, name); 108 109 output = new TypedIOPort(this, "output", false, true); 110 111 _attachText("_iconDescription", 112 "<svg>\n" + "<rect x=\"0\" y=\"0\" width=\"6\" " 113 + "height=\"40\" style=\"fill:red\"/>\n" + "</svg>\n"); 114 } 115 116 /////////////////////////////////////////////////////////////////// 117 //// ports and parameters //// 118 119 /** The output port. */ 120 public TypedIOPort output; 121 122 /////////////////////////////////////////////////////////////////// 123 //// public methods //// 124 125 /** Clone the actor into the specified workspace. 126 * @param workspace The workspace for the new object. 127 * @return A new actor. 128 * @exception CloneNotSupportedException If a derived class contains 129 * an attribute that cannot be cloned. 130 */ 131 @Override 132 public Object clone(Workspace workspace) throws CloneNotSupportedException { 133 RecordAssembler newObject = (RecordAssembler) super.clone(workspace); 134 newObject._portMap = _newPortMap(); 135 return newObject; 136 } 137 138 /** Read one token from each input port, assemble them into a RecordToken, 139 * and send the RecordToken to the output. 140 * @exception IllegalActionException If there is no director. 141 */ 142 @Override 143 public void fire() throws IllegalActionException { 144 super.fire(); 145 146 int i = 0; 147 Set<Entry<String, TypedIOPort>> entries = _portMap.entrySet(); 148 String[] labels = new String[entries.size()]; 149 Token[] values = new Token[entries.size()]; 150 151 for (Entry<String, TypedIOPort> entry : entries) { 152 labels[i] = entry.getKey(); 153 values[i] = entry.getValue().get(0); 154 i++; 155 } 156 157 RecordToken result = new RecordToken(labels, values); 158 159 output.send(0, result); 160 } 161 162 /** React to a name change of contained ports. Update the internal 163 * mapping from names and aliases to port objects, and invalidate 164 * the resolved types. 165 * @param object The object that changed. 166 */ 167 @Override 168 public void notifyOfNameChange(NamedObj object) { 169 if (object instanceof TypedIOPort) { 170 _mapPorts(); 171 } 172 } 173 174 /** Return true if all connected input ports have tokens, false if some 175 * connected input ports do not have a token. 176 * @return True if all connected input ports have tokens and the 177 * parent method returns true. 178 * @exception IllegalActionException If the hasToken() call to the 179 * input port throws it. 180 * @see ptolemy.actor.IOPort#hasToken(int) 181 */ 182 @Override 183 public boolean prefire() throws IllegalActionException { 184 boolean superReturnValue = super.prefire(); 185 for (TypedIOPort port : _portMap.values()) { 186 if (!port.hasToken(0)) { 187 if (_debugging) { 188 _debug("Port " + port.getName() 189 + " does not have a token, prefire()" 190 + " will return false."); 191 } 192 return false; 193 } 194 } 195 196 return true && superReturnValue; 197 } 198 199 /////////////////////////////////////////////////////////////////// 200 //// protected methods //// 201 202 /** Set up and return two type constraints. 203 * <ul> 204 * <li><tt>output ≥ {x = typeOf(inputPortX), y = typeOf(inputPortY), ..} 205 * </tt>, which requires the types of the input ports to be compatible 206 * with the corresponding types in the output record. 207 * </li> 208 * <li><tt>each input ≥ the type of the corresponding field inside the 209 * output record</tt>, which together with the first constraint forces 210 * the input types to be exactly equal to the types of the corresponding 211 * fields in the output record. This constraint is intended to back- 212 * propagate type information upstream, not to assure type compatibility. 213 * Therefore, this constraint is only set up for input ports that do not 214 * already have a type declared.</li> 215 * </ul> 216 * Note that the output record is not required to contain a corresponding 217 * field for every input, as downstream actors might require fewer fields 218 * in the record they accept for input. 219 * @return A set of type constraints 220 * @see ConstructAssociativeType 221 * @see ExtractFieldType 222 */ 223 @Override 224 protected Set<Inequality> _customTypeConstraints() { 225 Set<Inequality> result = new HashSet<Inequality>(); 226 227 // make sure the ports are mapped 228 _mapPorts(); 229 230 // constrain the type of every input to be greater than or equal to 231 // the resolved type of the corresponding field in the output record 232 for (Entry<String, TypedIOPort> entry : _portMap.entrySet()) { 233 String inputName = entry.getKey(); 234 TypedIOPort input = entry.getValue(); 235 // only include ports that have no type declared 236 if (input.getTypeTerm().isSettable()) { 237 result.add( 238 new Inequality(new ExtractFieldType(output, inputName), 239 input.getTypeTerm())); 240 } 241 } 242 243 // constrain the fields in the output record to be greater than or 244 // equal to the declared or resolved types of the input ports: 245 // output >= {x = typeOf(outputPortX), y = typeOf(outputPortY), ..} 246 result.add(new Inequality(new ConstructAssociativeType( 247 _portMap.values(), RecordType.class), output.getTypeTerm())); 248 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 /** Return a new _portMap, which is a map between 261 * port names and strings. Derived classes 262 * like OrderedRecordAssembler would return 263 * a map with a different ordering. 264 * @return a Map from port names to TypedIOPorts. 265 */ 266 protected Map<String, TypedIOPort> _newPortMap() { 267 // RecordToken._initializeStorage() should probably 268 // use a similar Collection class. 269 return new TreeMap<String, TypedIOPort>(); 270 } 271 272 /** Map port names or aliases to port objects. If the mapping 273 * has changed, then invalidate the resolved types, which 274 * forces new type constraints with appropriate field names 275 * to be generated. 276 */ 277 protected void _mapPorts() { 278 // Retrieve the manager. 279 Manager manager = this.getManager(); 280 281 // Generate a new mapping from names/aliases to ports. 282 Map<String, TypedIOPort> oldMap = _portMap; 283 _portMap = _newPortMap(); 284 for (TypedIOPort p : this.inputPortList()) { 285 String name = p.getName(); 286 String alias = p.getDisplayName(); 287 // ignore unconnected ports 288 if (p.numberOfSources() < 1) { 289 continue; 290 } 291 if (alias == null || alias.equals("")) { 292 _portMap.put(name, p); 293 } else { 294 _portMap.put(alias, p); 295 } 296 } 297 298 // Only invalidate resolved types if there actually was a name change. 299 // As a result, new type constraints will be generated. 300 if (manager != null && (oldMap == null || !_portMap.equals(oldMap))) { 301 manager.invalidateResolvedTypes(); 302 } 303 } 304 305 /////////////////////////////////////////////////////////////////// 306 //// protected variables //// 307 308 /** Keeps track of which name or alias is associated with which port. */ 309 protected Map<String, TypedIOPort> _portMap; 310 311}