001/* A composite actor that applies models dynamically. 002 003 Copyright (c) 2003-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 */ 027package ptolemy.actor.lib.hoc; 028 029import java.io.IOException; 030import java.io.StringWriter; 031import java.io.Writer; 032import java.lang.reflect.Constructor; 033import java.util.Iterator; 034 035import ptolemy.actor.CompositeActor; 036import ptolemy.actor.Director; 037import ptolemy.actor.IORelation; 038import ptolemy.actor.TypedCompositeActor; 039import ptolemy.actor.TypedIOPort; 040import ptolemy.actor.lib.Const; 041import ptolemy.data.BooleanToken; 042import ptolemy.data.IntToken; 043import ptolemy.data.StringToken; 044import ptolemy.data.expr.Parameter; 045import ptolemy.data.type.BaseType; 046import ptolemy.kernel.CompositeEntity; 047import ptolemy.kernel.Entity; 048import ptolemy.kernel.Port; 049import ptolemy.kernel.util.Attribute; 050import ptolemy.kernel.util.IllegalActionException; 051import ptolemy.kernel.util.InternalErrorException; 052import ptolemy.kernel.util.NameDuplicationException; 053import ptolemy.kernel.util.Workspace; 054import ptolemy.moml.MoMLChangeRequest; 055import ptolemy.moml.MoMLParser; 056import ptolemy.moml.filter.BackwardCompatibility; 057 058/////////////////////////////////////////////////////////////////// 059//// MobileModel 060 061/** 062 This is a composite actor with an input port that accepts MoML descriptions 063 of changes that are applied to the contents. Rather than 064 specified before executing, the inside model can be dynamically changed 065 either locally or remotely. Currently, the model that dynamically applied 066 to this actor is specified by a moml string from the <i>modelString</i> 067 input. 068 069 Currently, it only accepts models with one input and one output, and 070 requires the model name its input port as "input", output port as "output". 071 072 @author Yang Zhao 073 @version $Id$ 074 @since Ptolemy II 4.0 075 @Pt.ProposedRating Red (eal) 076 @Pt.AcceptedRating Red (reviewmoderator) 077 */ 078public class MobileModel extends TypedCompositeActor { 079 /** Construct an actor in the specified workspace with 080 * no container and an empty string as a name. You can then change 081 * the name with setName(). If the workspace argument is null, then 082 * use the default workspace. 083 * @param workspace The workspace that will list the actor. 084 * @exception IllegalActionException If populating the actor with 085 * ports and parameters fails. 086 */ 087 public MobileModel(Workspace workspace) throws IllegalActionException { 088 super(workspace); 089 _init(); 090 } 091 092 /** Construct an actor with a name and a container. 093 * The container argument must not be null, or a 094 * NullPointerException will be thrown. 095 * @param container The container. 096 * @param name The name of this actor. 097 * @exception IllegalActionException If the container is incompatible 098 * with this actor or if populating the actor with 099 * ports and parameters fails. 100 * @exception NameDuplicationException If the name coincides with 101 * an actor already in the container. 102 */ 103 public MobileModel(CompositeEntity container, String name) 104 throws IllegalActionException, NameDuplicationException { 105 super(container, name); 106 _init(); 107 } 108 109 /////////////////////////////////////////////////////////////////// 110 //// public variables //// 111 112 /** The input port for incoming data to the inside model. 113 */ 114 public TypedIOPort input; 115 116 /** The input port for model changing request of the inside model. 117 * The type is string. 118 */ 119 public TypedIOPort modelString; 120 121 /** The output port for the result after firing the inside model 122 * upon the incoming data. Notice that the type is determined by 123 * the type of the <i>defaultValue</i> parameter. 124 */ 125 public TypedIOPort output; 126 127 /** The inside Director for executing the inside model. 128 */ 129 public Parameter director; 130 131 /** This Parameter specifies whether to replace the previous model 132 * when there is model changing request or not. The type of this 133 * parameter is boolean. Select this parameter if it does replace. 134 */ 135 public Parameter refresh; 136 137 /** the Parameter specifies whether to connect the input and output 138 * to the inside model. The type of this parameter is boolean. 139 */ 140 public Parameter connectPorts; 141 142 /** The default output token when there is no inside model 143 * defined. The default value is 0, and the default type is 144 * int. Notice that the type of the output port 145 * is determined by the type of this parameter. 146 */ 147 public Parameter defaultValue; 148 149 /////////////////////////////////////////////////////////////////// 150 //// public methods //// 151 152 /** Clone the actor into the specified workspace. This calls the 153 * base class and then sets the value public variable in the new 154 * object to equal the cloned parameter in that new object. 155 * @param workspace The workspace for the new object. 156 * @return A new actor. 157 * @exception CloneNotSupportedException If a derived class contains 158 * an attribute that cannot be cloned. 159 */ 160 @Override 161 public Object clone(Workspace workspace) throws CloneNotSupportedException { 162 MobileModel newObject = (MobileModel) super.clone(workspace); 163 return newObject; 164 } 165 166 /** Save the model here if there is a new model to apply. and then call 167 * super.fire(). 168 * @exception IllegalActionException If there is no director, or if 169 * the director's fire() method throws it, or if the actor is not 170 * opaque. 171 */ 172 @Override 173 public void fire() throws IllegalActionException { 174 if (_debugging) { 175 _debug("Invoking fire"); 176 } 177 178 if (modelString.getWidth() < 1) { 179 throw new IllegalActionException(getName() + "need to have" 180 + "the modelString port be connected"); 181 } else if (modelString.hasToken(0)) { 182 StringToken str = null; 183 184 try { 185 str = (StringToken) modelString.get(0); 186 187 _parser.reset(); 188 189 CompositeActor model = (CompositeActor) _parser 190 .parse(str.stringValue()); 191 StringWriter writer = new StringWriter(); 192 193 try { 194 model.exportMoML(writer, 1); 195 } catch (Exception ex) { 196 throw new IllegalActionException(this, ex, 197 "Failed to export MoML for " + model); 198 } 199 200 String modelMoML = writer.toString(); 201 202 if (((BooleanToken) connectPorts.getToken()).booleanValue()) { 203 _moml = "<group>\n" + modelMoML 204 + "<relation name=\"newR1\" " 205 + "class=\"ptolemy.actor.TypedIORelation\">\n" 206 + "</relation>\n" + "<relation name=\"newR2\" " 207 + "class=\"ptolemy.actor.TypedIORelation\">\n" 208 + "</relation>\n" 209 + "<link port=\"input\" relation=\"newR1\"/>\n" 210 + "<link port=\"" + model.getName() 211 + ".input\" relation=\"newR1\"/>\n" 212 + "<link port=\"" + model.getName() 213 + ".output\" relation=\"newR2\"/>\n" 214 + "<link port=\"output\" relation=\"newR2\"/>\n" 215 + "</group>"; 216 } else { 217 _moml = "<group>\n" + modelMoML + "</group>"; 218 } 219 } catch (Exception ex) { 220 if (_debugging) { 221 _debug("Problem parsing " 222 + (str == null ? "null" : str.stringValue())); 223 } 224 225 throw new IllegalActionException(this, ex, "Problem parsing " 226 + (str == null ? "null" : str.stringValue())); 227 } 228 } 229 230 super.fire(); 231 } 232 233 /** Return true. 234 */ 235 @Override 236 public boolean isOpaque() { 237 return true; 238 } 239 240 /** Update the model here to achieve consistency. 241 * @exception IllegalActionException If there is no director, 242 * or if the director's postfire() method throws it, or if this actor 243 * is not opaque. 244 */ 245 @Override 246 public boolean postfire() throws IllegalActionException { 247 if (!_stopRequested && _moml != null) { 248 //remove the old model inside first, if there is one. 249 if (((BooleanToken) refresh.getToken()).booleanValue()) { 250 String delete = _requestToRemoveAll(this); 251 MoMLChangeRequest removeRequest = new MoMLChangeRequest(this, // originator 252 this, // context 253 delete, // MoML code 254 null); 255 requestChange(removeRequest); 256 } 257 258 //update the inside model change. 259 MoMLChangeRequest request2 = new MoMLChangeRequest(this, // originator 260 this, // context 261 _moml, // MoML code 262 null); 263 requestChange(request2); 264 265 if (_debugging) { 266 _debug("issues change request to modify the model"); 267 } 268 269 _moml = null; 270 271 // the model topology changes, 272 _modelChanged = true; 273 } 274 275 return super.postfire(); 276 } 277 278 /** Return true if the actor either of its input port has token. 279 * @exception IllegalActionException Not thrown in this base class. 280 */ 281 @Override 282 public boolean prefire() throws IllegalActionException { 283 if (_debugging) { 284 _debug("Invoking prefire"); 285 } 286 287 // if model just changed, there is a pure event registered 288 // for this model. this model needs firing immediately. 289 // FIXME: 290 // a correct solution is to register a pure event to local event queue 291 // and another pure event to upper level. 292 // Again, this involves hierarchical execution .... 293 if (_modelChanged) { 294 _modelChanged = false; 295 return true; 296 } else { 297 return super.prefire(); 298 } 299 300 //if (input.hasToken(0) || modelString.hasToken(0)) { 301 // return super.prefire(); 302 //} 303 //return false; 304 } 305 306 /** preinitialize this actor. create the director as specified 307 * by the <i>director</i> parameter. 308 * @exception IllegalActionException If can't create the director, or 309 * if the director's preinitialize() method throws it. 310 */ 311 @Override 312 public void preinitialize() throws IllegalActionException { 313 _director = null; 314 _createDirector(); 315 316 _moml = null; 317 318 try { 319 _parser = new MoMLParser(); 320 MoMLParser.setMoMLFilters(BackwardCompatibility.allFilters()); 321 322 // When no model applied, output the default value. 323 if (((BooleanToken) connectPorts.getToken()).booleanValue()) { 324 Const constActor = new Const(this, "Const"); 325 constActor.value 326 .setExpression(defaultValue.getToken().toString()); 327 connect(input, constActor.trigger); 328 connect(constActor.output, output); 329 } //otherwise, do nothing. 330 } catch (Exception ex) { 331 throw new IllegalActionException(this, ex, 332 "preinitialize() failed"); 333 } 334 335 //connect(input, output); 336 super.preinitialize(); 337 } 338 339 /** Clean up tha changes that have been made. 340 * @exception IllegalActionException If there is no director, 341 * or if the director's wrapup() method throws it, or if this 342 * actor is not opaque. 343 */ 344 @Override 345 public void wrapup() throws IllegalActionException { 346 // clean the inside content. 347 String delete = _requestToRemoveAll(this); 348 MoMLChangeRequest removeRequest = new MoMLChangeRequest(this, // originator 349 this, // context 350 delete, // MoML code 351 null); 352 requestChange(removeRequest); 353 super.wrapup(); 354 355 if (_director != null) { 356 try { 357 _director.setContainer(null); 358 } catch (NameDuplicationException ex) { 359 throw new InternalErrorException(ex); 360 } 361 } 362 } 363 364 /////////////////////////////////////////////////////////////////// 365 //// protected methods //// 366 367 /** Export the moml description of this. 368 */ 369 @Override 370 protected void _exportMoMLContents(Writer output, int depth) 371 throws IOException { 372 Iterator attributes = attributeList().iterator(); 373 374 while (attributes.hasNext()) { 375 Attribute attribute = (Attribute) attributes.next(); 376 attribute.exportMoML(output, depth); 377 } 378 379 Iterator ports = portList().iterator(); 380 381 while (ports.hasNext()) { 382 Port port = (Port) ports.next(); 383 port.exportMoML(output, depth); 384 } 385 386 output.write(exportLinks(depth, null)); 387 } 388 389 /////////////////////////////////////////////////////////////////// 390 //// private methods //// 391 392 /** create the inside director of this composite actor according 393 * to the <i>director</i> parameter. 394 * @exception IllegalActionException If cannot find the director 395 * class with the specified name by the <i>director</i> parameter, 396 * or if there is name duplication for the director. 397 */ 398 private void _createDirector() throws IllegalActionException { 399 try { 400 String directorName = ((StringToken) director.getToken()) 401 .stringValue(); 402 Class directorClass = Class.forName(directorName); 403 Class[] argClasses = new Class[2]; 404 argClasses[0] = CompositeEntity.class; 405 argClasses[1] = String.class; 406 407 Constructor constructor = directorClass.getConstructor(argClasses); 408 409 if (constructor != null) { 410 if (_debugging) { 411 _debug("find constructor for the specified director"); 412 } 413 414 Object[] args = new Object[2]; 415 args[0] = this; 416 args[1] = "new director"; 417 _director = (Director) constructor.newInstance(args); 418 419 if (_debugging) { 420 _debug("create a instance of the specified director"); 421 } 422 } 423 } catch (Exception ex) { 424 throw new IllegalActionException("get an illegal action exception" 425 + "when create director" + ex); 426 } 427 } 428 429 /** Create the parameters and ports. This method is called by the 430 * constructors. 431 * @exception IllegalActionException If creating the parameters 432 * and ports throws it. 433 */ 434 private void _init() throws IllegalActionException { 435 try { 436 input = new TypedIOPort(this, "input", true, false); 437 modelString = new TypedIOPort(this, "modelString", true, false); 438 modelString.setTypeEquals(BaseType.STRING); 439 defaultValue = new Parameter(this, "defaultValue", new IntToken(0)); 440 output = new TypedIOPort(this, "output", false, true); 441 output.setTypeAtLeast(defaultValue); 442 refresh = new Parameter(this, "refresh", new BooleanToken(true)); 443 refresh.setTypeEquals(BaseType.BOOLEAN); 444 connectPorts = new Parameter(this, "connectPorts", 445 new BooleanToken(true)); 446 connectPorts.setTypeEquals(BaseType.BOOLEAN); 447 448 // create a defaultDirector. Without this director, it may get 449 // an infinite loop when preinitialize, etc. is called in case the 450 // specified director is not successfully constructed. Even when the 451 // specified director is construced successfully, it cannot be removed 452 // in wrapup() without this default director. 453 //The default director may not work when we need multi tokens to fire 454 //the inside model because the receiver it creates is an instance of 455 //Mailbox, which can only hold one token. In this case, specify a proper 456 //director using the <i>director<i> parameter. 457 new Director(this, "defaultDirector"); 458 director = new Parameter(this, "director", 459 new StringToken("ptolemy.actor.Director")); 460 setClassName("ptolemy.actor.lib.hoc.MobileModel"); 461 } catch (NameDuplicationException e) { 462 // This should not be thrown. 463 throw new InternalErrorException(e); 464 } 465 } 466 467 /** Construct a MoML string for the composite actor to delete 468 * of its entities and relations. 469 * @param actor The composite actor. 470 */ 471 private String _requestToRemoveAll(CompositeActor actor) { 472 if (_debugging) { 473 _debug("create request to remove old model"); 474 } 475 476 StringBuffer delete = new StringBuffer("<group>"); 477 478 // FIXME: Should this also remove class definitions? 479 // To do that, use classDefinitionList(). 480 Iterator entities = actor.entityList().iterator(); 481 482 while (entities.hasNext()) { 483 Entity entity = (Entity) entities.next(); 484 delete.append("<deleteEntity name=\"" + entity.getName() 485 + "\" class=\"" + entity.getClass().getName() + "\"/>"); 486 } 487 488 Iterator relations = actor.relationList().iterator(); 489 490 while (relations.hasNext()) { 491 IORelation relation = (IORelation) relations.next(); 492 delete.append("<deleteRelation name=\"" + relation.getName() 493 + "\" class=\"ptolemy.actor.TypedIORelation\"/>"); 494 } 495 496 delete.append("</group>"); 497 return delete.toString(); 498 } 499 500 /////////////////////////////////////////////////////////////////// 501 //// private variables //// 502 503 /** The inside director. 504 */ 505 private Director _director; 506 507 /** The moml string for the inside model that contained by this actor. 508 * 509 */ 510 private String _moml; 511 512 /** The moml parser for parsing the moml string of the inside model. 513 * 514 */ 515 private MoMLParser _parser; 516 517 private boolean _modelChanged = false; 518}