001/* Set the value of a variable contained by the container.
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
027 */
028package ptolemy.actor.lib;
029
030import java.util.ArrayList;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Set;
034
035import ptolemy.actor.TypedAtomicActor;
036import ptolemy.actor.TypedIOPort;
037import ptolemy.actor.util.ExplicitChangeContext;
038import ptolemy.data.BooleanToken;
039import ptolemy.data.Token;
040import ptolemy.data.expr.ModelScope;
041import ptolemy.data.expr.Parameter;
042import ptolemy.data.expr.Variable;
043import ptolemy.data.type.BaseType;
044import ptolemy.graph.Inequality;
045import ptolemy.kernel.CompositeEntity;
046import ptolemy.kernel.Entity;
047import ptolemy.kernel.util.Attribute;
048import ptolemy.kernel.util.ChangeListener;
049import ptolemy.kernel.util.ChangeRequest;
050import ptolemy.kernel.util.IllegalActionException;
051import ptolemy.kernel.util.InternalErrorException;
052import ptolemy.kernel.util.NameDuplicationException;
053import ptolemy.kernel.util.NamedObj;
054import ptolemy.kernel.util.Settable;
055import ptolemy.kernel.util.StringAttribute;
056import ptolemy.kernel.util.Workspace;
057import ptolemy.util.MessageHandler;
058
059///////////////////////////////////////////////////////////////////
060//// SetVariable
061
062/**
063 <p>Set the value of a variable. If there is a variable or parameter
064 in scope with a name matching <i>variableName</i>, then that
065 variable is the one to be set. If there is no such variable,
066 then one will be created in the container of this actor.
067 A variable is in scope if it is contained by the container,
068 the container's container, or any container above in the hierarchy.
069 <b>NOTE:</b> We recommend always creating a parameter with
070 a name matching <i>variableName</i>, because then the model is
071 explicit about which variable will be set. It also makes it easier
072 to monitor the variable updates.
073 </p><p>
074 The update to the variable
075 may occur at two different times, depending on the value of the
076 <i>delayed</i> parameter.
077 If <i>delayed</i> is true, then the change to
078 the value of the variable is implemented in a change request, and
079 consequently will not take hold until the end of the current
080 top-level iteration.  This helps ensure that users of value of the
081 variable will see changes to the value deterministically
082 (independent of the schedule of execution of the actors),
083 assuming there is only a single instance of SetVariable writing
084 to the variable.
085 If <i>delayed</i> is false, then the change to the value of
086 the variable is performed immediately in the fire() method.
087 This allows more frequent
088 reconfiguration. However, this can result in nondeterminism if
089 the variable values are observed by any other actor in
090 the system. If you are trying to communicate with another
091 actor without wiring, use the Publisher and Subscriber
092 actors instead.
093 </p><p>
094 If <i>delayed</i> is false, then
095 the <i>output</i> port produces the same token provided at
096 the <i>input</i> port when the actor fires, after the
097 specified variable has been set. This can be used, even with
098 <i>delayed</i> set to false, to ensure determinacy by
099 triggering downstream actions only after the variable has
100 been set.
101 </p><p>
102 If <i>delayed</i> is true, then
103 the <i>output</i> port produces the current value
104 of the referenced variable. If the referenced variable
105 does not exist on the first firing, or is not an instance
106 of Variable, then no output is
107 produced on the first firing.
108 </p><p>
109 The variable can be any attribute that implements
110 the Settable interface, which includes Parameter.
111 If it is in addition an instance of
112 Variable or Parameter, then the input token is used directly to set the
113 value, and the type of the variable is constrained to be
114 the same as the type of the input. Otherwise, then input
115 token is converted to a string and the setExpression() method
116 on the variable is used to set the value.
117 </p><p>
118 For efficiency, the variable update does not automatically
119 trigger a repaint in Vergil. If the variable value is being used
120 to create an animation in Vergil, then you should include in the model
121 an instance of RepaintController, which can be found under
122 Utilities in the library.
123
124 @author Edward A. Lee, Steve Neuendorffer, Contributor: Blanc, Bert Rodiers
125 @see Publisher
126 @see Subscriber
127 @version $Id$
128 @since Ptolemy II 4.0
129 @Pt.ProposedRating Red (yuhong)
130 @Pt.AcceptedRating Red (cxh)
131 */
132public class SetVariable extends TypedAtomicActor
133        implements ChangeListener, ExplicitChangeContext {
134
135    /** Construct an actor in the specified workspace with an empty
136     *  string as a name. You can then change the name with setName().
137     *  If the workspace argument is null, then use the default workspace.
138     *  The object is added to the workspace directory.
139     *  Increment the version number of the workspace.
140     *  @param workspace The workspace that will list the entity.
141     */
142    public SetVariable(Workspace workspace) {
143        super(workspace);
144    }
145
146    /** Construct an actor with the given container and name.
147     *  @param container The container.
148     *  @param name The name of this actor.
149     *  @exception IllegalActionException If this actor cannot be contained
150     *   by the proposed container.
151     *  @exception NameDuplicationException If the container already has an
152     *   actor with this name.
153     */
154    public SetVariable(CompositeEntity container, String name)
155            throws NameDuplicationException, IllegalActionException {
156        super(container, name);
157
158        input = new TypedIOPort(this, "input", true, false);
159        output = new TypedIOPort(this, "output", false, true);
160
161        variableName = new StringAttribute(this, "variableName");
162
163        delayed = new Parameter(this, "delayed");
164        delayed.setTypeEquals(BaseType.BOOLEAN);
165        delayed.setExpression("true");
166    }
167
168    ///////////////////////////////////////////////////////////////////
169    ////                     ports and parameters                  ////
170
171    /** Parameter that determines when reconfiguration occurs. */
172    public Parameter delayed;
173
174    /** The input port. */
175    public TypedIOPort input;
176
177    /** The output port. */
178    public TypedIOPort output;
179
180    /** The name of the variable in the container to set. */
181    public StringAttribute variableName;
182
183    ///////////////////////////////////////////////////////////////////
184    ////                         public methods                    ////
185
186    /** Do nothing.
187     *  @param change The change that executed.
188     */
189    @Override
190    public void changeExecuted(ChangeRequest change) {
191    }
192
193    /** React to the fact that a change failed by setting a flag
194     *  that causes an exception to be thrown in next call to prefire()
195     *  or wrapup().
196     *  @param change The change request.
197     *  @param exception The exception that resulted.
198     */
199    @Override
200    public void changeFailed(ChangeRequest change,
201            java.lang.Exception exception) {
202        _setFailed = true;
203        MessageHandler.error("Failed to set variable.", exception);
204    }
205
206    /** Clone the actor into the specified workspace.
207     *  @param workspace The workspace for the new object.
208     *  @return A new actor.
209     *  @exception CloneNotSupportedException If a derived class contains
210     *   an attribute that cannot be cloned.
211     */
212    @Override
213    public Object clone(Workspace workspace) throws CloneNotSupportedException {
214        SetVariable newObject = (SetVariable) super.clone(workspace);
215        // Derived classes need this.
216        newObject._attribute = newObject
217                .getAttribute(newObject.variableName.getName());
218        return newObject;
219    }
220
221    /** Read at most one token from the input port and issue a change
222     *  request to update variables as indicated by the input.
223     *  @exception IllegalActionException If thrown reading the input.
224     */
225    @Override
226    public void fire() throws IllegalActionException {
227        super.fire();
228        if (!delayed.getToken().equals(BooleanToken.TRUE)) {
229            if (input.hasToken(0)) {
230                Token value = input.get(0);
231                _setValue(value);
232                output.send(0, value);
233            }
234        } else {
235            Attribute variable = getModifiedVariable();
236            if (variable instanceof Variable) {
237                Token previousToken = ((Variable) variable).getToken();
238                if (previousToken != null) {
239                    output.send(0, previousToken);
240                }
241            }
242        }
243    }
244
245    /**
246     * Return the change context being made explicit.  In this case,
247     * the change context returned is this actor.
248     * @return The change context being made explicit
249     */
250    @Override
251    public Entity getContext() {
252        try {
253            if (delayed.getToken().equals(BooleanToken.TRUE)) {
254                return (Entity) toplevel();
255            } else {
256                return this;
257            }
258        } catch (IllegalActionException ex) {
259            return this;
260        }
261    }
262
263    /** Return the (presumably Settable) attribute modified by this
264     *  actor.  This is the attribute in the container of this actor
265     *  with the name given by the variableName attribute.  If no such
266     *  attribute is found, then this method creates a new variable in
267     *  the actor's container with the correct name.  This method
268     *  gets write access on the workspace.
269     *  @exception IllegalActionException If the variable cannot be found.
270     *  @return The attribute modified by this actor.
271     */
272    public Attribute getModifiedVariable() throws IllegalActionException {
273        if (_workspace.getVersion() == _attributeVersion) {
274            return _attribute;
275        }
276        NamedObj container = getContainer();
277
278        if (container == null) {
279            throw new IllegalActionException(this, "No container.");
280        }
281
282        String variableNameValue = variableName.getExpression();
283        _attribute = null;
284
285        if (!variableNameValue.equals("")) {
286            // Look for the variableName anywhere in the hierarchy
287            _attribute = ModelScope.getScopedAttribute(null, container,
288                    variableNameValue);
289            if (_attribute == null) {
290                try {
291                    workspace().getWriteAccess();
292
293                    // container might be null, so create the variable
294                    // in the container of this actor.
295                    _attribute = new Variable(getContainer(),
296                            variableNameValue);
297                } catch (IllegalActionException ex) {
298                    throw new IllegalActionException(this, ex,
299                            "Failed to create Variable \"" + variableNameValue
300                                    + "\" in " + getContainer().getFullName()
301                                    + ".");
302                } catch (NameDuplicationException ex) {
303                    throw new InternalErrorException(ex);
304                } finally {
305                    workspace().doneWriting();
306                }
307            }
308            _attributeVersion = _workspace.getVersion();
309        }
310        return _attribute;
311    }
312
313    /** Return a list of variables that this entity modifies.  The
314     * variables are assumed to have a change context of the given
315     * entity.
316     * @return A list of variables.
317     * @exception IllegalActionException If the list of modified
318     * variables cannot be returned.
319     */
320    @Override
321    public List getModifiedVariables() throws IllegalActionException {
322        Attribute attribute = getModifiedVariable();
323        List list = new ArrayList(1);
324
325        if (attribute instanceof Variable) {
326            list.add(attribute);
327        }
328
329        return list;
330    }
331
332    /** Read at most one token from the input port and issue a change
333     *  request to update variables as indicated by the input.
334     *  @exception IllegalActionException If thrown reading the input.
335     */
336    @Override
337    public boolean postfire() throws IllegalActionException {
338        if (delayed.getToken().equals(BooleanToken.TRUE)) {
339            if (input.hasToken(0)) {
340                final Token value = input.get(0);
341
342                // If the previous set failed, then return false.
343                // We cannot just proceed with another request because
344                // it will likely trigger the same exception again,
345                // and the model will just repeatedly pop up exception
346                // windows, with no escape.
347                if (_setFailed) {
348                    return false;
349                }
350                // We can't filter change request here with an extra condition
351                // _updateNecessary(value) since it might be that there is
352                // more than one SetVariable setting the same variable with
353                // a different priority.
354                // Suppose you have variable a. a currently has value 1. a is set to 2
355                // by a lower priority SetVariable actor and to one by the higher one.
356                // With the test "if (_updateNecessary(value))", the value would not be
357                // set to 1 and hence the value would change this two instead of one.
358                // $PTII/ptolemy/actor/parameters/test/auto/Priority4.xml
359                // tests this behavior.
360
361                // The ChangeRequest has false as third argument to avoid complete
362                // repaints of the model.
363                ChangeRequest request = new ChangeRequest(this,
364                        "SetVariable change request", /* isStructuralChange */
365                        false) {
366                    @Override
367                    protected void _execute() throws IllegalActionException {
368                        _setValue(value);
369                    }
370                };
371
372                // To prevent prompting for saving the model, mark this
373                // change as non-persistent.
374                request.setPersistent(false);
375                request.addChangeListener(this);
376                requestChange(request);
377            }
378        }
379
380        return super.postfire();
381    }
382
383    /** If there is no variable with the specified name, then create one.
384     *  This is done in preinitialize() so that we can set up a type
385     *  constraint that ensures that the type of the variable is at
386     *  least that of the input port.
387     *  @exception IllegalActionException If the superclass throws it,
388     *   or if there is no container.
389     */
390    @Override
391    public void preinitialize() throws IllegalActionException {
392        super.preinitialize();
393
394        Attribute attribute = getModifiedVariable();
395
396        if (attribute instanceof Variable) {
397            ((Variable) attribute).setTypeAtLeast(input);
398        }
399
400        _setFailed = false;
401    }
402
403    ///////////////////////////////////////////////////////////////////
404    ////                         protected methods                 ////
405
406    /**
407     * Return a constraint requiring that if there is a specified variable to
408     * modify, the type of that variable is less than or equal to the type
409     * of the output port.
410     * @return A set of type constraints.
411     */
412    @Override
413    protected Set<Inequality> _customTypeConstraints() {
414        Set<Inequality> result = new HashSet<Inequality>();
415        try {
416            // type of variable <= type of output
417            Attribute attribute = getModifiedVariable();
418            if (attribute instanceof Variable) {
419                result.add(new Inequality(((Variable) attribute).getTypeTerm(),
420                        output.getTypeTerm()));
421                if (this.isBackwardTypeInferenceEnabled()) {
422                    result.add(new Inequality(output.getTypeTerm(),
423                            ((Variable) attribute).getTypeTerm()));
424                }
425            }
426        } catch (IllegalActionException e) {
427            // The variable cannot be found. Ignore it.
428        }
429
430        return result;
431    }
432
433    ///////////////////////////////////////////////////////////////////
434    ////                         private methods                   ////
435
436    /** Set the value of the associated container's variable.
437     *  @param value The new value.
438     */
439    private void _setValue(Token value) throws IllegalActionException {
440        Attribute variable = getModifiedVariable();
441
442        if (variable instanceof Variable) {
443            Token oldToken = ((Variable) variable).getToken();
444
445            if (oldToken == null || !oldToken.equals(value)) {
446                ((Variable) variable).setToken(value);
447
448                // NOTE: If we don't call validate(), then the
449                // change will not propagate to dependents.
450                ((Variable) variable).validate();
451            }
452        } else if (variable instanceof Settable) {
453            ((Settable) variable).setExpression(value.toString());
454
455            // NOTE: If we don't call validate(), then the
456            // change will not propagate to dependents.
457            ((Settable) variable).validate();
458        } else {
459            throw new IllegalActionException(SetVariable.this,
460                    "Cannot set the value of the variable " + "named: "
461                            + variableName.getExpression());
462        }
463    }
464
465    ///////////////////////////////////////////////////////////////////
466    ////                         private fields                    ////
467
468    /** Cached reference to the associated variable. */
469    private Attribute _attribute;
470
471    /** Workspace version for the cached attribute reference. */
472    private long _attributeVersion = -1;
473
474    /** Indicator that setting the variable failed. */
475    private boolean _setFailed = false;
476}