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}