001/* An action that sends outputs or sets variable values.
002
003 Copyright (c) 2000-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.domains.modal.kernel;
028
029import java.util.Iterator;
030
031import ptolemy.actor.IOPort;
032import ptolemy.actor.NoRoomException;
033import ptolemy.data.Token;
034import ptolemy.data.expr.ASTPtRootNode;
035import ptolemy.data.expr.UnknownResultException;
036import ptolemy.data.expr.Variable;
037import ptolemy.kernel.CompositeEntity;
038import ptolemy.kernel.Entity;
039import ptolemy.kernel.util.Attribute;
040import ptolemy.kernel.util.IllegalActionException;
041import ptolemy.kernel.util.NameDuplicationException;
042import ptolemy.kernel.util.Nameable;
043import ptolemy.kernel.util.NamedObj;
044import ptolemy.kernel.util.Workspace;
045
046///////////////////////////////////////////////////////////////////
047//// CommitActionsAttribute
048
049/**
050 An action that changes the state of the system. Typical commit actions set
051 variables either in the containing FSMActor or in a state refinement.
052 This action is contained by a transition,
053 and is evaluated whenever that transition is taken.  The evaluation
054 is done in the postfire() method of the FSMActor that contains the
055 transition (hereafter called "the FSM actor").
056 To specify an action that is executed earlier, in the fire()
057 method, when the transition is enabled rather than taken,
058 use the class OutputActionsAttribute.
059 <p>
060 The value of this attribute is a semicolon separated list of commands,
061 where each command gives a destination and a value.
062 The actions are given by calling setExpression() with
063 a string of the form:
064 <pre>
065 <i>command</i>; <i>command</i>; ...
066 </pre>
067 where each <i>command</i> has the form:
068 <pre>
069 <i>destination</i> = <i>expression</i>
070 </pre>
071 where <i>destination</i> is <i>variableName</i>,
072 where <i>variableName</i> is either a variable or parameter of
073 the FSM actor, or a variable or parameter of a refinement state.
074 To give a variable of a refinement state, use a dotted name,
075 as follows:
076 <pre>
077 <i>refinementStateName</i>.<i>variableName</i>
078 </pre>
079 <p>
080 The <i>expression</i> is a string giving an expression in the usual
081 Ptolemy II expression language.  The expression may include references
082 to variables and parameters contained by the FSM actor.
083 <p>
084 The <i>destination</i> can also be a port, however, this is discouraged.
085 Commit actions are not executed until postfire(), and the Ptolemy II
086 abstract semantics requires that outputs be produced in fire().
087 Nonetheless, this is supported. In this case, the <i>destination</i> is
088 either
089 <pre>
090 <i>portName</i>
091 </pre>
092 or
093 <pre>
094 <i>portName</i>(<i>channelNumber</i>)
095 </pre>
096 Here, <i>portName</i> is the name of a port of the FSM actor,
097 If no <i>channelNumber</i> is given, then the value
098 is broadcast to all channels of the port.
099 If destination name is given where there is both a port and a variable
100 with that name, then the port will be used.
101
102 @author Xiaojun Liu and Edward A. Lee
103 @version $Id$
104 @since Ptolemy II 8.0
105 @Pt.ProposedRating Yellow (eal)
106 @Pt.AcceptedRating Red (eal)
107 @see CommitActionsAttribute
108 @see Transition
109 @see FSMActor
110 */
111public class CommitActionsAttribute extends AbstractActionsAttribute
112        implements CommitAction {
113    /** Construct an action in the specified workspace with an empty
114     *  string as a name.
115     *  The object is added to the directory of the workspace.
116     *  Increment the version number of the workspace.
117     *  @param workspace The workspace that will list the attribute.
118     */
119    public CommitActionsAttribute(Workspace workspace) {
120        super(workspace);
121    }
122
123    /** Construct an action with the given name contained
124     *  by the specified transition. The <i>transition</i> argument must not
125     *  be null, or a NullPointerException will be thrown. This action will
126     *  use the workspace of the transition for synchronization and
127     *  version counts. If the name argument is null, then the name is
128     *  set to the empty string.
129     *  This increments the version of the workspace.
130     *  @param transition The transition that contains this action.
131     *  @param name The name of this action.
132     *  @exception IllegalActionException If the action is not of an
133     *   acceptable class for the container.
134     *  @exception NameDuplicationException If the transition already
135     *   has an attribute with the name.
136     */
137    public CommitActionsAttribute(Transition transition, String name)
138            throws IllegalActionException, NameDuplicationException {
139        super(transition, name);
140    }
141
142    ///////////////////////////////////////////////////////////////////
143    ////                         public methods                    ////
144
145    /** Send tokens to the designated outputs.  Each token is determined
146     *  by evaluating the expression portion of the action.
147     *  @exception IllegalActionException If expression evaluation fails,
148     *   or the specified port is not found, or sending to one of the
149     *   channels of the port throws a NoRoomException.
150     */
151    @Override
152    public void execute() throws IllegalActionException {
153        super.execute();
154
155        if (_destinations != null && _destinations.size() > 0) {
156            Iterator destinations = _destinations.iterator();
157            Iterator channels = getChannelNumberList().iterator();
158            Iterator parseTrees = _parseTrees.iterator();
159
160            while (destinations.hasNext()) {
161                NamedObj nextDestination = (NamedObj) destinations.next();
162
163                // Need to get the next channel even if it's not used.
164                Integer channel = (Integer) channels.next();
165                ASTPtRootNode parseTree = (ASTPtRootNode) parseTrees.next();
166                Token token;
167
168                try {
169                    token = _parseTreeEvaluator.evaluateParseTree(parseTree,
170                            _getParserScope());
171                } catch (IllegalActionException ex) {
172                    // Chain exceptions to get the actor that
173                    // threw the exception.
174                    throw new IllegalActionException(this, ex,
175                            "Expression invalid.");
176                }
177
178                if (nextDestination instanceof IOPort) {
179                    IOPort destination = (IOPort) nextDestination;
180
181                    // FIXME: Domain-polymorphic actors should
182                    // not be writing to output ports in commit actions.
183                    // Should we through an exception?  Also:
184                    // If the IOPort is an input as well as an output,
185                    // then we need to set its shadow variables.
186
187                    try {
188                        if (channel != null) {
189                            destination.send(channel.intValue(), token);
190
191                            if (_debugging) {
192                                _debug(getFullName() + " port: "
193                                        + destination.getName() + " channel: "
194                                        + channel.intValue() + ", token: "
195                                        + token);
196                            }
197                        } else {
198                            if (token == null) {
199                                destination.broadcastClear();
200
201                                if (_debugging) {
202                                    _debug(getFullName() + " port: "
203                                            + destination.getName()
204                                            + " broadcast Clear!");
205                                }
206                            } else {
207                                destination.broadcast(token);
208
209                                if (_debugging) {
210                                    _debug(getFullName() + " port: "
211                                            + destination.getName()
212                                            + " broadcast token: " + token);
213                                }
214                            }
215                        }
216                    } catch (NoRoomException ex) {
217                        throw new IllegalActionException(this,
218                                "Cannot complete action: " + ex.getMessage());
219                    } catch (UnknownResultException ex) {
220                        // Produce no output.
221                    }
222                } else if (nextDestination instanceof Variable) {
223                    Variable destination = (Variable) nextDestination;
224
225                    try {
226                        //Token token = variable.getToken();
227                        destination.setToken(token);
228
229                        // Force all dependents to re-evaluate.
230                        // This makes the parameters in the actors of
231                        // the refinement take on new values immediately
232                        // after the action is committed.
233                        destination.validate();
234
235                        if (_debugging) {
236                            _debug(getFullName() + " variable: "
237                                    + destination.getName() + ", value: "
238                                    + token);
239                        }
240                    } catch (UnknownResultException ex) {
241                        destination.setUnknown(true);
242                    }
243                } else {
244                    throw new IllegalActionException(this,
245                            "Destination is neither an IOPort nor a Variable: "
246                                    + nextDestination.getFullName());
247                }
248            }
249        }
250    }
251
252    ///////////////////////////////////////////////////////////////////
253    ////                         protected methods                 ////
254
255    /** Given a destination name, return a NamedObj that matches that
256     *  destination.
257     *  @param name The name of the destination, or null if none is found.
258     *  @return An object (like a port or a variable) with the specified name.
259     *  @exception IllegalActionException If the associated FSMActor
260     *   does not have a destination with the specified name.
261     */
262    @Override
263    protected NamedObj _getDestination(String name)
264            throws IllegalActionException {
265        Transition transition = (Transition) getContainer();
266
267        if (transition == null) {
268            throw new IllegalActionException(this,
269                    "Action has no container transition.");
270        }
271
272        Entity fsm = (Entity) transition.getContainer();
273
274        if (fsm == null) {
275            throw new IllegalActionException(this, transition,
276                    "Transition has no container.");
277        }
278
279        IOPort port = (IOPort) fsm.getPort(name);
280
281        if (port == null) {
282            // No port found.  Try for a variable.
283            Attribute variable = fsm.getAttribute(name);
284
285            if (variable == null) {
286                // Try for a refinement variable.
287                int period = name.indexOf(".");
288
289                if (period > 0) {
290                    String refinementName = name.substring(0, period);
291                    String entryName = name.substring(period + 1);
292
293                    // FIXME: Look in the container of the fsm???
294                    // Below we look for an attribute only in the fsm
295                    // itself.
296                    Nameable fsmContainer = fsm.getContainer();
297
298                    if (fsmContainer instanceof CompositeEntity) {
299                        Entity refinement = ((CompositeEntity) fsmContainer)
300                                .getEntity(refinementName);
301
302                        if (refinement != null) {
303                            Attribute entry = refinement
304                                    .getAttribute(entryName);
305
306                            if (entry instanceof Variable) {
307                                return entry;
308                            }
309                        }
310                    }
311                }
312
313                // FIXME: If not found, instead of throwing an exception, we
314                // look for an attribute with the given name in the containers.
315                // This, however, split the name by periods, so it only looks
316                // for attributes with the exact given name. Look at
317                // ptolemy.domains.ptera.kernel.ActionsAttribute for a solution.
318                NamedObj container = getContainer();
319                while (container != null) {
320                    Attribute attribute = container.getAttribute(name);
321                    if (attribute != null) {
322                        return attribute;
323                    }
324                    NamedObj containerContainer = container.getContainer();
325                    if (container instanceof RefinementActor) {
326                        State state = ((RefinementActor) container)
327                                .getRefinedState();
328                        if (state == null) {
329                            container = containerContainer;
330                        } else {
331                            container = state.getContainer();
332                        }
333                    } else {
334                        container = containerContainer;
335                    }
336                }
337
338                throw new IllegalActionException(fsm, this,
339                        "Cannot find port or variable with the name: " + name);
340            } else {
341                if (!(variable instanceof Variable)) {
342                    throw new IllegalActionException(fsm, this,
343                            "The attribute with name \"" + name
344                                    + "\" is not an "
345                                    + "instance of Variable.");
346                }
347
348                return variable;
349            }
350        } else {
351            if (!port.isOutput()) {
352                throw new IllegalActionException(fsm, this,
353                        "The port is not an output port: " + name);
354            }
355
356            return port;
357        }
358    }
359}