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}