001/* A parameter that has an associated port.
002
003 Copyright (c) 2002-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.parameters;
029
030import ptolemy.actor.Initializable;
031import ptolemy.actor.TypedActor;
032import ptolemy.data.StringToken;
033import ptolemy.data.Token;
034import ptolemy.data.expr.AbstractInitializableParameter;
035import ptolemy.kernel.ComponentEntity;
036import ptolemy.kernel.Entity;
037import ptolemy.kernel.Port;
038import ptolemy.kernel.util.Attribute;
039import ptolemy.kernel.util.IllegalActionException;
040import ptolemy.kernel.util.InternalErrorException;
041import ptolemy.kernel.util.KernelException;
042import ptolemy.kernel.util.Locatable;
043import ptolemy.kernel.util.Location;
044import ptolemy.kernel.util.NameDuplicationException;
045import ptolemy.kernel.util.NamedObj;
046import ptolemy.kernel.util.Workspace;
047
048///////////////////////////////////////////////////////////////////
049//// PortParameter
050
051/**
052 <p>This parameter creates an associated port that can be used to update
053 the current value of the parameter. This parameter has two values,
054 which may not be equal, a <i>current value</i> and a <i>persistent value</i>.
055 The persistent value is returned by
056 getExpression() and is set by any of three different mechanisms:</p>
057 <ul>
058 <li> calling setExpression();</li>
059 <li> calling setToken(); and </li>
060 <li> specifying a value as a constructor argument.</li>
061 </ul>
062 <p>
063 All three of these will also set the current value, which is then
064 equal to the persistent value.
065 The current value is returned by get getToken()
066 and is set by any of two different mechanisms:</p>
067 <ul>
068 <li> calling setCurrentValue();</li>
069 <li> calling update() sets the current value if there is an associated
070 port, and that port has a token to consume; and</li>
071 </ul>
072 These two techniques do not change the persistent value, so after
073 these are used, the persistent value and current value may be different.
074 <p>
075 When the container for this parameter is initialized, the current
076 value of the parameter is reset to match the persistent value.
077 <p>
078 When using this parameter in an actor, care must be exercised
079 to call update() exactly once per firing prior to calling getToken().
080 Each time update() is called, a new token will be consumed from
081 the associated port (if the port is connected and has a token).
082 If this is called multiple times in an iteration, it may result in
083 consuming tokens that were intended for subsequent iterations.
084 Thus, for example, update() should not be called in fire() and then
085 again in postfire().  Moreover, in some domains (such as DE),
086 it is essential that if a token is provided on a port, that it
087 is consumed.  In DE, the actor will be repeatedly fired until
088 the token is consumed.  Thus, it is an error to not call update()
089 once per iteration.  For an example of an actor that uses this
090 mechanism, see Ramp.</p>
091 <p>
092 If this actor is placed in a container that does not implement
093 the TypedActor interface, then no associated port is created,
094 and it functions as an ordinary parameter.  This is useful,
095 for example, if this is put in a library, where one would not
096 want the associated port to appear.</p>
097
098 <p>There are a few situations where PortParameter might not do what
099 you expect:</p>
100
101 <ol>
102 <li> If it is used in a transparent composite actor, then a token provided
103 to a PortParameter will never be read.  A transparent composite actor
104 is one without a director.
105 <br>Workaround: Put a director in the composite.<br>
106 </li>
107
108 <li> Certain actors (such as the Integrator in CT) read parameter
109 values only during initialization.  During initialization, a
110 PortParameter can only have a value set via the parameter (it
111 can't have yet received a token).  So if the initial value of the
112 Integrator is set to the value of the PortParameter, then it will
113 see only the parameter value, never the value provided via the
114 port.
115 <br>Workaround: Use a RunCompositeActor to contain the model with the
116 Integrator.
117 </li>
118
119 </ol>
120
121 @see ptolemy.actor.lib.Ramp
122 @see ParameterPort
123 @author Edward A. Lee
124 @version $Id$
125 @since Ptolemy II 3.0
126 @Pt.ProposedRating Green (eal)
127 @Pt.AcceptedRating Yellow (neuendor)
128 */
129public class PortParameter extends AbstractInitializableParameter
130        implements Initializable {
131
132    /** Construct a parameter with the given name contained by the specified
133     *  entity. The container argument must not be null, or a
134     *  NullPointerException will be thrown.  This parameter will create
135     *  an associated port in the same container.
136     *  @param container The container.
137     *  @param name The name of the parameter.
138     *  @exception IllegalActionException If the parameter is not of an
139     *   acceptable class for the container.
140     *  @exception NameDuplicationException If the name coincides with
141     *   a parameter already in the container.
142     */
143    public PortParameter(NamedObj container, String name)
144            throws IllegalActionException, NameDuplicationException {
145        this(container, name, (ParameterPort) null);
146    }
147
148    /** Construct a parameter with the given name contained by the specified
149     *  entity. The container argument must not be null, or a
150     *  NullPointerException will be thrown.  This parameter will create
151     *  an associated port in the same container.
152     *  @param container The container.
153     *  @param name The name of the parameter.
154     *  @param port The associated ParameterPort, or null to create one.
155     *  @exception IllegalActionException If the parameter is not of an
156     *   acceptable class for the container.
157     *  @exception NameDuplicationException If the name coincides with
158     *   a parameter already in the container.
159     */
160    protected PortParameter(NamedObj container, String name, ParameterPort port)
161            throws IllegalActionException, NameDuplicationException {
162        super(container, name);
163        _port = port;
164        if (port == null) {
165            // If we get to here, we know the container is a ComponentEntity,
166            // so the cast is safe.
167            Port existingPort = ((ComponentEntity) container).getPort(name);
168            if (existingPort instanceof ParameterPort) {
169                _port = (ParameterPort) existingPort;
170                ((ParameterPort) existingPort)._parameter = this;
171            } else {
172                _port = new ParameterPort((ComponentEntity) container, name,
173                        this);
174            }
175        }
176        _setTypeConstraints();
177    }
178
179    /** Construct a parameter with the given name contained by the specified
180     *  entity. The container argument must not be null, or a
181     *  NullPointerException will be thrown.  This parameter will create
182     *  an associated port in the same container.
183     *  @param container The container.
184     *  @param name The name of the parameter.
185     *  @param initializeParameterPort True if the parameterPort should
186     *   be initialized here. Some derived classes might want to initialize
187     *   the port themselves (e.g. MirrorPortParameter).
188     *  @exception IllegalActionException If the parameter is not of an
189     *   acceptable class for the container.
190     *  @exception NameDuplicationException If the name coincides with
191     *   a parameter already in the container.
192     */
193    public PortParameter(NamedObj container, String name,
194            boolean initializeParameterPort)
195            throws IllegalActionException, NameDuplicationException {
196        super(container, name);
197        // If we get to here, we know the container is a ComponentEntity,
198        // so the cast is safe.
199        if (initializeParameterPort && container instanceof TypedActor) {
200            _port = new ParameterPort((ComponentEntity) container, name, this);
201            _setTypeConstraints();
202        }
203    }
204
205    /** Construct a Parameter with the given container, name, and Token.
206     *  The token defines the initial persistent and current values.
207     *  The container argument must not be null, or a
208     *  NullPointerException will be thrown.  This parameter will use the
209     *  workspace of the container for synchronization and version counts.
210     *  If the name argument is null, then the name is set to the empty string.
211     *  The object is not added to the list of objects in the workspace
212     *  unless the container is null.
213     *  Increment the version of the workspace.
214     *  If the name argument is null, then the name is set to the empty
215     *  string.
216     *  @param container The container.
217     *  @param name The name.
218     *  @param token The Token contained by this Parameter.
219     *  @exception IllegalActionException If the parameter is not of an
220     *   acceptable class for the container.
221     *  @exception NameDuplicationException If the name coincides with
222     *   an parameter already in the container.
223     */
224    public PortParameter(NamedObj container, String name,
225            ptolemy.data.Token token)
226            throws IllegalActionException, NameDuplicationException {
227        this(container, name);
228        setToken(token);
229        if (token != null) {
230            if (isStringMode() && token instanceof StringToken) {
231                _persistentExpression = ((StringToken) token).stringValue();
232            } else {
233                _persistentExpression = token.toString();
234            }
235        } else {
236            _persistentExpression = "";
237        }
238    }
239
240    ///////////////////////////////////////////////////////////////////
241    ////                         public methods                    ////
242
243    /** React to a change in an attribute.  This method is called by
244     *  a contained attribute when its value changes.  In this class,
245     *  if the attribute is an instance of Location, then the location
246     *  of the associated port is set as well.
247     *  @param attribute The attribute that changed.
248     *  @exception IllegalActionException If the change is not acceptable
249     *   to this container (not thrown in this base class).
250     */
251    @Override
252    public void attributeChanged(Attribute attribute)
253            throws IllegalActionException {
254        if (attribute instanceof Locatable) {
255            Locatable location = (Locatable) attribute;
256
257            if (_port != null) {
258                Attribute portAttribute = _port.getAttribute("_location");
259                Locatable portLocation = null;
260
261                if (portAttribute instanceof Locatable) {
262                    portLocation = (Locatable) portAttribute;
263                } else {
264                    try {
265                        portLocation = new Location(_port, "_location");
266                        ((NamedObj) portLocation).propagateExistence();
267                    } catch (KernelException ex) {
268                        throw new InternalErrorException(ex);
269                    }
270                }
271
272                double[] locationValues = location.getLocation();
273                double[] portLocationValues = new double[2];
274                portLocationValues[0] = locationValues[0] - 20.0;
275                portLocationValues[1] = locationValues[1] - 5.0;
276                portLocation.setLocation(portLocationValues);
277            }
278        } else {
279            super.attributeChanged(attribute);
280        }
281    }
282
283    /** Clone the parameter. This overrides the base class to remove
284     *  the current association with a port.  It is assumed that the
285     *  port will also be cloned, and when the containers are set of
286     *  this parameter and that port, whichever one is set second
287     *  will result in re-establishment of the association.
288     *  @param workspace The workspace in which to place the cloned parameter.
289     *  @exception CloneNotSupportedException Not thrown in this base class.
290     *  @see java.lang.Object#clone()
291     *  @return The cloned parameter.
292     */
293    @Override
294    public Object clone(Workspace workspace) throws CloneNotSupportedException {
295        PortParameter newObject = (PortParameter) super.clone(workspace);
296        // Cannot establish an association with the cloned port until
297        // that port is cloned and the container of both is set.
298        newObject._port = null;
299        return newObject;
300    }
301
302    /** Get the persistent expression.
303     *  @return The expression used by this variable.
304     *  @see #setExpression(String)
305     */
306    @Override
307    public String getExpression() {
308        if (_persistentExpression == null) {
309            return "";
310        }
311        return _persistentExpression;
312    }
313
314    /** Return the associated port.  Normally, there always is one,
315     *  but if setContainer() is called to change the container, then
316     *  this might return null. Also, during cloning, there is a
317     *  transient during which this may return null.
318     *  @return The associated port.
319     */
320    public ParameterPort getPort() {
321        if (_port == null) {
322            // Attempt to find the port.
323            NamedObj container = getContainer();
324            if (container instanceof Entity) {
325                Port candidate = ((Entity) container).getPort(getName());
326                if (candidate instanceof ParameterPort) {
327                    _port = (ParameterPort) candidate;
328                    _setTypeConstraints();
329                }
330            }
331        }
332        return _port;
333    }
334
335    /** Reset the current value to match the persistent value.
336     *  @exception IllegalActionException If thrown by a subclass.
337     */
338    @Override
339    public void initialize() throws IllegalActionException {
340        super.initialize();
341        super.setExpression(_persistentExpression);
342        validate();
343    }
344
345    /** Reset the current value to match the persistent value.
346     *  @exception IllegalActionException If thrown by a subclass.
347     */
348    @Override
349    public void preinitialize() throws IllegalActionException {
350        super.preinitialize();
351        super.setExpression(_persistentExpression);
352        validate();
353    }
354
355    /** Set the container of this parameter. If the container is different
356     *  from what it was before and there is an associated port, then
357     *  also change the container of the port.
358     *  @see ParameterPort
359     *  @param entity The new container.
360     *  @exception IllegalActionException If the superclass throws it.
361     *  @exception NameDuplicationException If the superclass throws it.
362     */
363    @Override
364    public void setContainer(NamedObj entity)
365            throws IllegalActionException, NameDuplicationException {
366        if (_settingContainer) {
367            // Recursive call through the port.
368            return;
369        }
370        Entity previousContainer = (Entity) getContainer();
371        if (entity == previousContainer) {
372            // No change.
373            return;
374        }
375        super.setContainer(entity);
376
377        // If there is an associated port, and the container has changed,
378        // updated the container of the port.
379        if (_port != null && (entity == null || entity instanceof Entity)) {
380            try {
381                _settingContainer = true;
382                _port.setContainer((Entity) entity);
383            } catch (KernelException ex) {
384                super.setContainer(previousContainer);
385                throw ex;
386            } finally {
387                _settingContainer = false;
388            }
389        }
390    }
391
392    /** Set the current value of this parameter and notify the container
393     *  and value listeners. This does not change the persistent value
394     *  (returned by getExpression()), but does change the current value
395     *  (returned by getToken()).
396     *  <p>
397     *  If the type of this variable has been set with
398     *  setTypeEquals(), then convert the specified token into that
399     *  type, if possible, or throw an exception, if not.  If
400     *  setTypeAtMost() has been called, then verify that its type
401     *  constraint is satisfied, and if not, throw an exception.
402     *  Note that you can call this with a null argument regardless
403     *  of type constraints, unless there are other variables that
404     *  depend on its value.</p>
405     *  @param token The new token to be stored in this variable.
406     *  @exception IllegalActionException If the token type is not
407     *   compatible with specified constraints, or if you are attempting
408     *   to set to null a variable that has value dependents, or if the
409     *   container rejects the change.
410     */
411    public void setCurrentValue(ptolemy.data.Token token)
412            throws IllegalActionException {
413        if (_debugging) {
414            _debug("setCurrentValue: " + token);
415        }
416
417        super.setToken(token);
418        setUnknown(false);
419    }
420
421    /** Set the display name, and propagate the name change to the
422     *  associated port.
423     *  Increment the version of the workspace.
424     *  This method is write-synchronized on the workspace.
425     *  @param name The new display name.
426     */
427    @Override
428    public void setDisplayName(String name) {
429        if (_settingName) {
430            return;
431        }
432        super.setDisplayName(name);
433        ParameterPort port = getPort();
434        if (port != null) {
435            try {
436                _settingName = true;
437                port.setDisplayName(name);
438            } finally {
439                _settingName = false;
440            }
441        }
442    }
443
444    /** Override the base class to record the persistent expression.
445     *  @param expression The expression for this variable.
446     *  @see #getExpression()
447     */
448    @Override
449    public void setExpression(String expression) {
450        _persistentExpression = expression;
451        super.setExpression(expression);
452    }
453
454    /** Set or change the name, and propagate the name change to the
455     *  associated port.  If a null argument is given, then the
456     *  name is set to an empty string.
457     *  Increment the version of the workspace.
458     *  This method is write-synchronized on the workspace.
459     *  @param name The new name.
460     *  @exception IllegalActionException If the name contains a period.
461     *  @exception NameDuplicationException If the container already
462     *   contains an attribute with the proposed name.
463     */
464    @Override
465    public void setName(String name)
466            throws IllegalActionException, NameDuplicationException {
467        if (_settingName) {
468            return;
469        }
470        super.setName(name);
471        ParameterPort port = getPort();
472        if (port != null) {
473            String oldName = getName();
474            try {
475                _settingName = true;
476                port.setName(name);
477            } catch (KernelException ex) {
478                super.setName(oldName);
479                throw ex;
480            } finally {
481                _settingName = false;
482            }
483        }
484    }
485
486    /** Override the base class to record the persistent expression
487     *  to be the string representation of the specified token.
488     *  @param newValue The new persistent value.
489     *  @exception IllegalActionException If the token type is not
490     *   compatible with specified constraints, or if you are attempting
491     *   to set to null a variable that has value dependents, or if the
492     *   container rejects the change.
493     */
494    @Override
495    public void setToken(Token newValue) throws IllegalActionException {
496        if (newValue != null) {
497            if (isStringMode() && newValue instanceof StringToken) {
498                _persistentExpression = ((StringToken) newValue).stringValue();
499            } else {
500                _persistentExpression = newValue.toString();
501            }
502        } else {
503            _persistentExpression = "";
504        }
505        super.setToken(newValue);
506    }
507
508    /** Check to see whether a token has arrived at the
509     *  associated port, and if so, update the current value of
510     *  parameter with that token.  If there is no associated port,
511     *  do nothing.
512     *  @return True if an input token has been consumed, false if not.
513     *  @exception IllegalActionException If reading from the associated
514     *   port throws it.
515     */
516    public boolean update() throws IllegalActionException {
517        ParameterPort port = getPort();
518
519        if (port != null && port.isOutsideConnected() && port.hasToken(0)) {
520            Token token = port.get(0);
521            setCurrentValue(token);
522            // Have to validate so that containers of dependent
523            // variables get attributeChanged() called.
524            validate();
525
526            if (_debugging) {
527                _debug("Updated parameter value to: " + token);
528            }
529            return true;
530        }
531        return false;
532    }
533
534    ///////////////////////////////////////////////////////////////////
535    ////                         protected methods                 ////
536
537    /** Get the persistent expression as a string, to be used to export to MoML.
538     *  @return The persistent expression as a string.
539     */
540    @Override
541    protected String _getCurrentExpression() {
542        return _persistentExpression;
543    }
544
545    /** Override the base class to also propagate the associated port.
546     *  @param container Object to contain the new object.
547     *  @exception IllegalActionException If the object
548     *   cannot be cloned.
549     *  @return A new object of the same class and name
550     *   as this one.
551     */
552    @Override
553    protected NamedObj _propagateExistence(NamedObj container)
554            throws IllegalActionException {
555        NamedObj result = super._propagateExistence(container);
556
557        // Since we have created an associated port in the
558        // constructor, and since that port is not contained by
559        // this parameter, it will not automatically be propagated.
560        // If this parameter is contained by class definition
561        // somewhere above in the hierarchy, then not propagating
562        // the associated port is an error.
563        ParameterPort port = getPort();
564        if (port != null) {
565            port.propagateExistence();
566        }
567        return result;
568    }
569
570    /** Set the type constraints between the protected member _port
571     *  and this parameter.  This is a protected method so that subclasses
572     *  can define different type constraints.
573     */
574    protected void _setTypeConstraints() {
575        ParameterPort port = getPort();
576        if (port != null) {
577            port.setTypeSameAs(this);
578        }
579    }
580
581    ///////////////////////////////////////////////////////////////////
582    ////                         protected members                 ////
583
584    /** The associated port. */
585    protected ParameterPort _port;
586
587    ///////////////////////////////////////////////////////////////////
588    ////                         private variables                 ////
589
590    /** The persistent expression. */
591    private String _persistentExpression;
592
593    /** Indicator that we are in the midst of setting the container. */
594    private boolean _settingContainer = false;
595
596    /** Indicator that we are in the midst of setting the name. */
597    private boolean _settingName = false;
598}