001/* A transition in an FSMActor. 002 003 Copyright (c) 1999-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; 030import java.util.LinkedList; 031import java.util.List; 032import java.util.StringTokenizer; 033 034import ptolemy.actor.CompositeActor; 035import ptolemy.actor.Director; 036import ptolemy.actor.TypedActor; 037import ptolemy.actor.TypedCompositeActor; 038import ptolemy.data.BooleanToken; 039import ptolemy.data.StringToken; 040import ptolemy.data.Token; 041import ptolemy.data.expr.ASTPtRootNode; 042import ptolemy.data.expr.Parameter; 043import ptolemy.data.expr.ParseTreeEvaluator; 044import ptolemy.data.expr.ParserScope; 045import ptolemy.data.expr.PtParser; 046import ptolemy.data.expr.StringParameter; 047import ptolemy.data.expr.Variable; 048import ptolemy.data.type.BaseType; 049import ptolemy.kernel.ComponentRelation; 050import ptolemy.kernel.CompositeEntity; 051import ptolemy.kernel.Port; 052import ptolemy.kernel.attributes.VersionAttribute; 053import ptolemy.kernel.util.Attribute; 054import ptolemy.kernel.util.IllegalActionException; 055import ptolemy.kernel.util.InternalErrorException; 056import ptolemy.kernel.util.Location; 057import ptolemy.kernel.util.NameDuplicationException; 058import ptolemy.kernel.util.Nameable; 059import ptolemy.kernel.util.NamedObj; 060import ptolemy.kernel.util.Settable; 061import ptolemy.kernel.util.StreamListener; 062import ptolemy.kernel.util.StringAttribute; 063import ptolemy.kernel.util.Workspace; 064import ptolemy.moml.MoMLChangeRequest; 065 066/////////////////////////////////////////////////////////////////// 067//// Transition 068 069/** 070 A Transition has a source state and a destination state. A 071 transition has a guard expression, which is evaluated to a boolean value. 072 Whenever a transition out of the current state 073 is enabled, it must be taken in the current firing. 074 That is, unlike some state machines formalisms, our guard is not just 075 an enabler for the transition but rather a trigger for the transition. 076 077 <p> A transition can contain actions. The way to specify actions is 078 to give value to the <i>outputActions</i> parameter and the 079 <i>setActions</i> parameter. 080 081 The value of these parameters is a string of the form: 082 <pre> 083 <i>command</i>; <i>command</i>; ... 084 </pre> 085 where each <i>command</i> has the form: 086 <pre> 087 <i>destination</i> = <i>expression</i> 088 </pre> 089 For the <i>outputActions</i> parameter, <i>destination</i> is either 090 <pre> 091 <i>portName</i> 092 </pre> 093 or 094 <pre> 095 <i>portName</i>(<i>channelNumber</i>) 096 </pre> 097 Here, <i>portName</i> is the name of a port of the FSM actor, 098 If no <i>channelNumber</i> is given, then the value 099 is broadcast to all channels of the port. 100 <p> 101 For the <i>setActions</i> parameter, <i>destination</i> is 102 <pre> 103 <i>variableName</i> 104 </pre> 105 <i>variableName</i> identifies either a variable or parameter of 106 the FSM actor, or a variable or parameter of the refinement of the 107 destination state of the transition. To give a variable of the 108 refinement, use a dotted name, as follows: 109 <pre> 110 <i>refinementName</i>.<i>variableName</i> 111 </pre> 112 The <i>expression</i> is a string giving an expression in the usual 113 Ptolemy II expression language. The expression may include references 114 to variables and parameters contained by the FSM actor. 115 <p> 116 The <i>outputActions</i> and <i>setActions</i> parameters are not the only 117 ways to specify actions. In fact, you can add action attributes that are 118 instances of anything that inherits from Action. 119 (Use the Add button in the Edit Parameters dialog). 120 <p> 121 An action is either a ChoiceAction or a CommitAction. The <i>setActions</i> 122 parameter is a CommitAction, whereas the <i>outputActions</i> parameter is a 123 ChoiceAction. A commit action is executed when the transition is taken to 124 change the state of the FSM, in the postfire() method of FSMActor. 125 A choice action, by contrast, is executed in the fire() method 126 of the FSMActor when the transition is chosen, but not yet taken. 127 The difference is subtle, and for most domains, irrelevant. 128 A few domains, however, such as CT, which have fixed point semantics, 129 where the fire() method may be invoked several times before the 130 transition is taken (committed). For such domains, it is useful 131 to have actions that fulfill the ChoiceAction interface. 132 Such actions participate in the search for a fixed point, but 133 do not change the state of the FSM. 134 <p> 135 A transition can be preemptive or non-preemptive. When a preemptive transition 136 is chosen, the refinement of its source state is not fired. A non-preemptive 137 transition is only chosen after the refinement of its source state is fired. 138 <p> 139 The <i>history</i> parameter specifies whether the refinement of the destination 140 state refinement is initialized when the transition is taken. By default, this 141 is false, which means that the destination refinement is initialized. 142 If you change this to true, then the destination refinement will not be 143 initialized, so when the state is re-entered, the refinement will 144 continue executing where it left off. 145 <p> 146 The <i>nondeterministic</i> parameter specifies whether this transition is 147 nondeterministic. Here nondeterministic means that this transition may not 148 be the only enabled transition at a time. The default value is a boolean 149 token with value as false, meaning that if this transition is enabled, it 150 must be the only enabled transition. 151 <p> 152 The <i>immediateTransition</i> parameter, if given a value true, specifies 153 that this transition is may be taken as soon as its source state is entered, 154 in the same iteration. This may lead to transient states, where a state is 155 passed through without ever becoming the current state. 156 <p> 157 The <i>defaultTransition</i> parameter, if given a value true, specifies 158 that this transition is enabled if no other non-default 159 transition is enabled and if its guard evaluates to true. 160 <p> 161 The <i>error</i> parameter, if given a value true, specifies 162 that this transition is enabled if the refinement of the source state of 163 the transition throws a model error or an exception 164 while executing. The default value is a boolean 165 token with value false. When such an exception or model error 166 occurs, two variables are set that may be used in the guard 167 or the output or set actions of this transition: 168 <ul> 169 <li> <i>errorMessage</i>: The error message (a string). 170 <li> <i>errorClass</i>: The class of the exception thrown. 171 </ul> 172 In addition, if the exception is an instance of KernelException 173 or a subclass (such as IllegalActionException), then a third 174 variable is set: 175 <ul> 176 <li> <i>errorCause</i>: The Ptolemy object that caused the exception. 177 </ul> 178 The <i>errorCause</i> is made available as an ObjectToken on which 179 you can invoke methods such as getName() in the guard or output 180 or set actions of this transition. 181 182 @author Xiaojun Liu, Edward A. Lee, Haiyang Zheng, Christian Motika 183 @version $Id$ 184 @since Ptolemy II 8.0 185 @Pt.ProposedRating Yellow (hyzheng) 186 @Pt.AcceptedRating Red (hyzheng) 187 @see State 188 @see Action 189 @see ChoiceAction 190 @see CommitAction 191 @see CommitActionsAttribute 192 @see FSMActor 193 @see OutputActionsAttribute 194 */ 195public class Transition extends ComponentRelation { 196 /** Construct a transition with the given name contained by the specified 197 * entity. The container argument must not be null, or a 198 * NullPointerException will be thrown. This transition will use the 199 * workspace of the container for synchronization and version counts. 200 * If the name argument is null, then the name is set to the empty string. 201 * @param container The container. 202 * @param name The name of the transition. 203 * @exception IllegalActionException If the container is incompatible 204 * with this transition. 205 * @exception NameDuplicationException If the name coincides with 206 * any relation already in the container. 207 */ 208 public Transition(FSMActor container, String name) 209 throws IllegalActionException, NameDuplicationException { 210 super(container, name); 211 _init(); 212 } 213 214 /** Construct a transition in the given workspace with an empty string 215 * as a name. 216 * If the workspace argument is null, use the default workspace. 217 * The object is added to the workspace directory. 218 * Increment the version of the workspace. 219 * @param workspace The workspace for synchronization and version 220 * tracking. 221 * @exception IllegalActionException If the container is incompatible 222 * with this transition. 223 * @exception NameDuplicationException If the name coincides with 224 * any relation already in the container. 225 */ 226 public Transition(Workspace workspace) 227 throws IllegalActionException, NameDuplicationException { 228 super(workspace); 229 _init(); 230 } 231 232 /////////////////////////////////////////////////////////////////// 233 //// public methods //// 234 235 /** React to a change in an attribute. If the changed attribute is 236 * the <i>preemptive</i> parameter, evaluate the parameter. If the 237 * parameter is given an expression that does not evaluate to a 238 * boolean value, throw an exception; otherwise increment the 239 * version number of the workspace. 240 * @param attribute The attribute that changed. 241 * @exception IllegalActionException If thrown by the superclass 242 * attributeChanged() method, or the changed attribute is the 243 * <i>preemptive</i> parameter and is given an expression that 244 * does not evaluate to a boolean value. 245 */ 246 @Override 247 public void attributeChanged(Attribute attribute) 248 throws IllegalActionException { 249 if (attribute == preemptive) { 250 // evaluate the parameter to make sure it is given a valid 251 // expression 252 preemptive.getToken(); 253 workspace().incrVersion(); 254 } else if (attribute == immediate) { 255 _immediate = ((BooleanToken) immediate.getToken()).booleanValue(); 256 } else if (attribute == nondeterministic) { 257 _nondeterministic = ((BooleanToken) nondeterministic.getToken()) 258 .booleanValue(); 259 } else if (attribute == guardExpression) { 260 // The guard expression can only be evaluated at run 261 // time, because the input variables it can reference are created 262 // at run time. The guardExpression is a string 263 // attribute used to convey expressions without being evaluated. 264 // _guard is the variable that does the evaluation. 265 _guardParseTree = null; 266 _guardParseTreeVersion = -1; 267 _parseTreeEvaluatorVersion = -1; 268 } else if (attribute == refinementName) { 269 _refinementVersion = -1; 270 } else if (attribute == outputActions || attribute == setActions) { 271 _actionListsVersion = -1; 272 } else { 273 super.attributeChanged(attribute); 274 } 275 276 if (attribute == outputActions && _debugging) { 277 outputActions.addDebugListener(new StreamListener()); 278 } else if (attribute == setActions && _debugging) { 279 setActions.addDebugListener(new StreamListener()); 280 } else if (attribute == fsmTransitionParameterName) { 281 if (((BooleanToken) showFSMTransitionParameter.getToken()) 282 .booleanValue()) { 283 _getFSMTransitionParameter(); 284 try { 285 _fsmTransitionParameter 286 .setName(((StringToken) fsmTransitionParameterName 287 .getToken()).stringValue()); 288 } catch (NameDuplicationException e) { 289 throw new IllegalActionException(this, e.getCause(), 290 e.getLocalizedMessage()); 291 } 292 } 293 } else if (attribute == showFSMTransitionParameter) { 294 if (((BooleanToken) showFSMTransitionParameter.getToken()) 295 .booleanValue()) { 296 _getFSMTransitionParameter(); 297 if (_fsmTransitionParameter != null) { 298 _fsmTransitionParameter.hide(false); 299 _fsmTransitionParameter.setPersistent(true); 300 } 301 showFSMTransitionParameter.setPersistent(true); 302 fsmTransitionParameterName.setPersistent(true); 303 } else { 304 if (_fsmTransitionParameter != null) { 305 _fsmTransitionParameter.setPersistent(false); 306 } 307 showFSMTransitionParameter.setPersistent(false); 308 fsmTransitionParameterName.setPersistent(false); 309 } 310 } 311 } 312 313 /** Return the list of choice actions contained by this transition. 314 * @return The list of choice actions contained by this transition. 315 */ 316 public List choiceActionList() { 317 if (_actionListsVersion != workspace().getVersion()) { 318 _updateActionLists(); 319 } 320 321 return _choiceActionList; 322 } 323 324 /** Clone the transition into the specified workspace. This calls the 325 * base class and then sets the attribute public members to refer to 326 * the attributes of the new transition. 327 * @param workspace The workspace for the new transition. 328 * @return A new transition. 329 * @exception CloneNotSupportedException If a derived class contains 330 * an attribute that cannot be cloned. 331 */ 332 @Override 333 public Object clone(Workspace workspace) throws CloneNotSupportedException { 334 Transition newObject = (Transition) super.clone(workspace); 335 /* 336 newObject.guardExpression = (StringAttribute) newObject 337 .getAttribute("guardExpression"); 338 newObject.preemptive = (Parameter) newObject.getAttribute("preemptive"); 339 newObject.immediate = (Parameter) newObject.getAttribute("immediate"); 340 newObject.refinementName = (StringAttribute) newObject.getAttribute("refinementName"); 341 */ 342 newObject._actionListsVersion = -1; 343 newObject._choiceActionList = new LinkedList(); 344 newObject._commitActionList = new LinkedList(); 345 newObject._destinationState = null; 346 newObject._fsmTransitionParameter = null; 347 newObject._guardParseTree = null; 348 newObject._guardParseTreeVersion = -1; 349 // newObject._historySet = false; 350 newObject._parseTreeEvaluatorVersion = -1; 351 newObject._parseTreeEvaluator = null; 352 newObject._refinementVersion = -1; 353 newObject._refinement = null; 354 newObject._sourceState = null; 355 newObject._stateVersion = -1; 356 return newObject; 357 } 358 359 /** Return the list of commit actions contained by this transition. 360 * @return The list of commit actions contained by this transition. 361 */ 362 public List commitActionList() { 363 if (_actionListsVersion != workspace().getVersion()) { 364 _updateActionLists(); 365 } 366 367 return _commitActionList; 368 } 369 370 /** Return the destination state of this transition. 371 * @return The destination state of this transition. 372 */ 373 public State destinationState() { 374 if (_stateVersion != workspace().getVersion()) { 375 _checkConnectedStates(); 376 } 377 378 return _destinationState; 379 } 380 381 /** Return the full label, which may include the guard expression, 382 * the output expression and the set actions. 383 * @return The full label 384 */ 385 public String getFullLabel() { 386 StringBuffer buffer = new StringBuffer(""); 387 boolean hasAnnotation = false; 388 String text; 389 try { 390 text = annotation.stringValue(); 391 } catch (IllegalActionException e) { 392 text = "Exception evaluating annotation: " + e.getMessage(); 393 } 394 if (!text.trim().equals("")) { 395 hasAnnotation = true; 396 buffer.append(text); 397 } 398 399 String guard = getGuardExpression(); 400 if (guard != null && !guard.trim().equals("")) { 401 if (hasAnnotation) { 402 buffer.append("\n"); 403 } 404 buffer.append("guard: "); 405 buffer.append(guard); 406 } 407 408 String expression = outputActions.getExpression(); 409 if (expression != null && !expression.trim().equals("")) { 410 buffer.append("\n"); 411 buffer.append("output: "); 412 buffer.append(expression); 413 } 414 415 expression = setActions.getExpression(); 416 if (expression != null && !expression.trim().equals("")) { 417 buffer.append("\n"); 418 buffer.append("set: "); 419 buffer.append(expression); 420 } 421 return buffer.toString(); 422 } 423 424 /** Return the guard expression. The guard expression should evaluate 425 * to a boolean value. 426 * @return The guard expression. 427 * @see #setGuardExpression 428 */ 429 public String getGuardExpression() { 430 return guardExpression.getExpression(); 431 } 432 433 /** Return a string describing this transition. The string has up to 434 * three lines. The first line is the guard expression, preceded 435 * by "guard: ". The second line is the <i>outputActions</i> preceded 436 * by the string "output: ". The third line is the 437 * <i>setActions</i> preceded by the string "set: ". If any of these 438 * is missing, then the corresponding line is omitted. 439 * @return A string describing this transition. 440 */ 441 public String getLabel() { 442 try { 443 if (((BooleanToken) showFSMTransitionParameter.getToken()) 444 .booleanValue()) { 445 return ((StringToken) fsmTransitionParameterName.getToken()) 446 .stringValue(); 447 } else { 448 return getFullLabel(); 449 } 450 } catch (IllegalActionException e) { 451 return "Exception evaluating annotation: " + e.getMessage(); 452 } 453 } 454 455 /** Return the parse tree evaluator used by this transition to evaluate 456 * the guard expression. 457 * @return ParseTreeEvaluator for evaluating the guard expression. 458 */ 459 public ParseTreeEvaluator getParseTreeEvaluator() { 460 if (_parseTreeEvaluatorVersion != workspace().getVersion()) { 461 // If there is no current parse tree evaluator, 462 // then create one. If this transition is under the control 463 // of an FSMDirector, then delegate creation to that director. 464 // Otherwise, create a default instance of ParseTreeEvaluator. 465 FSMDirector director = _getDirector(); 466 if (director != null) { 467 _parseTreeEvaluator = director.getParseTreeEvaluator(); 468 } else { 469 // When this transition is used inside an FSMActor. 470 if (_parseTreeEvaluator == null) { 471 _parseTreeEvaluator = new ParseTreeEvaluator(); 472 } 473 } 474 _parseTreeEvaluatorVersion = workspace().getVersion(); 475 } 476 return _parseTreeEvaluator; 477 } 478 479 /** Return the refinements of this transition. The names of the refinements 480 * are specified by the <i>refinementName</i> attribute. The refinements 481 * must be instances of TypedActor and have the same container as 482 * the FSMActor containing this state, otherwise an exception is thrown. 483 * This method can also return null if there is no refinement. 484 * This method is read-synchronized on the workspace. 485 * @return The refinements of this state, or null if there are none. 486 * @exception IllegalActionException If the specified refinement 487 * cannot be found, or if a comma-separated list is malformed. 488 */ 489 public TypedActor[] getRefinement() throws IllegalActionException { 490 if (_refinementVersion == workspace().getVersion()) { 491 return _refinement; 492 } 493 494 try { 495 workspace().getReadAccess(); 496 497 String names = refinementName.getExpression(); 498 499 if (names == null || names.trim().equals("")) { 500 _refinementVersion = workspace().getVersion(); 501 _refinement = null; 502 return null; 503 } 504 505 StringTokenizer tokenizer = new StringTokenizer(names, ","); 506 int size = tokenizer.countTokens(); 507 508 if (size <= 0) { 509 _refinementVersion = workspace().getVersion(); 510 _refinement = null; 511 return null; 512 } 513 514 _refinement = new TypedActor[size]; 515 516 Nameable container = getContainer(); 517 TypedCompositeActor containerContainer = (TypedCompositeActor) container 518 .getContainer(); 519 int index = 0; 520 521 while (tokenizer.hasMoreTokens()) { 522 String name = tokenizer.nextToken().trim(); 523 524 if (name.equals("")) { 525 throw new IllegalActionException(this, 526 "Malformed list of refinements: " + names); 527 } 528 529 TypedActor element = (TypedActor) containerContainer 530 .getEntity(name); 531 532 if (element == null) { 533 throw new IllegalActionException(this, 534 "Cannot find " + "refinement with name \"" + name 535 + "\" in " 536 + containerContainer.getFullName()); 537 } 538 539 _refinement[index++] = element; 540 } 541 542 _refinementVersion = workspace().getVersion(); 543 return _refinement; 544 } finally { 545 workspace().doneReading(); 546 } 547 } 548 549 /** Return true if this transition is a default transition. Return false 550 * otherwise. 551 * @return True if this transition is a default transition. 552 * @exception IllegalActionException If the default parameter 553 * cannot be evaluated. 554 */ 555 public boolean isDefault() throws IllegalActionException { 556 return ((BooleanToken) defaultTransition.getToken()).booleanValue(); 557 } 558 559 /** Return true if the transition is enabled, that is the guard is true, 560 * and false if the guard evaluates to false. 561 * @return True If the transition is enabled and some event is detected. 562 * @exception IllegalActionException If the guard cannot be evaluated. 563 */ 564 public boolean isEnabled() throws IllegalActionException { 565 NamedObj container = getContainer(); 566 if (container instanceof FSMActor) { 567 return isEnabled(((FSMActor) container).getPortScope()); 568 } else { 569 return false; 570 } 571 } 572 573 /** Return true if the transition is enabled, that is the guard is true, 574 * and false if the guard evaluates to false. 575 * @param scope The parser scope in which the guard is to be evaluated. 576 * @return True If the transition is enabled and some event is detected. 577 * @exception IllegalActionException If the guard cannot be evaluated. 578 */ 579 public boolean isEnabled(ParserScope scope) throws IllegalActionException { 580 ParseTreeEvaluator parseTreeEvaluator = getParseTreeEvaluator(); 581 if (_guardParseTree == null 582 || _guardParseTreeVersion != _workspace.getVersion()) { 583 String expr = getGuardExpression(); 584 // If the expression is empty, interpret this as true. 585 if (expr.trim().equals("")) { 586 return true; 587 } 588 // Parse the guard expression. 589 PtParser parser = new PtParser(); 590 try { 591 _guardParseTree = parser.generateParseTree(expr); 592 _guardParseTreeVersion = _workspace.getVersion(); 593 } catch (IllegalActionException ex) { 594 throw new IllegalActionException(this, ex, 595 "Failed to parse guard expression \"" + expr + "\""); 596 } 597 } 598 Token token = parseTreeEvaluator.evaluateParseTree(_guardParseTree, 599 scope); 600 if (!(token instanceof BooleanToken)) { 601 throw new IllegalActionException(this, 602 "Guard expression does not evaluate to a boolean!" 603 + " The gaurd expression is: \"" 604 + guardExpression.getExpression() 605 + "\", which evaluates to " + token); 606 } 607 boolean result = ((BooleanToken) token).booleanValue(); 608 return result; 609 } 610 611 /** Return true if this transition is an error transition. Whether this 612 * transition an error transition is specified by the <i>error</i> parameter. 613 * @return True if this transition is an error transition. 614 * @exception IllegalActionException If the parameter cannot be evaluated. 615 */ 616 public boolean isErrorTransition() throws IllegalActionException { 617 return ((BooleanToken) error.getToken()).booleanValue(); 618 } 619 620 /** Return true if this transition is a history transition. 621 * If the <i>history</i> parameter has been set to true, then return true. 622 * Otherwise, check to see whether the model was created by a Ptolemy version 623 * earlier than 9.1.devel. If it was not, return false, the new default. 624 * If it was, then look for a parameter named 625 * "reset", and set the history parameter to the complement of it 626 * and return that value. 627 * If there is no such parameter, then we have to assume the default 628 * behavior that prevailed before 9.1.devel, and set history to true. 629 * @return true if this transition is a history transition. 630 * @exception IllegalActionException If the value of the history parameter 631 * cannot be read. 632 */ 633 public boolean isHistory() throws IllegalActionException { 634 // Ensure that the corrections below for older version compatibility 635 // are performed only once. 636 if (_historySet) { 637 return ((BooleanToken) history.getToken()).booleanValue(); 638 } 639 _historySet = true; 640 // History has not been explicitly set true. Should use either new 641 // or old default depending on the version of Ptolemy that created the model. 642 try { 643 VersionAttribute version = (VersionAttribute) toplevel() 644 .getAttribute("_createdBy", VersionAttribute.class); 645 if (version == null) { 646 // No version attribute. Return whatever the value 647 // of the history parameter is. 648 return ((BooleanToken) history.getToken()).booleanValue(); 649 } 650 if (_REFERENCE_VERSION == null) { 651 _REFERENCE_VERSION = new VersionAttribute("9.0.devel"); 652 } 653 if (version.compareTo(_REFERENCE_VERSION) <= 0) { 654 // Model was created under old defaults. Look for a reset parameter. 655 final Parameter reset = (Parameter) getAttribute("reset", 656 Parameter.class); 657 if (reset != null) { 658 Token resetValue = reset.getToken(); 659 // Remove the reset parameter. 660 // If the model is subsequently saved, then the history 661 // parameter will take over, and it will be confusing to 662 // also have a reset parameter that doesn't do anything. 663 try { 664 reset.setContainer(null); 665 } catch (NameDuplicationException e) { 666 // Should not happen. Ignore. 667 } 668 // If the value of the reset parameter is false, 669 // and the destination states have refinements, 670 // then set the history parameter to true. 671 // Otherwise, leave the history parameter at its default. 672 if (resetValue instanceof BooleanToken) { 673 boolean resetValueBoolean = ((BooleanToken) resetValue) 674 .booleanValue(); 675 if (!resetValueBoolean) { 676 // reset parameter exists and has value false, but if the destination 677 // state has no refinement, we nonetheless change this to make 678 // the history parameter false. 679 State destinationState = destinationState(); 680 if (destinationState != null) { 681 TypedActor[] refinements = destinationState 682 .getRefinement(); 683 if (refinements == null 684 || refinements.length == 0) { 685 // No need to make history true. Stick with the default. 686 return false; 687 } 688 } 689 // Set the new parameter to its non-default value. 690 history.setExpression("true"); 691 // Force this to be exported because this might be getting set 692 // during construction of the model, in which case, true will be 693 // assumed to be the default value. 694 history.setPersistent(true); 695 return true; 696 } else { 697 // No need to set the new parameter, since this is the default. 698 return false; 699 } 700 } else { 701 // Parameter reset exists, but is not a boolean. Old default is 702 // that history is true. 703 history.setExpression("true"); 704 // Force this to be exported because this might be getting set 705 // during construction of the model, in which case, true will be 706 // assumed to be the default value. 707 history.setPersistent(true); 708 return true; 709 } 710 } else { 711 // Parameter reset does not exist. Old default is 712 // that history is true. But this is only really required 713 // if the destination state has a refinement. 714 State destinationState = destinationState(); 715 if (destinationState != null) { 716 TypedActor[] refinements = destinationState 717 .getRefinement(); 718 if (refinements == null || refinements.length == 0) { 719 // No need to make history true. Stick with the default. 720 return false; 721 } 722 } 723 history.setExpression("true"); 724 // Force this to be exported because this might be getting set 725 // during construction of the model, in which case, true will be 726 // assumed to be the default value. 727 history.setPersistent(true); 728 return true; 729 } 730 } else { 731 // Version is recent. Return the current value of the history parameter. 732 return ((BooleanToken) history.getToken()).booleanValue(); 733 } 734 } catch (IllegalActionException e) { 735 // Can't access version attribute. Return default. 736 return ((BooleanToken) history.getToken()).booleanValue(); 737 } 738 } 739 740 /** Return true if this transition is immediate. Whether this transition 741 * is immediate is specified by the <i>immediateTransition</i> parameter. 742 * @return True if this transition is immediate. 743 */ 744 public boolean isImmediate() { 745 return _immediate; 746 } 747 748 /** Return true if this transition is nondeterministic. Return false 749 * otherwise. 750 * @return True if this transition is nondeterministic. 751 */ 752 public boolean isNondeterministic() { 753 return _nondeterministic; 754 } 755 756 /** Return true if this transition is a termination transition. Whether this 757 * transition a termination transition is specified by the <i>termination</i> parameter. 758 * @return True if this transition is a termination transition. 759 * @exception IllegalActionException If the parameter cannot be evaluated. 760 */ 761 public boolean isTermination() throws IllegalActionException { 762 return ((BooleanToken) termination.getToken()).booleanValue(); 763 } 764 765 /** Return true if this transition is preemptive. Whether this transition 766 * is preemptive is specified by the <i>preemptive</i> parameter. 767 * @return True if this transition is preemptive. 768 * @exception IllegalActionException If the parameter cannot be evaluated. 769 */ 770 public boolean isPreemptive() throws IllegalActionException { 771 return ((BooleanToken) preemptive.getToken()).booleanValue(); 772 } 773 774 /** Override the base class to ensure that the proposed container 775 * is an instance of FSMActor or null; if it is null, then 776 * remove it from the container, and also remove any refinement(s) 777 * that it references that are not referenced by some other 778 * transition or state. 779 * 780 * @param container The proposed container. 781 * @exception IllegalActionException If the transition would result 782 * in a recursive containment structure, or if 783 * this transition and container are not in the same workspace, or 784 * if the argument is not a FSMActor or null. 785 * @exception NameDuplicationException If the container already has 786 * an relation with the name of this transition. 787 */ 788 @Override 789 public void setContainer(CompositeEntity container) 790 throws IllegalActionException, NameDuplicationException { 791 if (container != null) { 792 if (!(container instanceof FSMActor)) { 793 throw new IllegalActionException(container, this, 794 "Transition can only be contained by instances of " 795 + "FSMActor."); 796 } 797 } else { 798 if (_fsmTransitionParameter != null) { 799 _fsmTransitionParameter.setContainer(null); 800 } 801 } 802 803 super.setContainer(container); 804 } 805 806 /** Set the guard expression. The guard expression should evaluate 807 * to a boolean value. 808 * @param expression The guard expression. 809 * @see #getGuardExpression 810 */ 811 public void setGuardExpression(String expression) { 812 try { 813 guardExpression.setExpression(expression); 814 guardExpression.validate(); 815 } catch (IllegalActionException ex) { 816 throw new InternalErrorException("Error in setting the " 817 + "guard expression of a transition."); 818 } 819 } 820 821 /** Set the FSMTransitionParameter. 822 * @param parameter The parameter. 823 */ 824 public void setFsmTransitionParameter(FSMTransitionParameter parameter) { 825 _fsmTransitionParameter = parameter; 826 } 827 828 /** Return the source state of this transition. 829 * @return The source state of this transition. 830 */ 831 public State sourceState() { 832 if (_stateVersion != workspace().getVersion()) { 833 _checkConnectedStates(); 834 } 835 836 return _sourceState; 837 } 838 839 /////////////////////////////////////////////////////////////////// 840 //// public variables //// 841 842 /** An annotation that describes the transition. If this is non-empty, 843 * then a visual editor will be expected to put this annotation on 844 * or near the transition to document its function. This is a string 845 * that defaults to the empty string. Note that it can reference 846 * variables in scope using the notation $name. 847 */ 848 public StringParameter annotation; 849 850 /** Indicator that this transition is a default transition. A 851 * default transition is enabled only if no other non-default 852 * transition is enabled. This is a boolean with default value 853 * false. If the value is true, then the guard expression is 854 * ignored. 855 */ 856 public Parameter defaultTransition = null; 857 858 /** Parameter specifying whether this transition should be treated 859 * as an error transition. The default value is a boolean with 860 * the value false, which indicates that this transition is not 861 * an error transition. If the value is true, that this transition 862 * is enabled if and only if the refinement of the source state of 863 * the transition throws a model error while executing. 864 */ 865 public Parameter error = null; 866 867 /** Attribute the exit angle of a visual rendition. 868 * This parameter contains a DoubleToken, initially with value PI/5. 869 * It must lie between -PI and PI. Otherwise, it will be truncated 870 * to lie within this range. 871 */ 872 public Parameter exitAngle; 873 874 /** The name of the transition, which defaults to the name of 875 * the transition followed by the string "Parameter". 876 */ 877 public Parameter fsmTransitionParameterName; 878 879 /** Attribute giving the orientation of a self-loop. This is equal to 880 * the tangent at the midpoint (more or less). 881 * This parameter contains a DoubleToken, initially with value 0.0. 882 */ 883 public Parameter gamma; 884 885 /** Attribute specifying the guard expression. 886 */ 887 public StringAttribute guardExpression = null; 888 889 /** Parameter specifying whether the refinements of the destination 890 * state are initialized when the transition is taken. 891 * This is a boolean that defaults to false. 892 */ 893 public Parameter history; 894 895 /** Parameter specifying whether this transition is immediate. 896 */ 897 public Parameter immediate = null; 898 899 /** Parameter specifying whether this transition is nondeterministic. 900 * Here nondeterministic means that this transition may not be the only 901 * enabled transition at a time. The default value is a boolean token 902 * with value as false, meaning that if this transition is enabled, it 903 * must be the only enabled transition. 904 */ 905 public Parameter nondeterministic = null; 906 907 /** The action commands that produce outputs when the transition is taken. 908 */ 909 public OutputActionsAttribute outputActions; 910 911 /** Parameter specifying whether this transition is preemptive. 912 */ 913 public Parameter preemptive = null; 914 915 /** Attribute specifying one or more names of refinements. The 916 * refinements must be instances of TypedActor and have the same 917 * container as the FSMActor containing this state, otherwise 918 * an exception will be thrown when getRefinement() is called. 919 * Usually, the refinement is a single name. However, if a 920 * comma-separated list of names is provided, then all the specified 921 * refinements will be executed. 922 * This attribute has a null expression or a null string as 923 * expression when the state is not refined. 924 * @deprecated Use immediate transitions. 925 */ 926 @Deprecated 927 public StringAttribute refinementName; 928 929 /** The action commands that set parameters when the transition is taken. 930 * By default, this is empty. 931 */ 932 public CommitActionsAttribute setActions; 933 934 /** True of the the value of the {@link #fsmTransitionParameterName} parameter 935 * should be returned by {@link #getLabel()}. 936 */ 937 public Parameter showFSMTransitionParameter; 938 939 /** Parameter specifying whether the refinements of the origin 940 * state must have terminated (postfire has returned false) 941 * for the transition to be enabled. 942 */ 943 public Parameter termination; 944 945 /////////////////////////////////////////////////////////////////// 946 //// protected methods //// 947 948 /** Throw an IllegalActionException if the port cannot be linked 949 * to this transition. A transition has a source state and a 950 * destination state. A transition is only linked to the outgoing 951 * port of its source state and the incoming port of its destination 952 * state. 953 * @exception IllegalActionException If the port cannot be linked 954 * to this transition. 955 */ 956 @Override 957 protected void _checkPort(Port port) throws IllegalActionException { 958 super._checkPort(port); 959 960 if (!(port.getContainer() instanceof State)) { 961 throw new IllegalActionException(this, port.getContainer(), 962 "Transition can only connect to instances of State."); 963 } 964 965 State st = (State) port.getContainer(); 966 967 if (port != st.incomingPort && port != st.outgoingPort) { 968 throw new IllegalActionException(this, port.getContainer(), 969 "Transition can only be linked to incoming or outgoing " 970 + "port of State."); 971 } 972 973 if (numLinks() == 0) { 974 return; 975 } 976 977 if (numLinks() >= 2) { 978 throw new IllegalActionException(this, 979 "Transition can only connect two States."); 980 } 981 982 Iterator ports = linkedPortList().iterator(); 983 Port pt = (Port) ports.next(); 984 State s = (State) pt.getContainer(); 985 986 if (pt == s.incomingPort && port == st.incomingPort 987 || pt == s.outgoingPort && port == st.outgoingPort) { 988 throw new IllegalActionException(this, 989 "Transition can only have one source and one destination."); 990 } 991 992 return; 993 } 994 995 /////////////////////////////////////////////////////////////////// 996 //// private methods //// 997 998 // Check the states connected by this transition, cache the result. 999 // This method is read-synchronized on the workspace. 1000 private void _checkConnectedStates() { 1001 try { 1002 workspace().getReadAccess(); 1003 1004 Iterator ports = linkedPortList().iterator(); 1005 _sourceState = null; 1006 _destinationState = null; 1007 1008 while (ports.hasNext()) { 1009 Port p = (Port) ports.next(); 1010 State s = (State) p.getContainer(); 1011 1012 if (p == s.incomingPort) { 1013 _destinationState = s; 1014 } else { 1015 _sourceState = s; 1016 } 1017 } 1018 1019 _stateVersion = workspace().getVersion(); 1020 } finally { 1021 workspace().doneReading(); 1022 } 1023 } 1024 1025 /** Return the FSMDirector in charge of this transition, 1026 * or null if there is none. 1027 * @return The director in charge of this transition. 1028 */ 1029 private FSMDirector _getDirector() { 1030 // Get the containing FSMActor. 1031 NamedObj container = getContainer(); 1032 if (container != null) { 1033 // Get the containing modal model. 1034 CompositeActor modalModel = (CompositeActor) container 1035 .getContainer(); 1036 if (modalModel != null) { 1037 // Get the director for the modal model. 1038 Director director = modalModel.getDirector(); 1039 if (director instanceof FSMDirector) { 1040 return (FSMDirector) director; 1041 } 1042 } 1043 } 1044 return null; 1045 } 1046 1047 private void _getFSMTransitionParameter() throws IllegalActionException { 1048 if (getContainer() != null) { 1049 if (_fsmTransitionParameter == null) { 1050 _fsmTransitionParameter = (FSMTransitionParameter) getContainer() 1051 .getAttribute(((StringToken) fsmTransitionParameterName 1052 .getToken()).stringValue()); 1053 if (_fsmTransitionParameter != null) { 1054 try { 1055 _fsmTransitionParameter.setTransition(this); 1056 } catch (NameDuplicationException e) { 1057 throw new IllegalActionException(this, e.getCause(), 1058 e.getLocalizedMessage()); 1059 } 1060 } 1061 } 1062 if (_fsmTransitionParameter == null) { 1063 Location sourceStateLocation = (Location) sourceState() 1064 .getAttribute("_location"); 1065 Location destinationStateLocation = (Location) destinationState() 1066 .getAttribute("_location"); 1067 String moml = "<property name=\"" 1068 + ((StringToken) fsmTransitionParameterName.getToken()) 1069 .stringValue() 1070 + "\" class=\"ptolemy.domains.modal.kernel.FSMTransitionParameter\">\n" 1071 + " <property name=\"_hideName\" class=\"ptolemy.kernel.util.SingletonAttribute\"/>\n" 1072 + " <property name=\"_icon\" class=\"ptolemy.vergil.icon.ValueIcon\">\n" 1073 + " <property name=\"_color\" class=\"ptolemy.actor.gui.ColorAttribute\" value=\"{0.0, 0.0, 1.0, 1.0}\"/>\n" 1074 + " <property name=\"displayWidth\" value=\"1000\"/>\n" 1075 + " <property name=\"numberOfLines\" value=\"100\"/>\n" 1076 + " </property>\n" 1077 + " <property name=\"_location\" class=\"ptolemy.kernel.util.Location\" value=\"[" 1078 + (int) (destinationStateLocation.getLocation()[0] 1079 + (sourceStateLocation.getLocation()[0] 1080 - destinationStateLocation 1081 .getLocation()[0]) 1082 / 2) 1083 + ", " 1084 + (int) (destinationStateLocation.getLocation()[1] 1085 + (sourceStateLocation.getLocation()[1] 1086 - destinationStateLocation 1087 .getLocation()[1]) 1088 / 2) 1089 + "]\"/>\n" 1090 + " <property name=\"_smallIconDescription\" class=\"ptolemy.kernel.util.SingletonConfigurableAttribute\">\n" 1091 + " <configure><svg><text x=\"20\" style=\"font-size:14; font-family:SansSerif; fill:blue\" y=\"20\">-P-</text></svg></configure>\n" 1092 + " </property>\n" 1093 + " <property name=\"_editorFactory\" class=\"ptolemy.vergil.toolbox.VisibleParameterEditorFactory\"/>\n" 1094 + " <property name=\"_configurer\" class=\"ptolemy.actor.gui.TransitionEditorPaneFactory\"/>\n" 1095 + "</property>"; 1096 1097 MoMLChangeRequest request = new MoMLChangeRequest(this, 1098 getContainer(), moml); 1099 getContainer().requestChange(request); 1100 } 1101 } 1102 } 1103 1104 // Initialize the variables of this transition. 1105 private void _init() 1106 throws IllegalActionException, NameDuplicationException { 1107 fsmTransitionParameterName = new StringParameter(this, 1108 "fsmTransitionParameterName"); 1109 fsmTransitionParameterName.setExpression(this.getName() + "Parameter"); 1110 fsmTransitionParameterName.setVisibility(Settable.FULL); 1111 1112 showFSMTransitionParameter = new Parameter(this, 1113 "showFSMTransitionParameter"); 1114 showFSMTransitionParameter.setTypeEquals(BaseType.BOOLEAN); 1115 showFSMTransitionParameter.setToken(BooleanToken.FALSE); 1116 1117 annotation = new StringParameter(this, "annotation"); 1118 annotation.setExpression(""); 1119 // Add a hint to indicate to the PtolemyQuery class to open with a text style. 1120 Variable variable = new Variable(annotation, "_textHeightHint"); 1121 variable.setExpression(_TEXT_HEIGHT); 1122 variable.setPersistent(false); 1123 1124 guardExpression = new StringAttribute(this, "guardExpression"); 1125 // Add a hint to indicate to the PtolemyQuery class to open with a text style. 1126 variable = new Variable(guardExpression, "_textHeightHint"); 1127 variable.setExpression(_TEXT_HEIGHT); 1128 variable.setPersistent(false); 1129 1130 outputActions = new OutputActionsAttribute(this, "outputActions"); 1131 // Add a hint to indicate to the PtolemyQuery class to open with a text style. 1132 variable = new Variable(outputActions, "_textHeightHint"); 1133 variable.setExpression(_TEXT_HEIGHT); 1134 variable.setPersistent(false); 1135 1136 setActions = new CommitActionsAttribute(this, "setActions"); 1137 // Add a hint to indicate to the PtolemyQuery class to open with a text style. 1138 variable = new Variable(setActions, "_textHeightHint"); 1139 variable.setExpression(_TEXT_HEIGHT); 1140 variable.setPersistent(false); 1141 1142 exitAngle = new Parameter(this, "exitAngle"); 1143 exitAngle.setVisibility(Settable.NONE); 1144 exitAngle.setExpression("PI/5.0"); 1145 exitAngle.setTypeEquals(BaseType.DOUBLE); 1146 1147 gamma = new Parameter(this, "gamma"); 1148 gamma.setVisibility(Settable.NONE); 1149 gamma.setExpression("0.0"); 1150 gamma.setTypeEquals(BaseType.DOUBLE); 1151 1152 // default attributes. 1153 defaultTransition = new Parameter(this, "defaultTransition"); 1154 defaultTransition.setTypeEquals(BaseType.BOOLEAN); 1155 defaultTransition.setToken(BooleanToken.FALSE); 1156 // We would like to call this parameter "default" but 1157 // can't because this is a Java keyword. 1158 defaultTransition.setDisplayName("default"); 1159 1160 // Nondeterministic attributes. 1161 nondeterministic = new Parameter(this, "nondeterministic"); 1162 nondeterministic.setTypeEquals(BaseType.BOOLEAN); 1163 nondeterministic.setToken(BooleanToken.FALSE); 1164 1165 immediate = new Parameter(this, "immediate"); 1166 immediate.setTypeEquals(BaseType.BOOLEAN); 1167 immediate.setToken(BooleanToken.FALSE); 1168 1169 preemptive = new Parameter(this, "preemptive"); 1170 preemptive.setTypeEquals(BaseType.BOOLEAN); 1171 preemptive.setToken(BooleanToken.FALSE); 1172 1173 // From version 9.0.devel to 9.1.devel, the parameter 1174 // reset was replaced by history and the default behavior 1175 // was changed so that by default transitions are not 1176 // history transitions (are reset transitions). 1177 history = new Parameter(this, "history"); 1178 history.setTypeEquals(BaseType.BOOLEAN); 1179 history.setToken(BooleanToken.FALSE); 1180 1181 error = new Parameter(this, "error"); 1182 error.setTypeEquals(BaseType.BOOLEAN); 1183 error.setToken(BooleanToken.FALSE); 1184 1185 termination = new Parameter(this, "termination"); 1186 termination.setTypeEquals(BaseType.BOOLEAN); 1187 termination.setToken(BooleanToken.FALSE); 1188 1189 // Add refinement name parameter 1190 refinementName = new StringAttribute(this, "refinementName"); 1191 refinementName.setVisibility(Settable.EXPERT); 1192 1193 } 1194 1195 // Update the cached lists of actions. 1196 // This method is read-synchronized on the workspace. 1197 private void _updateActionLists() { 1198 try { 1199 workspace().getReadAccess(); 1200 _choiceActionList.clear(); 1201 _commitActionList.clear(); 1202 1203 Iterator actions = attributeList(Action.class).iterator(); 1204 1205 while (actions.hasNext()) { 1206 Action action = (Action) actions.next(); 1207 1208 if (action instanceof ChoiceAction) { 1209 _choiceActionList.add(action); 1210 } 1211 1212 if (action instanceof CommitAction) { 1213 _commitActionList.add(action); 1214 } 1215 } 1216 1217 _actionListsVersion = workspace().getVersion(); 1218 } finally { 1219 workspace().doneReading(); 1220 } 1221 } 1222 1223 /////////////////////////////////////////////////////////////////// 1224 //// private variables //// 1225 1226 // Version of cached lists of actions. 1227 private long _actionListsVersion = -1; 1228 1229 // Cached list of choice actions contained by this transition. 1230 private List _choiceActionList = new LinkedList(); 1231 1232 // Cached list of commit actions contained by this Transition. 1233 private List _commitActionList = new LinkedList(); 1234 1235 // Cached destination state of this transition. 1236 private State _destinationState = null; 1237 1238 private FSMTransitionParameter _fsmTransitionParameter; 1239 1240 // The parse tree for the guard expression. 1241 private ASTPtRootNode _guardParseTree; 1242 1243 // Version of the cached guard parse tree 1244 private long _guardParseTreeVersion = -1; 1245 1246 // Flag to ensure that the corrections below for older version compatibility 1247 // are performed only once. 1248 private boolean _historySet = false; 1249 1250 // Set to true if the transition should be checked 1251 // as soon as the source state is entered. This may lead 1252 // to transient states. 1253 private boolean _immediate = false; 1254 1255 private boolean _nondeterministic = false; 1256 1257 // The parse tree evaluator for the transition. 1258 // Note that this variable should not be accessed directly even inside 1259 // this class. Instead, always use the getParseTreeEvaluator() method. 1260 private ParseTreeEvaluator _parseTreeEvaluator; 1261 1262 // Version of the cached parse tree evaluator 1263 private long _parseTreeEvaluatorVersion = -1; 1264 1265 // Latest version before default history behavior changed. 1266 private static VersionAttribute _REFERENCE_VERSION; 1267 1268 // Cached reference to the refinement of this state. 1269 private TypedActor[] _refinement = null; 1270 1271 // Version of the cached reference to the refinement. 1272 private long _refinementVersion = -1; 1273 1274 // Cached source state of this transition. 1275 private State _sourceState = null; 1276 1277 // Version of cached source/destination state. 1278 private long _stateVersion = -1; 1279 1280 // Default text height in the dialog box. 1281 private static String _TEXT_HEIGHT = "4"; 1282}