001/* A base class for actors that perform life-cycle management. 002 003 Copyright (c) 2003-2016 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 028 */ 029package ptolemy.actor.lib.hoc; 030 031import java.lang.ref.WeakReference; 032import java.util.Iterator; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.ListIterator; 036 037import ptolemy.actor.Director; 038import ptolemy.actor.IOPort; 039import ptolemy.actor.TypedCompositeActor; 040import ptolemy.actor.parameters.ParameterPort; 041import ptolemy.actor.parameters.PortParameter; 042import ptolemy.data.StringToken; 043import ptolemy.data.Token; 044import ptolemy.data.expr.Variable; 045import ptolemy.kernel.CompositeEntity; 046import ptolemy.kernel.util.Attribute; 047import ptolemy.kernel.util.ChangeListener; 048import ptolemy.kernel.util.ChangeRequest; 049import ptolemy.kernel.util.Changeable; 050import ptolemy.kernel.util.IllegalActionException; 051import ptolemy.kernel.util.NameDuplicationException; 052import ptolemy.kernel.util.NamedObj; 053import ptolemy.kernel.util.Settable; 054import ptolemy.kernel.util.Workspace; 055 056/////////////////////////////////////////////////////////////////// 057//// LifeCycleManager 058 059/** 060 This is a composite actor with some services for life-cycle management. 061 062 FIXME: More. 063 064 @author Edward A. Lee, Yang Zhao 065 @version $Id$ 066 @since Ptolemy II 4.0 067 @see ModelReference 068 @see ptolemy.actor.lib.SetVariable 069 @Pt.ProposedRating Yellow (eal) 070 @Pt.AcceptedRating Red (eal) 071 */ 072public class LifeCycleManager extends TypedCompositeActor { 073 /** Construct an actor in the default workspace with no 074 * container and an empty string as its name. Add the actor to the 075 * workspace directory. You should set the local director or 076 * executive director before attempting to send data to the actor or 077 * to execute it. Increment the version number of the workspace. 078 */ 079 public LifeCycleManager() { 080 super(); 081 } 082 083 /** Construct a LifeCycleManager in the specified workspace with 084 * no container and an empty string as a name. You can then change 085 * the name with setName(). If the workspace argument is null, then 086 * use the default workspace. You should set the local director or 087 * executive director before attempting to send data to the actor 088 * or to execute it. Add the actor to the workspace directory. 089 * Increment the version number of the workspace. 090 * @param workspace The workspace that will list the actor. 091 */ 092 public LifeCycleManager(Workspace workspace) { 093 super(workspace); 094 } 095 096 /** Construct a LifeCycleManager with a name and a container. 097 * The container argument must not be null, or a 098 * NullPointerException will be thrown. This actor will use the 099 * workspace of the container for synchronization and version counts. 100 * If the name argument is null, then the name is set to the empty string. 101 * Increment the version of the workspace. This actor will have no 102 * local director initially, and its executive director will be simply 103 * the director of the container. 104 * 105 * @param container The container. 106 * @param name The name of this actor. 107 * @exception IllegalActionException If the container is incompatible 108 * with this actor. 109 * @exception NameDuplicationException If the name coincides with 110 * an actor already in the container. 111 */ 112 public LifeCycleManager(CompositeEntity container, String name) 113 throws IllegalActionException, NameDuplicationException { 114 super(container, name); 115 } 116 117 /////////////////////////////////////////////////////////////////// 118 //// public methods //// 119 120 /** Override the base class to delegate to the container AND 121 * also record the listener locally. 122 * @param listener The listener to add. 123 * @see #removeChangeListener(ChangeListener) 124 * @see #requestChange(ChangeRequest) 125 * @see Changeable 126 */ 127 @Override 128 public void addChangeListener(ChangeListener listener) { 129 NamedObj container = getContainer(); 130 131 if (container != null) { 132 container.addChangeListener(listener); 133 } 134 synchronized (_changeLock) { 135 if (_changeListeners == null) { 136 _changeListeners = new LinkedList<WeakReference<ChangeListener>>(); 137 } else { 138 // In case there is a previous instance, remove it. 139 removeChangeListener(listener); 140 } 141 142 _changeListeners.add(0, new WeakReference(listener)); 143 } 144 } 145 146 /** Override the base class to not delegate up the hierarchy 147 * but rather to handle the request locally. 148 * @see #addChangeListener(ChangeListener) 149 * @see #requestChange(ChangeRequest) 150 * @see #isDeferringChangeRequests() 151 * @see Changeable 152 */ 153 @Override 154 public void executeChangeRequests() { 155 // Have to execute a copy of the change request list 156 // because the list may be modified during execution. 157 List<ChangeRequest> copy = _copyChangeRequestList(); 158 159 if (copy != null) { 160 _executeChangeRequests(copy); 161 // Change requests may have been queued during the execute. 162 // Execute those by a recursive call. 163 executeChangeRequests(); 164 } 165 } 166 167 /** Override the base class to not delegate up the hierarchy and to 168 * indicate only whether this composite is locally deferring change requests. 169 * Note that even if this returns false, change requests may be deferred 170 * because the container is deferring change requests. 171 * @return True if change requests are being deferred. 172 * @see #setDeferringChangeRequests(boolean) 173 * @see Changeable 174 */ 175 @Override 176 public boolean isDeferringChangeRequests() { 177 return _deferChangeRequests; 178 } 179 180 /** Override the base class to remove the listener in 181 * the container AND locally. 182 * @param listener The listener to remove. 183 * @see #addChangeListener(ChangeListener) 184 * @see Changeable 185 */ 186 @Override 187 public synchronized void removeChangeListener(ChangeListener listener) { 188 NamedObj container = getContainer(); 189 if (container != null) { 190 container.removeChangeListener(listener); 191 } 192 synchronized (_changeLock) { 193 if (_changeListeners != null) { 194 ListIterator<WeakReference<ChangeListener>> listeners = _changeListeners 195 .listIterator(); 196 197 while (listeners.hasNext()) { 198 WeakReference<ChangeListener> reference = listeners.next(); 199 200 if (reference.get() == listener) { 201 listeners.remove(); 202 } else if (reference.get() == null) { 203 listeners.remove(); 204 } 205 } 206 } 207 } 208 } 209 210 /** Override the base class to delegate up the hierarchy only if this 211 * composite is not deferring change requests, but the 212 * the container is. Otherwise, if this 213 * composite is deferring change requests, the defer the change, 214 * regardless of what the container is doing. Otherwise, execute 215 * the change. 216 * @param change The requested change. 217 * @see #executeChangeRequests() 218 * @see #setDeferringChangeRequests(boolean) 219 * @see Changeable 220 */ 221 @Override 222 public void requestChange(ChangeRequest change) { 223 NamedObj container = getContainer(); 224 if (container != null && !_deferChangeRequests 225 && container.isDeferringChangeRequests()) { 226 super.requestChange(change); 227 return; 228 } 229 230 // Have to ensure that 231 // the collection of change listeners doesn't change during 232 // this execution. But we don't want to hold a lock on the 233 // this NamedObj during execution of the change because this 234 // could lead to deadlock. So we synchronize to _changeLock. 235 List<ChangeRequest> copy = null; 236 synchronized (_changeLock) { 237 // Queue the request. 238 // Create the list of requests if it doesn't already exist 239 if (_changeRequests == null) { 240 _changeRequests = new LinkedList<ChangeRequest>(); 241 } 242 243 _changeRequests.add(change); 244 if (!_deferChangeRequests) { 245 copy = _copyChangeRequestList(); 246 } 247 } 248 249 // Do not want to hold the _changeLock while 250 // executing change requests. See comments inside 251 // executeChangeRequests(). 252 if (copy != null) { 253 _executeChangeRequests(copy); 254 } 255 } 256 257 /** Override the base class to not delegate to the container. 258 * @param isDeferring If true, defer change requests. 259 * @see #addChangeListener(ChangeListener) 260 * @see #executeChangeRequests() 261 * @see #isDeferringChangeRequests() 262 * @see #requestChange(ChangeRequest) 263 * @see Changeable 264 */ 265 @Override 266 public void setDeferringChangeRequests(boolean isDeferring) { 267 // Make sure to avoid modification of this flag in the middle 268 // of a change request or change execution. 269 List<ChangeRequest> copy = null; 270 synchronized (_changeLock) { 271 _deferChangeRequests = isDeferring; 272 273 if (isDeferring == false) { 274 // Must not hold _changeLock while executing change requests. 275 copy = _copyChangeRequestList(); 276 } 277 } 278 if (copy != null) { 279 _executeChangeRequests(copy); 280 } 281 } 282 283 /////////////////////////////////////////////////////////////////// 284 //// protected methods //// 285 286 /** Run a complete execution of the contained model. A complete 287 * execution consists of invocation of super.initialize(), repeated 288 * invocations of super.prefire(), super.fire(), and super.postfire(), 289 * followed by super.wrapup(). The invocations of prefire(), fire(), 290 * and postfire() are repeated until either the model indicates it 291 * is not ready to execute (prefire() returns false), or it requests 292 * a stop (postfire() returns false or stop() is called). 293 * 294 * <p>Note that we do not call preinitialize() here because if we 295 * do, then PortParameter.preinitialize() will set the value of 296 * the PortParameter to the value of the persistent expression. 297 * Instead, initialize() should check that things like the Matlab 298 * engine have been started and if necessary start them because 299 * wrapup() might have closed the engine.</p> 300 * 301 * @exception IllegalActionException If there is no director, or if 302 * the director's action methods throw it. 303 * @return One of COMPLETED, STOP_ITERATING, or NOT_READY to 304 * indicate that either the execution completed or stop 305 * was externally requested, stopped because 306 * postfire() returned false, or stopped because prefire() returned 307 * false, respectively. 308 */ 309 protected int _executeInsideModel() throws IllegalActionException { 310 try { 311 // Make sure that change requests are not executed when requested, 312 // but rather only executed when executeChangeRequests() is called. 313 setDeferringChangeRequests(true); 314 315 Director insideDirector = getDirector(); 316 Director outsideDirector = getExecutiveDirector(); 317 if (insideDirector == outsideDirector) { 318 throw new IllegalActionException(this, 319 "An inside director is required to execute the inside model."); 320 } 321 322 // Force the inside director to behave as if it were at the top level. 323 insideDirector.setEmbedded(false); 324 325 // See method comment about why we don't call preinitialize here. 326 // See ptolemy/matlab/test/MatlabRunComposite.xml. 327 // See ptolemy/actor/lib/hoc/test/auto/knownFailedTests/PreinitializeMustBeInvokedRunComposite.xml 328 // preinitialize(); 329 330 _readInputs(); 331 332 if (_stopRequested) { 333 return COMPLETED; 334 } 335 336 // FIXME: Reset time to zero. How? 337 // NOTE: Use the superclass initialize() because this method overrides 338 // initialize() and does not initialize the model. 339 super.initialize(); 340 341 // Call iterate() until finish() is called or postfire() 342 // returns false. 343 _debug("-- Beginning to iterate."); 344 345 int lastIterateResult = COMPLETED; 346 347 while (!_stopRequested) { 348 executeChangeRequests(); 349 350 if (super.prefire()) { 351 // Cannot use super.fire() here because it does 352 // some inappropriate things like reading port parameters 353 // and transferring inputs. 354 _fireInsideModel(); 355 356 if (!super.postfire()) { 357 lastIterateResult = STOP_ITERATING; 358 break; 359 } 360 } else { 361 lastIterateResult = NOT_READY; 362 break; 363 } 364 } 365 366 return lastIterateResult; 367 } finally { 368 try { 369 executeChangeRequests(); 370 super.wrapup(); 371 } finally { 372 // Indicate that it is now safe to execute 373 // change requests when they are requested. 374 setDeferringChangeRequests(false); 375 } 376 377 if (!_stopRequested) { 378 _writeOutputs(); 379 } 380 381 if (_debugging) { 382 _debug("---- Firing is complete."); 383 } 384 } 385 } 386 387 /** Invoke the fire() method of its local director. 388 * @exception IllegalActionException If there is no director, or if 389 * the director's fire() method throws it. 390 */ 391 protected void _fireInsideModel() throws IllegalActionException { 392 if (_debugging) { 393 _debug("Firing the inside model."); 394 } 395 396 try { 397 _workspace.getReadAccess(); 398 if (!_stopRequested) { 399 getDirector().fire(); 400 } 401 } finally { 402 _workspace.doneReading(); 403 } 404 if (_debugging) { 405 _debug("Done firing inside model."); 406 } 407 } 408 409 /** Return the actor whose life cycle is being managed by this actor. 410 * This base class returns this actor itself. 411 * @return This. 412 */ 413 protected TypedCompositeActor _getManagedActor() { 414 return this; 415 } 416 417 /** Iterate over input ports and read any available values into 418 * the referenced model parameters. 419 * @exception IllegalActionException If reading the ports or 420 * setting the parameters causes it. 421 */ 422 protected void _readInputs() throws IllegalActionException { 423 // NOTE: This is an essentially exact copy of the code in ModelReference, 424 // but this class and that one can't easily share a common base class. 425 if (_debugging) { 426 _debug("** Reading inputs (if any)."); 427 } 428 429 Iterator ports = inputPortList().iterator(); 430 431 while (ports.hasNext()) { 432 IOPort port = (IOPort) ports.next(); 433 434 if (port instanceof ParameterPort) { 435 PortParameter parameter = ((ParameterPort) port).getParameter(); 436 437 parameter.update(); 438 439 if (_getManagedActor() == this) { 440 // If the managed actor is this, then the parameter value 441 // will already be visible within the actor. 442 // Have to make sure we set the persistent value 443 // of the parameter, not just the current value, otherwise 444 // it will be reset when the model is initialized. 445 parameter.setExpression(parameter.getToken().toString()); 446 447 if (_debugging) { 448 _debug("** Updated PortParameter: " + port.getName() 449 + " to value " + parameter.getToken()); 450 } 451 452 continue; 453 } else { 454 // If the managed actor is not this, then we need to set 455 // a matching parameter, if it exists, in the managed actor. 456 _setInsideParameter(port.getName(), parameter.getToken()); 457 } 458 } 459 if (port.isOutsideConnected() && port.hasToken(0)) { 460 Token token = port.get(0); 461 _setInsideParameter(port.getName(), token); 462 } 463 } 464 } 465 466 /** Iterate over output ports and read any available values from 467 * the referenced model parameters and produce them on the outputs. 468 * @exception IllegalActionException If reading the parameters or 469 * writing to the ports causes it. 470 */ 471 protected void _writeOutputs() throws IllegalActionException { 472 // NOTE: This is an essentially exact copy of the code in ModelReference, 473 // but this class and that one can't easily share a common base class. 474 if (_debugging) { 475 _debug("** Writing outputs (if any)."); 476 } 477 478 Iterator ports = outputPortList().iterator(); 479 480 while (ports.hasNext()) { 481 IOPort port = (IOPort) ports.next(); 482 483 // Only write if the port has a connected channel. 484 if (port.isOutsideConnected()) { 485 Attribute attribute = _getManagedActor() 486 .getAttribute(port.getName()); 487 488 // Use the token directly rather than a string if possible. 489 if (attribute instanceof Variable) { 490 if (_debugging) { 491 _debug("** Transferring parameter to output: " 492 + port.getName() + " (" 493 + ((Variable) attribute).getToken() + ")"); 494 } 495 496 port.send(0, ((Variable) attribute).getToken()); 497 } else if (attribute instanceof Settable) { 498 if (_debugging) { 499 _debug("** Transferring parameter as string to output: " 500 + port.getName() + " (" 501 + ((Settable) attribute).getExpression() + ")"); 502 } 503 504 port.send(0, new StringToken( 505 ((Settable) attribute).getExpression())); 506 } 507 } 508 } 509 } 510 511 /////////////////////////////////////////////////////////////////// 512 //// private methods //// 513 514 /** If the managed actor (this by default) has a parameter with the 515 * specified name, then set the value of that parameter to the 516 * specified token. 517 * @param name The name. 518 * @param token The value. 519 * @exception IllegalActionException If setting fails. 520 */ 521 private void _setInsideParameter(String name, Token token) 522 throws IllegalActionException { 523 Attribute attribute = _getManagedActor().getAttribute(name); 524 525 // Use the token directly rather than a string if possible. 526 if (attribute instanceof Variable) { 527 if (_debugging) { 528 _debug("** Transferring input to parameter: " + name); 529 } 530 531 ((Variable) attribute).setToken(token); 532 } else if (attribute instanceof Settable) { 533 if (_debugging) { 534 _debug("** Transferring input as string to parameter: " + name); 535 } 536 537 ((Settable) attribute).setExpression(token.toString()); 538 } 539 } 540}