001/* Dataflow utilities
002
003 Copyright (c) 2004-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.util;
029
030import java.util.Comparator;
031
032import ptolemy.actor.IOPort;
033import ptolemy.actor.sched.NotSchedulableException;
034import ptolemy.data.BooleanToken;
035import ptolemy.data.IntToken;
036import ptolemy.data.Token;
037import ptolemy.data.expr.Parameter;
038import ptolemy.data.expr.TemporaryVariable;
039import ptolemy.data.expr.Variable;
040import ptolemy.kernel.Port;
041import ptolemy.kernel.util.IllegalActionException;
042import ptolemy.kernel.util.InternalErrorException;
043import ptolemy.kernel.util.KernelException;
044import ptolemy.kernel.util.NamedObj;
045import ptolemy.kernel.util.Settable;
046
047///////////////////////////////////////////////////////////////////
048//// DFUtilities
049
050/**
051 This class factors code out of the SDF domain, for use in different
052 schedulers, so that they can be implemented in a consistent fashion.
053 This interface contains static methods that are often useful from
054 outside of an SDFDirector, and so are provided here in an interface
055 that can be imported.
056
057 @author Stephen Neuendorffer
058 @version $Id$
059 @since Ptolemy II 4.1
060 @Pt.ProposedRating Red (neuendor)
061 @Pt.AcceptedRating Red (neuendor)
062 */
063public class DFUtilities {
064    ///////////////////////////////////////////////////////////////////
065    ////                         public inner classes              ////
066
067    /** A comparator for named objects.
068     */
069    public static class NamedObjComparator implements Comparator {
070        /** Compare two objects.
071         *  If the objects are not NamedObjs, then an InternalErrorException
072         *  is thrown.
073         *  @param object1 The first object to be compared.
074         *  @param object2 The second object to be compared.
075         *  @return 0 if the objects are the same.
076         */
077        @Override
078        public int compare(Object object1, Object object2) {
079            // Note: This is rather slow, because getFullName is not cached.
080
081            if (object1 instanceof NamedObj && object2 instanceof NamedObj) {
082                // Compare full names.
083                NamedObj namedObject1 = (NamedObj) object1;
084                NamedObj namedObject2 = (NamedObj) object2;
085                int compare = namedObject1.getFullName()
086                        .compareTo(namedObject2.getFullName());
087
088                if (compare != 0) {
089                    return compare;
090                }
091
092                // Compare class names.
093                Class class1 = namedObject1.getClass();
094                Class class2 = namedObject2.getClass();
095                compare = class1.getName().compareTo(class2.getName());
096
097                if (compare != 0) {
098                    return compare;
099                }
100
101                if (object1.equals(object2)) {
102                    return 0;
103                } else {
104                    // FIXME This should never happen, hopefully.  Otherwise
105                    // the comparator needs to be made more specific.
106                    throw new InternalErrorException("Comparator not "
107                            + "capable of comparing not equal objects.");
108                }
109            } else {
110                throw new InternalErrorException("Arguments to comparator "
111                        + "must be instances of NamedObj: " + object1 + ", "
112                        + object2);
113            }
114        }
115    }
116
117    ///////////////////////////////////////////////////////////////////
118    ////                         public methods                    ////
119
120    /** Return the number of tokens that will be produced or consumed on the
121     *  given port.   If the port is an input, then return its consumption
122     *  rate, or if the port is an output, then return its production rate.
123     *  @param port The given port.
124     *  @return The number of tokens that will be produced or consumed on the
125     *  given port.
126     *  @exception NotSchedulableException If the port is both an input and
127     *  an output, or is neither an input nor an output.
128     *  @exception IllegalActionException If a rate does not contain a
129     *  valid expression.
130     *  @see #setRate
131     */
132    public static int getRate(IOPort port)
133            throws NotSchedulableException, IllegalActionException {
134        if (port.isInput() && port.isOutput()) {
135            throw new NotSchedulableException(port,
136                    "Port is both an input and an output, which is not"
137                            + " allowed in SDF.");
138        } else if (port.isInput()) {
139            return getTokenConsumptionRate(port);
140        } else if (port.isOutput()) {
141            return getTokenProductionRate(port);
142        } else {
143            throw new NotSchedulableException(port,
144                    "Port is neither an input and an output, which is not"
145                            + " allowed in SDF.");
146        }
147    }
148
149    /** Get the Variable with the specified name in the given port, or
150     *  with the specified name preceded by an underscore.  If there
151     *  is no such variable, return null.
152     *  @param port The port.
153     *  @param name The name of the variable.
154     *  @return The variable with the specified name in the given port.
155     *  @see #setRateVariable(Port, String, int)
156     */
157    public static Variable getRateVariable(Port port, String name) {
158        Variable parameter = (Variable) port.getAttribute(name);
159
160        if (parameter == null) {
161            String altName = "_" + name;
162            parameter = (Variable) port.getAttribute(altName);
163        }
164
165        return parameter;
166    }
167
168    /** Get the integer value stored in the Variable with the
169     *  specified name.  If there is still no such variable, then
170     *  return the specified default.
171     *  @param port The port.
172     *  @param name The name of the variable.
173     *  @param defaultValue The default value of the variable.
174     *  @return A rate.
175     *  @exception IllegalActionException If the variable does not contain
176     *  a valid token, or the token is not an IntToken.
177     */
178    public static int getRateVariableValue(Port port, String name,
179            int defaultValue) throws IllegalActionException {
180        Variable parameter = getRateVariable(port, name);
181
182        if (parameter == null) {
183            return defaultValue;
184        }
185
186        Token token = parameter.getToken();
187
188        if (token == null) {
189            // The tokenConsumptionRate parameter is present, but was
190            // not set.  BooleanSelect had this problem.
191            return defaultValue;
192        }
193
194        if (token.isNil()) {
195            throw new IllegalActionException(port,
196                    "Port rate parameter value is missing (is nil).");
197        }
198
199        if (token instanceof IntToken) {
200            return ((IntToken) token).intValue();
201        } else {
202            throw new IllegalActionException(
203                    "Variable " + parameter.getFullName() + " was expected "
204                            + "to contain an IntToken, but instead "
205                            + "contained a " + token.getType() + ".");
206        }
207    }
208
209    /** Get the number of tokens that are consumed on the given port.
210     *  If the port is not an input port, then return zero.
211     *  Otherwise, return the value of the port's
212     *  <i>tokenConsumptionRate</i> parameter.  If this parameter does
213     *  not exist, then assume the actor is homogeneous and return
214     *  one.
215     *  @param port The given port.
216     *  @return The number of tokens the scheduler believes will be consumed
217     *  from the given input port during each firing.
218     *  @exception IllegalActionException If the tokenConsumptionRate
219     *  parameter has an invalid expression.
220     *  @see #setTokenConsumptionRate
221     */
222    public static int getTokenConsumptionRate(IOPort port)
223            throws IllegalActionException {
224        if (!port.isInput()) {
225            return 0;
226        } else {
227            return getRateVariableValue(port, "tokenConsumptionRate", 1);
228        }
229    }
230
231    /** Get the number of tokens that are initially
232     *  available on the given input port
233     *  after initialization.  If the port is not an
234     *  input port, then presumably any initial tokens
235     *  will be available on the inside.  Return the value of
236     *  the port's <i>tokenInitConsumption</i> parameter.   If the parameter
237     *  does not exist, then assume the port has no initial tokens.
238     *  a value of zero.
239     *  @param port The given port.
240     *  @return The number of tokens the scheduler believes will be available
241     *   at the given input port after initialization.
242     *  @exception IllegalActionException If the tokenInitConsumption
243     *   parameter has an invalid expression.
244     *  @see #setTokenInitConsumption
245     */
246    public static int getTokenInitConsumption(IOPort port)
247            throws IllegalActionException {
248        return getRateVariableValue(port, "tokenInitConsumption", 0);
249    }
250
251    /** Get the number of tokens that are produced on the given port
252     *  during initialization.  If the port is not an
253     *  output port, then the number of tokens is presumably the number
254     *  of initial tokens produced on the inside of the port.
255     *  The number of tokens returned is the value of
256     *  the port's <i>tokenInitProduction</i> parameter.   If the parameter
257     *  does not exist, then assume the actor is zero-delay and return
258     *  a value of zero.
259     *  @param port The given port.
260     *  @return The number of tokens the scheduler believes will be produced
261     *  from the given output port during initialization.
262     *  @exception IllegalActionException If the tokenInitProduction
263     *  parameter has an invalid expression.
264     *  @see #setTokenInitProduction
265     */
266    public static int getTokenInitProduction(IOPort port)
267            throws IllegalActionException {
268        return getRateVariableValue(port, "tokenInitProduction", 0);
269    }
270
271    /** Get the number of tokens that are produced on the given port.
272     *  If the port is not an output port, then return zero.
273     *  Otherwise, return the value of the port's
274     *  <i>tokenProductionRate</i> parameter. If the parameter does
275     *  not exist, then assume the actor is homogeneous and return a
276     *  rate of one.
277     *  @param port The given port.
278     *  @return The number of tokens the scheduler believes will be produced
279     *   from the given output port during each firing.
280     *  @exception IllegalActionException If the tokenProductionRate
281     *   parameter has an invalid expression.
282     *  @see #setTokenProductionRate
283     */
284    public static int getTokenProductionRate(IOPort port)
285            throws IllegalActionException {
286        if (!port.isOutput()) {
287            return 0;
288        } else {
289            return getRateVariableValue(port, "tokenProductionRate", 1);
290        }
291    }
292
293    /** If a variable with the given name does not exist, then create
294     *  a variable with the given name and set the value of that
295     *  variable to the specified value. The resulting variable is not
296     *  persistent and not editable, but will be visible to the user.
297     *  @param port The port.
298     *  @param name Name of the variable.
299     *  @param value The value.
300     *  @exception IllegalActionException If a new parameter can not be
301     *  created for the give port.
302     */
303    public static void setExpressionIfNotDefined(Port port, String name,
304            String value) throws IllegalActionException {
305        Variable rateParameter = (Variable) port.getAttribute(name);
306
307        if (rateParameter == null) {
308            try {
309                String altName = "_" + name;
310                rateParameter = (Variable) port.getAttribute(altName);
311
312                if (rateParameter == null) {
313                    rateParameter = new Parameter(port, altName);
314                    rateParameter.setVisibility(Settable.NOT_EDITABLE);
315                    rateParameter.setPersistent(false);
316                }
317
318                rateParameter.setExpression(value);
319                rateParameter.validate();
320            } catch (KernelException ex) {
321                throw new InternalErrorException(port, ex, "Should not occur");
322            }
323        }
324    }
325
326    /** If a variable with the given name does not exist, then create
327     *  a variable with the given name and set the value of that
328     *  variable to the specified value. The resulting variable is not
329     *  persistent and not editable, but will be visible to the user.
330     *  @param port The port.
331     *  @param name Name of the variable.
332     *  @param value The value.
333     *  @exception IllegalActionException If a new parameter can not be
334     *  created for the given port, or the given value is not an acceptable.
335     */
336    public static void setIfNotDefined(Port port, String name, int value)
337            throws IllegalActionException {
338        Variable rateParameter = (Variable) port.getAttribute(name);
339
340        if (rateParameter == null) {
341            try {
342                String altName = "_" + name;
343                rateParameter = (Variable) port.getAttribute(altName);
344
345                if (rateParameter == null) {
346                    rateParameter = new Parameter(port, altName);
347                    rateParameter.setVisibility(Settable.NOT_EDITABLE);
348                    rateParameter.setPersistent(false);
349                }
350
351                rateParameter.setToken(new IntToken(value));
352            } catch (KernelException ex) {
353                throw new InternalErrorException(port, ex, "Should not occur");
354            }
355        }
356    }
357
358    /** If the specified container does not contain a variable with
359     *  the specified name, then create such a variable and set its
360     *  value to the specified integer.  The resulting variable is not
361     *  persistent and not editable, but will be visible to the user.
362     *  If the variable does exist, then just set its value.
363     *  @param container The container.
364     *  @param name Name of the variable.
365     *  @param value The value.
366     *  @exception IllegalActionException If the variable exists and
367     *  its value cannot be set.
368     */
369    public static void setOrCreate(NamedObj container, String name, int value)
370            throws IllegalActionException {
371        Variable variable = _getOrCreate(container, name);
372        variable.setToken(new IntToken(value));
373    }
374
375    /** If the specified container does not contain a variable with
376     *  the specified name, then create such a variable and set its
377     *  expression to the specified string.  The resulting variable is not
378     *  persistent and not editable, but will be visible to the user.
379     *  If the variable does exist, then just set its expression.
380     *  @param container The container.
381     *  @param name Name of the variable.
382     *  @param expression The expression.
383     *  @exception IllegalActionException If the variable exists and
384     *  its value cannot be set.
385     */
386    public static void setOrCreate(NamedObj container, String name,
387            String expression) throws IllegalActionException {
388        Variable variable = _getOrCreate(container, name);
389        variable.setExpression(expression);
390    }
391
392    /** Set the rate variable with the specified name to the specified
393     *  value.  If it doesn't exist, create it.
394     *  @param port The port.
395     *  @param name The variable name.
396     *  @param rate The rate value.
397     *  @exception IllegalActionException If the rate is a negative integer,
398     *  or the rate can not be set.
399     *  @return The rate parameter.
400     *  @see #getRate(IOPort)
401     */
402    public static Variable setRate(Port port, String name, int rate)
403            throws IllegalActionException {
404        if (rate < 0) {
405            throw new IllegalActionException(
406                    "Negative rate is not allowed: " + rate);
407        }
408
409        Variable parameter = (Variable) port.getAttribute(name);
410
411        if (parameter != null) {
412            parameter.setToken(new IntToken(rate));
413        } else {
414            try {
415                // Use Variable rather than Parameter so the
416                // value is transient.
417                parameter = new Variable(port, name, new IntToken(rate));
418                parameter.setVisibility(Settable.NOT_EDITABLE);
419                parameter.setPersistent(false);
420            } catch (KernelException ex) {
421                throw new InternalErrorException(port, ex, "Should not occur");
422            }
423        }
424        return parameter;
425    }
426
427    /** If a variable with the given name does not exist, then create
428     *  a variable with the given name. Then set the value of the
429     *  variable to the specified value.
430     *  @param port The port.
431     *  @param name Name of the variable.
432     *  @param value The value.
433     *  @exception IllegalActionException If a new parameter can not be
434     *  created for the given port, or the given value is not an acceptable.
435     *  @see #getRateVariable(Port, String)
436     */
437    public static void setRateVariable(Port port, String name, int value)
438            throws IllegalActionException {
439        Variable rateParameter = (Variable) port.getAttribute(name);
440
441        if (rateParameter == null) {
442            try {
443                String altName = "_" + name;
444                rateParameter = (Variable) port.getAttribute(altName);
445
446                if (rateParameter == null) {
447                    rateParameter = new Parameter(port, altName);
448                    rateParameter.setVisibility(Settable.NOT_EDITABLE);
449                    rateParameter.setPersistent(false);
450                }
451            } catch (KernelException ex) {
452                throw new InternalErrorException(port, ex, "Should not occur");
453            }
454        }
455        rateParameter.setToken(new IntToken(value));
456    }
457
458    /** Set the <i>tokenConsumptionRate</i> parameter of the given port
459     *  to the given rate.  If no parameter exists, then create a new one.
460     *  The new one is an instance of Variable, so it is not persistent.
461     *  That is, it will not be saved in the MoML file if the model is
462     *  saved. The port is normally an input port, but this is not
463     *  checked.
464     *  @param port The given port.
465     *  @param rate The given rate.
466     *  @exception IllegalActionException If the rate is negative.
467     *  @see #getTokenConsumptionRate
468     */
469    public static void setTokenConsumptionRate(IOPort port, int rate)
470            throws IllegalActionException {
471        setRate(port, "tokenConsumptionRate", rate);
472    }
473
474    /** Set the <i>tokenInitConsumption</i> parameter of the given port to
475     *  the given rate.  If no parameter exists, then create a new one.
476     *  The new one is an instance of Variable, so it is not persistent.
477     *  That is, it will not be saved in the MoML file if the model is
478     *  saved. The port is normally an output port, but this is not
479     *  checked.
480     *  @param port The given port.
481     *  @param rate The given rate.
482     *  @exception IllegalActionException If the rate is negative.
483     *  @see #getTokenInitConsumption(IOPort)
484     */
485    public static void setTokenInitConsumption(IOPort port, int rate)
486            throws IllegalActionException {
487        setRate(port, "tokenInitConsumption", rate);
488    }
489
490    /** Set the <i>tokenInitProduction</i> parameter of the given port to
491     *  the given rate.  If no parameter exists, then create a new one.
492     *  The new one is an instance of Variable, so it is not persistent.
493     *  That is, it will not be saved in the MoML file if the model is
494     *  saved. The port is normally an output port, but this is not
495     *  checked.
496     *  @param port The given port.
497     *  @param rate The given rate.
498     *  @exception IllegalActionException If the rate is negative.
499     *  @see #getTokenInitProduction
500     */
501    public static void setTokenInitProduction(IOPort port, int rate)
502            throws IllegalActionException {
503        setRate(port, "tokenInitProduction", rate);
504    }
505
506    /** Set the <i>tokenProductionRate</i> parameter of the given port
507     *  to the given rate.  If no parameter exists, then create a new one.
508     *  The new one is an instance of Variable, so it is transient.
509     *  That is, it will not be exported to MoML files.
510     *  The port is normally an output port, but this is not checked.
511     *  @param port The given port.
512     *  @param rate The given rate.
513     *  @exception IllegalActionException If the rate is negative.
514     *  @see #getTokenProductionRate
515     */
516    public static void setTokenProductionRate(IOPort port, int rate)
517            throws IllegalActionException {
518        setRate(port, "tokenProductionRate", rate);
519    }
520
521    /** Depending on the given flag, add an invisible, persistent
522     *  variable named "_showRate" with value true to the given port
523     *  that indicates to the user interface that rate parameters on
524     *  the given port should be displayed in the user interface.
525     *  @param port The port.
526     *  @param flag The flag.
527     *  @exception IllegalActionException If a new parameter can not be
528     *  created for the given port, or the given flag is not an acceptable.
529     */
530    public static void showRate(Port port, boolean flag)
531            throws IllegalActionException {
532        String name = "_showRate";
533
534        // Look for an existing parameter.
535        Variable variable = (Variable) port.getAttribute(name);
536
537        if (variable == null) {
538            try {
539                variable = new Parameter(port, name);
540                variable.setVisibility(Settable.EXPERT);
541                variable.setPersistent(false);
542            } catch (KernelException ex) {
543                throw new InternalErrorException(port, ex, "Should not occur");
544            }
545        }
546
547        variable.setToken(BooleanToken.getInstance(flag));
548    }
549
550    ///////////////////////////////////////////////////////////////////
551    ////                         private methods                   ////
552    // If a variable exists with the given container and given name,
553    // then return it. Otherwise, create the variable and return it.
554    private static Variable _getOrCreate(NamedObj container, String name) {
555        Variable variable = (Variable) container.getAttribute(name);
556
557        if (variable == null) {
558            try {
559                variable = new TemporaryVariable(container, name);
560                variable.setVisibility(Settable.NOT_EDITABLE);
561                variable.setPersistent(false);
562            } catch (KernelException ex) {
563                throw new InternalErrorException(container, ex,
564                        "Should not occur");
565            }
566        }
567
568        return variable;
569    }
570}