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