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}