001/* A decorator attribute that monitors decorator values to enforce constraints. 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 PT_COPYRIGHT_VERSION_2 024 COPYRIGHTENDKEY 025 026 */ 027package ptolemy.data.expr; 028 029import java.util.Collection; 030import java.util.LinkedList; 031import java.util.List; 032 033import ptolemy.data.BooleanToken; 034import ptolemy.data.DoubleToken; 035import ptolemy.data.Token; 036import ptolemy.data.type.BaseType; 037import ptolemy.kernel.CompositeEntity; 038import ptolemy.kernel.Entity; 039import ptolemy.kernel.util.Attribute; 040import ptolemy.kernel.util.Decorator; 041import ptolemy.kernel.util.DecoratorAttributes; 042import ptolemy.kernel.util.HierarchyListener; 043import ptolemy.kernel.util.IllegalActionException; 044import ptolemy.kernel.util.InternalErrorException; 045import ptolemy.kernel.util.NameDuplicationException; 046import ptolemy.kernel.util.NamedObj; 047import ptolemy.kernel.util.Settable; 048import ptolemy.kernel.util.Workspace; 049import ptolemy.util.MessageHandler; 050 051/////////////////////////////////////////////////////////////////// 052//// ConstraintMonitor 053 054/** 055 A contract monitor that decorates each entity in a model with a 056 <i>value</i> parameter and monitors the sum of all such values. 057 If the sum of the values equals or exceeds the value given in 058 {@link #threshold} and {@link #warningEnabled} is true, then this 059 class will issue a warning when the aggregate value matches or 060 exceeds the threshold. 061 The decorator values default to 0.0, so the default total 062 is 0.0. The default threshold is Infinity, so no warnings will 063 be issued by default. 064 <p> 065 If the {@link #includeOpaqueContents} parameter is true, then this decorator will 066 also decorate entities within opaque composite actors. By default, 067 this is false. 068 <p> 069 This object is a {@link Parameter} whose value is the total 070 of the values of all the decorator values. To use it, simply 071 drag it into a model. 072 <p> 073 @author Edward A. Lee 074 @version $Id$ 075 @since Ptolemy II 10.0 076 @Pt.ProposedRating Yellow (eal) 077 @Pt.AcceptedRating Red (eal) 078 */ 079public class ConstraintMonitor extends Parameter implements Decorator { 080 081 /** Construct an instance in the given container with the given name. 082 * The container argument must not be null, or a 083 * NullPointerException will be thrown. 084 * If the name argument is null, then the name is set to the 085 * empty string. Increment the version number of the workspace. 086 * @param container The container, which contains the objects to be decorated 087 * @param name Name of this constraint monitor. 088 * @exception IllegalActionException If this object is not compatible 089 * with the specified container. 090 * @exception NameDuplicationException If the name collides with an 091 * attribute in the container or if there is a name duplication during 092 * initialization. 093 */ 094 public ConstraintMonitor(CompositeEntity container, String name) 095 throws IllegalActionException, NameDuplicationException { 096 super(container, name); 097 098 setTypeEquals(BaseType.DOUBLE); 099 setExpression("0.0"); 100 setVisibility(Settable.NOT_EDITABLE); 101 102 threshold = new Parameter(this, "threshold"); 103 threshold.setTypeEquals(BaseType.DOUBLE); 104 threshold.setExpression("Infinity"); 105 106 warningEnabled = new Parameter(this, "warningEnabled"); 107 warningEnabled.setExpression("true"); 108 warningEnabled.setTypeEquals(BaseType.BOOLEAN); 109 110 includeOpaqueContents = new Parameter(this, "includeOpaqueContents"); 111 includeOpaqueContents.setExpression("false"); 112 includeOpaqueContents.setTypeEquals(BaseType.BOOLEAN); 113 114 includeTransparents = new Parameter(this, "includeTransparents"); 115 includeTransparents.setExpression("false"); 116 includeTransparents.setTypeEquals(BaseType.BOOLEAN); 117 } 118 119 /////////////////////////////////////////////////////////////////// 120 //// parameters //// 121 122 /** If true, then this decorator decorates entities within 123 * opaque composite actors. This is a boolean that defaults to 124 * false. 125 */ 126 public Parameter includeOpaqueContents; 127 128 /** If true, then this decorator decorates transparent composite 129 * entities. This is a boolean that defaults to false. 130 */ 131 public Parameter includeTransparents; 132 133 /** Threshold at which this monitor issues a warning, if 134 * {@link #warningEnabled} is true. 135 * This is a double that defaults to Infinity, meaning no 136 * constraint on the sum of the decorator values. 137 */ 138 public Parameter threshold; 139 140 /** If true (the default), then a warning is issued when the 141 * aggregate value equals or exceeds the specified {@link #threshold}. 142 * This is a boolean that defaults to true. 143 */ 144 public Parameter warningEnabled; 145 146 /////////////////////////////////////////////////////////////////// 147 //// public methods //// 148 149 /** Override the base class to invalidate if parameters have changed. 150 * @exception IllegalActionException If the change is not acceptable 151 * to this container (not thrown in this base class). 152 */ 153 @Override 154 public void attributeChanged(Attribute attribute) 155 throws IllegalActionException { 156 if (attribute == includeOpaqueContents 157 || attribute == includeTransparents) { 158 invalidate(); 159 } 160 super.attributeChanged(attribute); 161 } 162 163 /** Clone the object into the specified workspace. The new object is 164 * <i>not</i> added to the directory of that workspace (you must do this 165 * yourself if you want it there). 166 * The result is an object with no container. 167 * @param workspace The workspace for the cloned object. 168 * @exception CloneNotSupportedException Not thrown in this base class 169 * @return The new object. 170 */ 171 @Override 172 public Object clone(Workspace workspace) throws CloneNotSupportedException { 173 ConstraintMonitor newObject = (ConstraintMonitor) super.clone( 174 workspace); 175 newObject._lastValueWarning = null; 176 newObject._decoratedObjects = null; 177 newObject._decoratedObjectsVersion = -1L; 178 return newObject; 179 } 180 181 /** Return the decorated attributes for the target NamedObj, or null 182 * if the target is not decorated by this decorator. 183 * Specification, every Entity that is not a class definition 184 * that is contained (deeply) by the container of this decorator 185 * is decorated. This includes atomic entities and both opaque and 186 * transparent composite entities. 187 * @param target The NamedObj that will be decorated. 188 * @return The decorated attributes for the target NamedObj. 189 * @exception IllegalActionException If some object cannot be determined to 190 * be decorated or not (e.g., a parameter cannot be evaluated). 191 */ 192 @Override 193 public DecoratorAttributes createDecoratorAttributes(NamedObj target) 194 throws IllegalActionException { 195 boolean transparents = ((BooleanToken) includeTransparents.getToken()) 196 .booleanValue(); 197 boolean opaques = ((BooleanToken) includeOpaqueContents.getToken()) 198 .booleanValue(); 199 NamedObj container = getContainer(); 200 // If the container of this decorator is not a CompositeEntity, 201 // then it cannot possibly deeply contain the target. 202 if (!(container instanceof CompositeEntity)) { 203 return null; 204 } 205 if (target instanceof Entity && !((Entity) target).isClassDefinition() 206 && (transparents || !(target instanceof CompositeEntity) 207 || ((CompositeEntity) target).isOpaque()) 208 && _deepContains((CompositeEntity) container, (Entity) target, 209 opaques)) { 210 try { 211 return new ConstraintMonitorAttributes(target, this); 212 } catch (NameDuplicationException e) { 213 // This should not occur. 214 throw new InternalErrorException(e); 215 } 216 } else { 217 return null; 218 } 219 } 220 221 /** Return a list of the entities deeply contained by the container 222 * of this resource scheduler. This includes all entities, whether 223 * transparent or opaque, including those inside opaque entities. 224 * It does not include entities that are class definitions. 225 * @return A list of the objects decorated by this decorator. 226 * @exception IllegalActionException If some object cannot be determined to 227 * be decorated or not (e.g., a parameter cannot be evaluated). 228 */ 229 @Override 230 public List<NamedObj> decoratedObjects() throws IllegalActionException { 231 if (workspace().getVersion() == _decoratedObjectsVersion) { 232 return _decoratedObjects; 233 } 234 boolean transparents = ((BooleanToken) includeTransparents.getToken()) 235 .booleanValue(); 236 boolean opaques = ((BooleanToken) includeOpaqueContents.getToken()) 237 .booleanValue(); 238 239 CompositeEntity container = (CompositeEntity) getContainer(); 240 241 _decoratedObjectsVersion = workspace().getVersion(); 242 243 // Do the easy case (the default case) first. 244 if (!transparents && !opaques) { 245 _decoratedObjects = container.deepEntityList(); 246 return _decoratedObjects; 247 } 248 249 // Now the more complex case. 250 _decoratedObjects = new LinkedList<NamedObj>(); 251 _addAllContainedEntities(container, _decoratedObjects, transparents, 252 opaques); 253 return _decoratedObjects; 254 } 255 256 /** Return the value of {@link #includeOpaqueContents}. 257 * decorate objects across opaque hierarchy boundaries. 258 * @exception IllegalActionException If there is a problem 259 * getting the boolean valu from the includeOpaqueContents token. 260 */ 261 @Override 262 public boolean isGlobalDecorator() throws IllegalActionException { 263 boolean opaques = ((BooleanToken) includeOpaqueContents.getToken()) 264 .booleanValue(); 265 return opaques; 266 } 267 268 /** Override the base class to check whether the threshold constraint 269 * is satisfied. 270 * @return The token contained by this variable converted to the 271 * type of this variable, or null if there is none. 272 * @exception IllegalActionException If the expression cannot 273 * be parsed or cannot be evaluated, or if the result of evaluation 274 * violates type constraints, or if the result of evaluation is null 275 * and there are variables that depend on this one. 276 */ 277 @Override 278 public Token getToken() throws IllegalActionException { 279 Token result = super.getToken(); 280 // Check to see whether we need to report errors. 281 // Avoid duplicate notices. 282 boolean enabled = ((BooleanToken) warningEnabled.getToken()) 283 .booleanValue(); 284 if (!result.equals(_lastValueWarning)) { 285 if (enabled) { 286 double thresholdValue = ((DoubleToken) threshold.getToken()) 287 .doubleValue(); 288 double aggregateValue = ((DoubleToken) result).doubleValue(); 289 if (aggregateValue >= thresholdValue) { 290 _lastValueWarning = result; 291 String message = "WARNING: " + getName() 292 + " constraint monitor: Aggregate value of " 293 + aggregateValue 294 + ((aggregateValue == thresholdValue) ? " hits " 295 : " exceeds ") 296 + "threshold of " + threshold + "."; 297 MessageHandler.message(message); 298 } else { 299 // No warning. 300 _lastValueWarning = null; 301 } 302 } else { 303 // No warning requested. 304 _lastValueWarning = null; 305 } 306 } 307 return result; 308 } 309 310 /** Override the base class to mark this as needing evaluation even though 311 * there is no expression. 312 */ 313 @Override 314 public synchronized void invalidate() { 315 super.invalidate(); 316 _needsEvaluation = true; 317 } 318 319 /** Override the base class to establish a connection with any 320 * decorated objects it finds in scope in the container. 321 * @return The current list of value listeners, which are evaluated 322 * as a consequence of this call to validate(). 323 * @exception IllegalActionException If this variable or a 324 * variable dependent on this variable cannot be evaluated (and is 325 * not lazy) and the model error handler throws an exception. 326 * Also thrown if the change is not acceptable to the container. 327 */ 328 @Override 329 public Collection validate() throws IllegalActionException { 330 List<NamedObj> decoratedObjects = decoratedObjects(); 331 for (NamedObj decoratedObject : decoratedObjects) { 332 // The following will create the DecoratorAttributes if it does not 333 // already exist, and associate it with this decorator. 334 ConstraintMonitorAttributes decoratorAttributes = (ConstraintMonitorAttributes) decoratedObject 335 .getDecoratorAttributes(this); 336 decoratorAttributes.value.addValueListener(this); 337 } 338 return super.validate(); 339 } 340 341 /** Override the base class to mark that evaluation is needed regardless 342 * of the current expression. 343 * @param settable The object that has changed value. 344 */ 345 @Override 346 public void valueChanged(Settable settable) { 347 if (!_needsEvaluation) { 348 _needsEvaluation = true; 349 _notifyValueListeners(); 350 } 351 } 352 353 /////////////////////////////////////////////////////////////////// 354 //// protected methods //// 355 356 /** Add to the specified list all contained entities of the specified container 357 * that are not class definitions. This includes all the entities 358 * returned by {@link CompositeEntity#deepEntityList()}. 359 * If {@link #includeTransparents} is true, then it also 360 * includes transparent composite entities. 361 * If {@link #includeOpaqueContents} is true, then it also 362 * includes the entities contained within opaque composite entities. 363 * @param container The container of the entities. 364 * @param result The list to which to add the entities. 365 * @param transparents Specification of whether to include transparent 366 * composite entities. 367 * @param opaques Specification of whether to include the 368 * contents of opaque composite entities. 369 */ 370 protected void _addAllContainedEntities(CompositeEntity container, 371 List<NamedObj> result, boolean transparents, boolean opaques) { 372 try { 373 _workspace.getReadAccess(); 374 List<Entity> entities = container.entityList(); 375 for (Entity entity : entities) { 376 boolean isComposite = entity instanceof CompositeEntity; 377 if (!isComposite || ((CompositeEntity) entity).isOpaque() 378 || transparents) { 379 result.add(entity); 380 } 381 if (isComposite && (!((CompositeEntity) entity).isOpaque() 382 || opaques)) { 383 _addAllContainedEntities((CompositeEntity) entity, result, 384 transparents, opaques); 385 } 386 } 387 } finally { 388 _workspace.doneReading(); 389 } 390 } 391 392 /** Return true if the specified target is deeply contained by the specified container 393 * subject to the constraints given by the opaques parameter. 394 * @param container The container. 395 * @param target The object that may be contained by the container. 396 * @param opaques If true, then allow one or more intervening opaque composite actors 397 * in the hierarchy. 398 * @return True if the specified target is deeply contained by the container 399 */ 400 protected boolean _deepContains(CompositeEntity container, Entity target, 401 boolean opaques) { 402 try { 403 _workspace.getReadAccess(); 404 if (target != null) { 405 CompositeEntity targetContainer = (CompositeEntity) target 406 .getContainer(); 407 while (targetContainer != null) { 408 if (targetContainer == container) { 409 return true; 410 } 411 if (!opaques && targetContainer.isOpaque()) { 412 return false; 413 } 414 targetContainer = (CompositeEntity) targetContainer 415 .getContainer(); 416 } 417 } 418 return false; 419 } finally { 420 _workspace.doneReading(); 421 } 422 } 423 424 /** Evaluate the current expression to a token, which in this case means 425 * to sum the values of all the decorated objects. 426 * @exception IllegalActionException If the expression cannot 427 * be parsed or cannot be evaluated, or if a dependency loop is found. 428 */ 429 @Override 430 protected void _evaluate() throws IllegalActionException { 431 double result = 0.0; 432 List<NamedObj> decoratedObjects = decoratedObjects(); 433 for (NamedObj decoratedObject : decoratedObjects) { 434 Parameter value = (Parameter) decoratedObject 435 .getDecoratorAttribute(this, "value"); 436 result += ((DoubleToken) value.getToken()).doubleValue(); 437 } 438 setToken(new DoubleToken(result)); 439 _needsEvaluation = false; 440 } 441 442 /////////////////////////////////////////////////////////////////// 443 //// private field //// 444 445 /** Cached list of decorated objects. */ 446 private List<NamedObj> _decoratedObjects; 447 448 /** Version for _decoratedObjects. */ 449 private long _decoratedObjectsVersion = -1L; 450 451 /** To avoid duplicate warnings, when we issue a warning, record the value. */ 452 private Token _lastValueWarning = null; 453 454 /////////////////////////////////////////////////////////////////// 455 //// inner classes //// 456 457 /** Class containing the decorator attributes that decorate objects. 458 * In this case, there is exactly one decorator attribute called <i>value</i>. 459 */ 460 static public class ConstraintMonitorAttributes extends DecoratorAttributes 461 implements HierarchyListener { 462 463 public ConstraintMonitorAttributes(NamedObj container, 464 ConstraintMonitor decorator) 465 throws IllegalActionException, NameDuplicationException { 466 super(container, decorator); 467 _init(); 468 value.addValueListener(decorator); 469 container.addHierarchyListener(this); 470 } 471 472 public ConstraintMonitorAttributes(NamedObj container, String name) 473 throws IllegalActionException, NameDuplicationException { 474 super(container, name); 475 _init(); 476 container.addHierarchyListener(this); 477 } 478 479 public Parameter value; 480 481 /** Override the base class so that if the decorator already exists in 482 * scope, the decorator becomes a value listener to the value attribute. 483 * @exception IllegalActionException If the change is not acceptable 484 * to this container (not thrown in this base class). 485 */ 486 @Override 487 public void attributeChanged(Attribute attribute) 488 throws IllegalActionException { 489 // The following will establish a link to my decorator if it exists. 490 super.attributeChanged(attribute); 491 if (_decorator != null) { 492 value.addValueListener((ConstraintMonitor) _decorator); 493 } else { 494 super.attributeChanged(attribute); 495 } 496 } 497 498 /** Notify this object that the containment hierarchy above it has changed. 499 * @exception IllegalActionException If the change is not 500 * acceptable. 501 */ 502 @Override 503 public void hierarchyChanged() throws IllegalActionException { 504 ConstraintMonitor decorator = (ConstraintMonitor) getDecorator(); 505 if (_previousDecorator != null && decorator != _previousDecorator) { 506 // Force an evaluation of the decorator, since this attribute 507 // may no longer be in scope. 508 _previousDecorator.invalidate(); 509 } 510 } 511 512 /** Record the current decorator. 513 * @exception IllegalActionException If the change is not acceptable. 514 */ 515 @Override 516 public void hierarchyWillChange() throws IllegalActionException { 517 _previousDecorator = (ConstraintMonitor) getDecorator(); 518 } 519 520 private ConstraintMonitor _previousDecorator = null; 521 522 private void _init() 523 throws IllegalActionException, NameDuplicationException { 524 value = new Parameter(this, "value"); 525 value.setTypeEquals(BaseType.DOUBLE); 526 value.setExpression("0.0"); 527 } 528 } 529}