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