001/* A base class for actions with semicolon delimited lists of commands.
002
003 Copyright (c) 2000-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 */
027package ptolemy.domains.modal.kernel;
028
029import java.util.Collections;
030import java.util.HashSet;
031import java.util.Iterator;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036
037import ptolemy.data.IntToken;
038import ptolemy.data.Token;
039import ptolemy.data.expr.ASTPtAssignmentNode;
040import ptolemy.data.expr.ASTPtRootNode;
041import ptolemy.data.expr.ParseTreeEvaluator;
042import ptolemy.data.expr.ParseTreeFreeVariableCollector;
043import ptolemy.data.expr.ParseTreeTypeInference;
044import ptolemy.data.expr.ParseTreeWriter;
045import ptolemy.data.expr.ParserScope;
046import ptolemy.data.expr.PtParser;
047import ptolemy.data.expr.Variable;
048import ptolemy.data.type.ArrayType;
049import ptolemy.data.type.BaseType;
050import ptolemy.data.type.HasTypeConstraints;
051import ptolemy.data.type.MonotonicFunction;
052import ptolemy.data.type.Type;
053import ptolemy.data.type.Typeable;
054import ptolemy.graph.Inequality;
055import ptolemy.graph.InequalityTerm;
056import ptolemy.kernel.Entity;
057import ptolemy.kernel.util.IllegalActionException;
058import ptolemy.kernel.util.NameDuplicationException;
059import ptolemy.kernel.util.NamedObj;
060import ptolemy.kernel.util.Workspace;
061
062///////////////////////////////////////////////////////////////////
063//// AbstractActionsAttribute
064
065/**
066 A base class for actions with semicolon delimited lists of commands.
067 <p>
068 The value of this attribute is a semicolon separated list of commands,
069 where each command gives a destination to send data to and a value
070 to send. The actions are given by calling setExpression() with
071 a string of the form:
072 <pre>
073 <i>command</i>; <i>command</i>; ...
074 </pre>
075 where each <i>command</i> has the form:
076 <pre>
077 <i>destination</i> = <i>expression</i>
078 </pre>
079 where <i>destination</i> is either
080 <pre>
081 <i>name</i>
082 </pre>
083 or
084 <pre>
085 <i>name</i>(<i>number</i>)
086 </pre>
087 <p>
088 The <i>expression</i> is a string giving an expression in the usual
089 Ptolemy II expression language.  The expression may include references
090 to variables and parameters contained by the FSM actor.
091
092 @author Xiaojun Liu and Edward A. Lee
093 @version $Id$
094 @since Ptolemy II 8.0
095 @Pt.ProposedRating Yellow (eal)
096 @Pt.AcceptedRating Red (eal)
097 @see CommitActionsAttribute
098 @see Transition
099 @see FSMActor
100 */
101public abstract class AbstractActionsAttribute extends Action
102        implements HasTypeConstraints {
103    /** Construct an action with the given name contained
104     *  by the specified container (which should be a Transition when used in
105     *  the FSM domain, and an Event in the Ptera domain). The <i>container</i>
106     *  argument must not
107     *  be null, or a NullPointerException will be thrown. This action will
108     *  use the workspace of the container for synchronization and
109     *  version counts. If the name argument is null, then the name is
110     *  set to the empty string.
111     *  This increments the version of the workspace.
112     *  @param container The container that contains this action.
113     *  @param name The name of this action.
114     *  @exception IllegalActionException If the action is not of an
115     *   acceptable class for the container, or if the name contains
116     *   a period.
117     *  @exception NameDuplicationException If the container already
118     *   has an attribute with the name.
119     */
120    public AbstractActionsAttribute(NamedObj container, String name)
121            throws IllegalActionException, NameDuplicationException {
122        super(container, name);
123    }
124
125    /** Construct an action in the specified workspace with an empty
126     *  string as a name.
127     *  The object is added to the directory of the workspace.
128     *  Increment the version number of the workspace.
129     *  @param workspace The workspace that will list the attribute.
130     */
131    public AbstractActionsAttribute(Workspace workspace) {
132        super(workspace);
133    }
134
135    ///////////////////////////////////////////////////////////////////
136    ////                         public methods                    ////
137
138    /** Clone the actor into the specified workspace. This calls the
139     *  base class and then sets the attribute public members to refer
140     *  to the attributes of the new actor.
141     *  @param workspace The workspace for the new actor.
142     *  @return A new FSMActor.
143     *  @exception CloneNotSupportedException If a derived class contains
144     *   an attribute that cannot be cloned.
145     */
146    @Override
147    public Object clone(Workspace workspace) throws CloneNotSupportedException {
148        AbstractActionsAttribute newObject = (AbstractActionsAttribute) super.clone(
149                workspace);
150        newObject._destinations = null;
151        newObject._destinationsListVersion = -1;
152        newObject._numbers = null;
153        newObject._parseTreeEvaluator = null;
154        newObject._parseTrees = null;
155
156        newObject._scope = null;
157
158        // The _destinationNames is a list of ports or parameter names that are
159        // written to by this action. It is constructed in setExpression().
160        // The clone needs to reconstruct this.
161        newObject._destinationNames = null;
162        try {
163            newObject.setExpression(getExpression());
164        } catch (IllegalActionException e) {
165            throw new CloneNotSupportedException(e.getMessage());
166        }
167
168        return newObject;
169    }
170
171    /** Execute this action.  For each destination identified in the
172     *  action, compute the value in the action and perform the
173     *  particular assignment.  This method should be extended by
174     *  derived classes to perform the evaluation and assignment as
175     *  appropriate.
176     *  @exception IllegalActionException If a destination is not found.
177     */
178    @Override
179    public void execute() throws IllegalActionException {
180        if (_destinationsListVersion != workspace().getVersion()) {
181            _updateDestinations();
182        }
183
184        if (_parseTreeEvaluator == null) {
185            _parseTreeEvaluator = new ParseTreeEvaluator();
186        }
187    }
188
189    /** Return the list of channel numbers given in expression set
190     *  for this attribute.  If no destinations are specified, then return
191     *  an empty list.
192     *  @return the list of channel numbers.
193     *  @exception IllegalActionException If thrown while evaluating
194     *  the channel numbers.
195     */
196    public List getChannelNumberList() throws IllegalActionException {
197        List list = new LinkedList();
198        if (_numbers != null) {
199            Iterator iterator = _numbers.iterator();
200            while (iterator.hasNext()) {
201                Object next = iterator.next();
202                if (next instanceof Integer) {
203                    list.add(next);
204                } else if (next instanceof ASTPtRootNode) {
205                    Token token = _parseTreeEvaluator.evaluateParseTree(
206                            (ASTPtRootNode) next, _getParserScope());
207                    list.add(((IntToken) token).intValue());
208                } else {
209                    list.add(null);
210                }
211            }
212        }
213        return list;
214    }
215
216    /** Return the destination object referred to by the given name.
217     *  Depending on the subclass of this class, this might be a variable,
218     *  or an output port.
219     *  @param name The name of the destination object.
220     *  @return The destination object with the given name.
221     *  @exception IllegalActionException If the given name is not a valid
222     *  destination for this action.
223     */
224    public NamedObj getDestination(String name) throws IllegalActionException {
225        return _getDestination(name);
226    }
227
228    /** Return the list of destinations of assignments in this action.
229     *  @return A list of IOPort for output actions, and a list of parameters
230     *   for set actions.
231     *  @exception IllegalActionException If the destination list cannot be
232     *   constructed.
233     */
234    @Override
235    public List getDestinations() throws IllegalActionException {
236        if (_destinationsListVersion != workspace().getVersion()) {
237            _updateDestinations();
238        }
239        return _destinations;
240    }
241
242    /** Return the list of destination names given in expression set
243     *  for this attribute.  If no destinations are specified, then return
244     *  an empty list.
245     *  @return the list of destination names.
246     */
247    public List getDestinationNameList() {
248        if (_destinationNames == null) {
249            return new LinkedList();
250        } else {
251            return Collections.unmodifiableList(_destinationNames);
252        }
253    }
254
255    /** Return the expression referred to by the given name.  When the
256     *  action is executed, this expression will be evaluated and
257     *  assigned to the object associated with the name.
258     *  @param name The name of an expression.
259     *  @return The expression referred to by the given name.
260     *  @see #setExpression
261     */
262    public String getExpression(String name) {
263        ParseTreeWriter writer = new ParseTreeWriter();
264        return writer.printParseTree((ASTPtRootNode) _parseTrees
265                .get(_destinationNames.indexOf(name)));
266    }
267
268    /** Return the parse tree referred to by the given name.
269     *  @param name The name of a parse tree.
270     *  @return The parse tree referred to by the given name.
271     */
272    public ASTPtRootNode getParseTree(String name) {
273        return (ASTPtRootNode) _parseTrees.get(_destinationNames.indexOf(name));
274    }
275
276    /** Return the list of parse trees given in expression set
277     *  for this attribute.  If no destinations are specified, then return
278     *  an empty list.
279     *  @return the list of parse trees.
280     */
281    public List getParseTreeList() {
282        if (_parseTrees == null) {
283            return new LinkedList();
284        } else {
285            return Collections.unmodifiableList(_parseTrees);
286        }
287    }
288
289    /** Test if a channel number is associated with the given name.
290     *  @param name The channel name.
291     *  @return true If a channel was specified.
292     */
293    public boolean isChannelSpecified(String name) {
294        Integer integer = (Integer) _numbers
295                .get(_destinationNames.indexOf(name));
296        return integer != null;
297    }
298
299    /** Set the action and notify the container
300     *  that the action has changed by calling attributeChanged(),
301     *  and notify any listeners that have
302     *  been registered using addValueListener().
303     *  @param expression The action.
304     *  @exception IllegalActionException If the change is not acceptable
305     *  to the container, or if the action is syntactically incorrect.
306     *  @see #getExpression
307     */
308    @Override
309    public void setExpression(String expression) throws IllegalActionException {
310        super.setExpression(expression);
311
312        // Initialize the lists that store the commands to be executed.
313        // NOTE: This must be done before we return if the expression is
314        // null, otherwise, previous set actions will continue to be
315        // executed.
316        _destinationNames = new LinkedList();
317        _numbers = new LinkedList();
318        _parseTrees = new LinkedList();
319
320        // Indicate that the _destinations list is invalid.  We defer
321        // determination of the destinations because the destinations
322        // may not have been created yet.
323        _destinationsListVersion = -1;
324
325        // This is important for InterfaceAutomata which extend from
326        // this class.
327        if (expression == null || expression.trim().equals("")) {
328            return;
329        }
330
331        PtParser parser = new PtParser();
332        Map map = parser.generateAssignmentMap(expression);
333
334        for (Iterator names = map.entrySet().iterator(); names.hasNext();) {
335            Map.Entry entry = (Map.Entry) names.next();
336            ASTPtAssignmentNode node = (ASTPtAssignmentNode) entry.getValue();
337
338            // Parse the destination specification first.
339            String completeDestinationSpec = node.getIdentifier();
340            int openParen = completeDestinationSpec.indexOf("(");
341
342            if (openParen > 0) {
343                // A channel is being specified.
344                int closeParen = completeDestinationSpec.indexOf(")");
345
346                if (closeParen < openParen) {
347                    throw new IllegalActionException(this,
348                            "Malformed action: expected destination = "
349                                    + "expression. Got: "
350                                    + completeDestinationSpec);
351                }
352
353                _destinationNames.add(
354                        completeDestinationSpec.substring(0, openParen).trim());
355
356                String channelSpec = completeDestinationSpec
357                        .substring(openParen + 1, closeParen);
358
359                try {
360                    _numbers.add(Integer.valueOf(channelSpec));
361                } catch (NumberFormatException ex) {
362                    _numbers.add(parser.generateParseTree(channelSpec));
363                }
364            } else {
365                // No channel is specified.
366                _destinationNames.add(completeDestinationSpec);
367                _numbers.add(null);
368            }
369
370            // Parse the expression
371            _parseTrees.add(node.getExpressionTree());
372        }
373    }
374
375    /** Give a descriptive string.
376     *  @return The expression.
377     */
378    @Override
379    public String toString() {
380        return getExpression();
381    }
382
383    /** Return the type constraints of this object.
384     *  The constraints are a set of inequalities.
385     *  @return a list of instances of Inequality.
386     *  @see ptolemy.graph.Inequality
387     */
388    @Override
389    public Set<Inequality> typeConstraints() {
390        Set<Inequality> list = new HashSet<Inequality>();
391
392        for (Iterator names = getDestinationNameList().iterator(); names
393                .hasNext();) {
394            String name = (String) names.next();
395
396            try {
397                NamedObj object = getDestination(name);
398
399                if (object instanceof Typeable) {
400                    InequalityTerm term = ((Typeable) object).getTypeTerm();
401                    list.add(new Inequality(new TypeFunction(name), term));
402                }
403            } catch (Exception ex) {
404                throw new RuntimeException(ex);
405            }
406        }
407
408        return list;
409    }
410
411    ///////////////////////////////////////////////////////////////////
412    ////                         protected methods                 ////
413
414    /** Given a destination name, return a NamedObj that matches that
415     *  destination.  An implementation of this method should never return
416     *  null (throw an exception instead).
417     *  @param name The name of the destination, or null if none is found.
418     *  @return An object (like a port or a variable) with the specified name.
419     *  @exception IllegalActionException If the associated FSMActor
420     *   does not have a destination with the specified name.
421     */
422    protected abstract NamedObj _getDestination(String name)
423            throws IllegalActionException;
424
425    ///////////////////////////////////////////////////////////////////
426    ////                         protected variables               ////
427
428    /** Return a parser scope used to evaluate or type-check this action.
429     *
430     *  @return The parser scope.
431     */
432    protected ParserScope _getParserScope() {
433        if (_scope == null) {
434            FSMActor fsmActor = (FSMActor) getContainer().getContainer();
435            _scope = fsmActor.getPortScope();
436        }
437        return _scope;
438    }
439
440    /** List of destination names. */
441    protected List _destinationNames;
442
443    /** List of destinations. */
444    protected List _destinations;
445
446    /** The workspace version number when the _destinations list is last
447     *  updated.
448     */
449    protected long _destinationsListVersion = -1;
450
451    /** The parse tree evaluator. */
452    protected ParseTreeEvaluator _parseTreeEvaluator;
453
454    /** The list of parse trees. */
455    protected List _parseTrees;
456
457    ///////////////////////////////////////////////////////////////////
458    ////                         private methods                   ////
459
460    /*  Cache a reference to each destination of this action.  For
461     *  each destination in the _destinationNames list, create a
462     *  corresponding entry in the _destinations list that refers to
463     *  the destination.
464     *  @exception IllegalActionException If the associated FSMActor
465     *   does not have a destination with the specified name.
466     */
467    private void _updateDestinations() throws IllegalActionException {
468        try {
469            workspace().getReadAccess();
470            _destinations = new LinkedList();
471
472            if (_destinationNames != null) {
473                Iterator destinationNames = _destinationNames.iterator();
474                while (destinationNames.hasNext()) {
475                    String destinationName = (String) destinationNames.next();
476                    NamedObj destination = _getDestination(destinationName);
477                    _destinations.add(destination);
478                }
479            }
480
481            _destinationsListVersion = workspace().getVersion();
482        } finally {
483            workspace().doneReading();
484        }
485    }
486
487    /** The scope. */
488    private ParserScope _scope;
489
490    // This class implements a monotonic function of the type of
491    // the output port.
492    // The function value is determined by type inference on the
493    // expression, in the scope of this Expression actor.
494    private class TypeFunction extends MonotonicFunction {
495        /** Create a new type function.  This function represents a
496         * constraint on the type of the destination with the given
497         * name.
498         */
499        public TypeFunction(String name) {
500            _name = name;
501        }
502
503        ///////////////////////////////////////////////////////////////
504        ////                       public inner methods            ////
505
506        /** Return the function result.
507         *  @return A Type.
508         *  @exception IllegalActionException If inferring types for the
509         *  expression fails.
510         */
511        @Override
512        public Object getValue() throws IllegalActionException {
513            try {
514                // Deal with the singularity at UNKNOWN..  Assume that if
515                // any variable that the expression depends on is UNKNOWN,
516                // then the type of the whole expression is unknown..
517                // This allows us to properly find functions that do exist
518                // (but not for UNKNOWN arguments), and to give good error
519                // messages when functions are not found.
520                InequalityTerm[] terms = getVariables();
521
522                for (InequalityTerm term : terms) {
523                    if (term != this && term.getValue() == BaseType.UNKNOWN) {
524                        return BaseType.UNKNOWN;
525                    }
526                }
527
528                int index = _destinationNames.indexOf(_name);
529                ASTPtRootNode parseTree = (ASTPtRootNode) _parseTrees
530                        .get(index);
531
532                Type type = _typeInference.inferTypes(parseTree,
533                        _getParserScope());
534
535                // Return the array type with type as the element type when
536                // there is an index following the name and the name resolves to
537                // a variable. E.g., A(i) accesses the i-th element of variable
538                // A, so when we get "type" as the type of A(i), we return the
539                // array type constructed with "type" as the type of A itself.
540                // -- tfeng (11/22/2008)
541                NamedObj container = getContainer();
542                while (container != null && !(container instanceof Entity)) {
543                    container = container.getContainer();
544                }
545                if (container != null
546                        && ((Entity) container).getPort(_name) == null) {
547                    // Not a port, then it must be a variable.
548                    if (_numbers.get(index) != null &&
549                    // If the destination is not a variable, it should
550                    // be a port, and port(i) refers to the i-th channel
551                    // of the port, which has the same type as the port
552                    // itself.
553                    // -- tfeng (11/26/2008)
554                            getDestination(_name) instanceof Variable) {
555                        // Has a number in parentheses following the name.
556                        ArrayType arrayType = new ArrayType(type);
557                        return arrayType;
558                    }
559                }
560
561                return type;
562            } catch (Exception ex) {
563                throw new IllegalActionException(AbstractActionsAttribute.this,
564                        ex,
565                        "An error occurred during expression type inference");
566            }
567        }
568
569        /** Return the type variable in this inequality term. If the type
570         *  of input ports are not declared, return an one element array
571         *  containing the inequality term representing the type of the port;
572         *  otherwise, return an empty array.
573         *  @return An array of InequalityTerm.
574         */
575        @Override
576        public InequalityTerm[] getVariables() {
577            // Return an array that contains type terms for all of the
578            // inputs and all of the parameters that are free variables for
579            // the expression.
580            try {
581                ASTPtRootNode parseTree = (ASTPtRootNode) _parseTrees
582                        .get(_destinationNames.indexOf(_name));
583
584                Set set = _variableCollector.collectFreeVariables(parseTree,
585                        _getParserScope());
586                List termList = new LinkedList();
587
588                for (Iterator elements = set.iterator(); elements.hasNext();) {
589                    String name = (String) elements.next();
590                    InequalityTerm term = _getParserScope().getTypeTerm(name);
591
592                    if (term != null && term.isSettable()) {
593                        termList.add(term);
594                    }
595                }
596
597                return (InequalityTerm[]) termList
598                        .toArray(new InequalityTerm[termList.size()]);
599            } catch (IllegalActionException ex) {
600                return new InequalityTerm[0];
601            }
602        }
603
604        /** Override the base class to give a description of this term.
605         *  @return A description of this term.
606         */
607        @Override
608        public String getVerboseString() {
609            return getExpression(_name);
610        }
611
612        ///////////////////////////////////////////////////////////////
613        ////                      private inner variables          ////
614
615        private String _name;
616
617        private ParseTreeTypeInference _typeInference = new ParseTreeTypeInference();
618
619        private ParseTreeFreeVariableCollector _variableCollector = new ParseTreeFreeVariableCollector();
620    }
621
622    /** List of channels. Elements may be numbers or variable names. */
623    private List _numbers;
624}