001/* A count and a list of schedule elements.
002
003 Copyright (c) 1998-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.sched;
029
030import java.util.ArrayList;
031import java.util.ConcurrentModificationException;
032import java.util.Iterator;
033import java.util.List;
034import java.util.NoSuchElementException;
035
036import ptolemy.actor.Actor;
037import ptolemy.kernel.util.InternalErrorException;
038import ptolemy.kernel.util.InvalidStateException;
039
040///////////////////////////////////////////////////////////////////
041//// Schedule
042
043/**
044 This class represents a static schedule of actor executions.  An
045 instance of this class is returned by the scheduler of a model to
046 represent order of actor firings in the model.  A schedule consists of
047 a list of schedule elements and the number of times the schedule
048 should repeat (called the <i>iteration count</i>).
049
050 <p>Each element of
051 the schedule is represented by an instance of the ScheduleElement
052 class.  Each element may correspond to a number of firings of a single
053 actor (represented by the Firing class) or an entire sub-schedule
054 (represented by a hierarchically contained instance of this class).
055 This nesting allows this concise representation of looped schedules.
056 The nesting can be arbitrarily deep, but must be a tree where the
057 leaf nodes represent actor firings.  It is up to the scheduler to
058 enforce this requirement. </p>
059
060 <p>The add() and remove() methods are used to add or
061 remove schedule elements. Only elements of type ScheduleElement (Schedule
062 or Firing) may be added to the schedule list. Otherwise an exception will
063 occur. </p>
064
065 <p>The iteration count is set by the
066 setIterationCount() method. If this method is not invoked, a default value
067 of one will be used. </p>
068
069 <p>As an example, suppose that we have an SDF graph containing actors
070 A, B, C, and D, with the firing order ABCBCBCDD.</p>
071
072 <p>This firing order can be represented by a simple looped schedule.  The
073 code to create this schedule appears below.</p>
074
075
076 <pre>
077 Schedule S = new Schedule();
078 Firing S1 = new Firing();
079 Schedule S2 = new Schedule();
080 Firing S3 = new Firing();
081 S.add(S1);
082 S.add(S2);
083 S.add(S3);
084 S1.setActor(A);
085 S2.setIterationCount(3);
086 Firing S2_1 = new Firing();
087 Firing S2_2 = new Firing();
088 S2_1.setActor(B);
089 S2_2.setActor(C);
090 S2.add(S2_1);
091 S2.add(S2_2);
092 S3.setIterationCount(2);
093 S3.setActor(D);
094 </pre>
095 <p> Note that this implementation is not synchronized. It is therefore not safe
096 for a thread to make modifications to the schedule structure while
097 multiple threads are concurrently accessing the schedule.</p>
098 <h1>References</h1>
099 <p>S. S. Bhattacharyya, P K. Murthy, and E. A. Lee,
100 Software Syntheses from Dataflow Graphs, Kluwer Academic Publishers, 1996.</p>
101
102 @author Brian K. Vogel, Steve Neuendorffer
103 @version $Id$
104 @since Ptolemy II 1.0
105 @Pt.ProposedRating Green (vogel)
106 @Pt.AcceptedRating Yellow (chf)
107 @see ptolemy.actor.sched.Firing
108 @see ptolemy.actor.sched.ScheduleElement
109 */
110public class Schedule extends ScheduleElement {
111    /** Construct a schedule with iteration count of one and an
112     *  empty schedule list. This constructor should be used when
113     *  creating a root schedule. The constructor that takes a
114     *  parameter should be used when creating a subschedule.
115     */
116    public Schedule() {
117        super();
118
119        // This list will contain the schedule elements.
120        _schedule = new ArrayList(3);
121
122        //_firingIteratorVersion = 0;
123        // Default tree depth to use for allocation state arrays
124        // for the firingIterator() method. The arrays will be
125        // dynamically resized as needed. 3 was an arbitrary
126        // choice. Any positive integer will do. The arrays are
127        // only resized while iterating through the first iterator
128        // that was created since the schedule was modified.
129        _treeDepth = 3;
130    }
131
132    ///////////////////////////////////////////////////////////////////
133    ////                         public methods                    ////
134
135    /** Return the actor sequence in the schedule.
136     *  A runtime exception is thrown if the
137     *  underlying schedule structure is modified while the iterator
138     *  is active.
139     *  <p>
140     *  Implementation note: This method is optimized to be memory
141     *  efficient. It walks the schedule tree structure as the
142     *  iterator methods are invoked.
143     *
144     *  @return An iterator over instances of Firing.
145     *  @exception ConcurrentModificationException If the
146     *   underlying schedule structure is modified while the iterator
147     *   is active.
148     *  @exception InternalErrorException If the schedule contains
149     *   any leaf nodes that are not an instance of Firing.
150     */
151    @Override
152    public Iterator actorIterator() {
153        return new ActorIterator();
154    }
155
156    /** Append the specified schedule element to the end of the schedule
157     *  list. This element must be an instance of ScheduleElement.
158     *
159     *  @param element The schedule element to add.
160     */
161    public void add(ScheduleElement element) {
162        // Give element a reference to this schedule so that it can
163        // notify this schedule (via _incrementVersions()) when
164        // element is modified.
165        element.setParent(this);
166        _incrementVersion();
167        _schedule.add(element);
168    }
169
170    /** Insert the specified schedule element at the specified position in
171     *  the schedule list. This element must be an instance of
172     *  ScheduleElement. An exception is thrown if the index is out of
173     *  range.
174     *
175     *  @param index The index at which the specified element is to be
176     *   inserted.
177     *  @param element The schedule element to add.
178     *  @exception IndexOutOfBoundsException If the specified index is out of
179     *   range (index &lt; 0 || index &gt; size()).
180     */
181    public void add(int index, ScheduleElement element) {
182        // Give element a reference to this schedule so that it can
183        // notify this schedule (via _incrementVersions()) when
184        // element is modified.
185        element.setParent(this);
186        _incrementVersion();
187        _schedule.add(index, element);
188    }
189
190    /** Return the actor invocation sequence of this schedule in the form
191     *  of a sequence of firings. All of the lowest-level nodes of the
192     *  schedule should be an instance of Firing. Otherwise an exception will
193     *  occur at some point in the iteration.
194     *  <p>
195     *  A runtime exception is thrown if the
196     *  underlying schedule structure is modified while the iterator
197     *  is active.
198     *  <p>
199     *  Implementation note: This method is optimized to be memory
200     *  efficient. It walks the schedule tree structure as the
201     *  iterator methods are invoked.
202     *
203     *  @return An iterator over a sequence of firings.
204     *  @exception ConcurrentModificationException If the
205     *   underlying schedule structure is modified while the iterator
206     *   is active.
207     */
208    @Override
209    public Iterator firingIterator() {
210        return new FiringIterator(this);
211    }
212
213    /** Return the element at the specified position in the list.
214     *
215     * @param index The index of the element to return.
216     * @return The element at the specified position in the list.
217     */
218    public ScheduleElement get(int index) {
219        return _schedule.get(index);
220    }
221
222    /** Return an iterator over the schedule elements of this schedule.
223     *  The ordering of elements in the iterator sequence is the order
224     *  of the schedule list. The elements of the iterator sequence are
225     *  instances of Firing or Schedule.
226     *  <p>
227     *  A runtime exception is thrown if the
228     *  underlying schedule structure is modified while the iterator
229     *  is active.
230     *
231     *  @return An iterator over the schedule elements of this schedule.
232     *  @exception ConcurrentModificationException If the
233     *   underlying schedule structure is modified while the iterator
234     *   is active.
235     */
236    public Iterator iterator() {
237        return _schedule.iterator();
238    }
239
240    /** Remove the schedule element at the specified position in the
241     *  schedule list.
242     *
243     *  @param index The index of the schedule element to be removed.
244     *  @return The schedule element that was removed.
245     */
246    public ScheduleElement remove(int index) {
247        _incrementVersion();
248        return _schedule.remove(index);
249    }
250
251    /** Return the number of elements in this list.
252     *
253     *  @return The number of elements in this list.
254     */
255    public int size() {
256        return _schedule.size();
257    }
258
259    /**
260     * Output a string representation of this Schedule.
261     */
262    @Override
263    public String toString() {
264        StringBuffer result = new StringBuffer("Execute Schedule{\n");
265
266        Iterator i = iterator();
267        while (i.hasNext()) {
268            ScheduleElement element = (ScheduleElement) i.next();
269            result.append(element + "\n");
270        }
271
272        result.append("}");
273
274        if (getIterationCount() > 1) {
275            result.append(" " + getIterationCount() + " times");
276        }
277
278        return result.toString();
279    }
280
281    ///////////////////////////////////////////////////////////////////
282    ////                         inner classes                     ////
283
284    /** An adapter class for iterating over the actors of this
285     *  schedule. An exception is thrown if the schedule structure
286     *  changes while this iterator is active.
287     */
288    private class ActorIterator implements Iterator {
289        /** Construct a ScheduleIterator.
290         */
291        public ActorIterator() {
292            _advance = true;
293            _firingIterator = firingIterator();
294            _currentVersion = _getVersion();
295            _currentIteration = 0;
296        }
297
298        /** Return true if the iteration has more elements.
299         *
300         *  @exception ConcurrentModificationException If the schedule
301         *   data structure has changed since this iterator
302         *   was created.
303         *  @return true if the iterator has more elements.
304         */
305        @Override
306        public boolean hasNext() {
307            if (_currentVersion != _getVersion()) {
308                throw new ConcurrentModificationException(
309                        "Schedule structure changed while iterator is active.");
310            } else if (_advance == true) {
311                boolean returnValue;
312
313                if (_currentFiring == null) {
314                    returnValue = _firingIterator.hasNext();
315
316                    if (returnValue == true) {
317                        _currentFiring = (Firing) _firingIterator.next();
318                        _currentActor = _currentFiring.getActor();
319                        _currentIteration++;
320                        _advance = false;
321                        _lastHasNext = true;
322                        return _lastHasNext;
323                    } else {
324                        _advance = false;
325                        _lastHasNext = false;
326                        return _lastHasNext;
327                    }
328                } else {
329                    if (_currentIteration < _currentFiring
330                            .getIterationCount()) {
331                        _currentIteration++;
332                        _advance = false;
333                        _lastHasNext = true;
334                        return _lastHasNext;
335                    } else {
336                        _currentIteration = 0;
337                        returnValue = _firingIterator.hasNext();
338
339                        if (returnValue == true) {
340                            _currentFiring = (Firing) _firingIterator.next();
341                            _currentActor = _currentFiring.getActor();
342                            _currentIteration++;
343                            _advance = false;
344                            _lastHasNext = true;
345                            return _lastHasNext;
346                        } else {
347                            _advance = false;
348                            _lastHasNext = false;
349                            return _lastHasNext;
350                        }
351                    }
352                }
353            } else {
354                return _lastHasNext;
355            }
356        }
357
358        /** Return the next object in the iteration.
359         *
360         *  @exception InvalidStateException If the schedule
361         *   data structure has changed since this iterator
362         *   was created.
363         *  @return the next object in the iteration.
364         */
365        @Override
366        public Object next() throws NoSuchElementException {
367            if (!hasNext()) {
368                throw new NoSuchElementException("No element to return.");
369            } else if (_currentVersion != _getVersion()) {
370                throw new ConcurrentModificationException(
371                        "Schedule structure changed while iterator is active.");
372            } else {
373                _advance = true;
374                return _currentActor;
375            }
376        }
377
378        /** Throw an exception, since removal is not allowed. It really
379         *  doesn't make sense to remove an actor from an actor invocation
380         *  sequence anyway.
381         */
382        @Override
383        public void remove() {
384            throw new UnsupportedOperationException();
385        }
386
387        private Iterator _firingIterator;
388
389        private Firing _currentFiring;
390
391        private Actor _currentActor;
392
393        private long _currentVersion;
394
395        private int _currentIteration;
396
397        private boolean _advance;
398
399        private boolean _lastHasNext;
400    }
401
402    /** An adapter class for iterating over the firings of this
403     *  schedule. An exception is thrown if the schedule structure
404     *  changes while this iterator is active. The iterator walks
405     *  the schedule tree as the hasNext() and next() methods are
406     *  invoked, using a small number of state variables.
407     */
408    private class FiringIterator implements Iterator {
409        /** Construct a ScheduleIterator.
410         */
411        public FiringIterator(Schedule schedule) {
412            // If _advance is true, then hasNext() can move
413            // to the next node when invoked.
414            _advance = true;
415            _startingVersion = _getVersion();
416
417            // state. This is the current position as we walk
418            // the schedule tree.
419            _currentNode = schedule;
420
421            // The depth of _currentNode in the schedule tree.
422            // Depth 0 corresponds to the level of the root node
423            // of the tree.
424            _currentDepth = 0;
425
426            // These state arrays are dynamically increased
427            // in size as we deeper into the tree. Their
428            // length is equal to the number of nesting levels
429            // in the schedule.
430            _iterationCounts = new int[_treeDepth];
431            _horizontalNodePosition = new int[_treeDepth];
432        }
433
434        /** Return true if the iteration has more elements.
435         *
436         *  @exception ConcurrentModificationException If the schedule
437         *   data structure has changed since this iterator
438         *   was created.
439         *  @exception InternalErrorException If the schedule contains
440         *   any leaf nodes that are not an instance of Firing.
441         *  @return true if the iterator has more elements.
442         */
443        @Override
444        public boolean hasNext() {
445            // This code may look messy, but it simply walks the
446            // schedule tree.
447            if (_startingVersion != _getVersion()) {
448                throw new ConcurrentModificationException(
449                        "Schedule structure changed while iterator is active.");
450            } else if (_advance == true) {
451                // Try to go to the next firing node in the tree. Return
452                // false if we fail.
453                if (_currentNode instanceof Firing) {
454                    Schedule scheduleNode = _backTrack(_currentNode);
455                    _currentNode = _findLeafNode(scheduleNode);
456
457                    if (_currentNode == null) {
458                        // There are no more Firings in the tree.
459                        _advance = false;
460                        _lastHasNext = false;
461                        return _lastHasNext;
462                    }
463                } else if (_currentNode instanceof Schedule) {
464                    // This condition should only happen for the first element
465                    // in the iteration.
466                    _currentNode = _findLeafNode((Schedule) _currentNode);
467
468                    if (_currentNode == null) {
469                        // There are no mo Firings in the tree at all.
470                        _advance = false;
471                        _lastHasNext = false;
472                        return _lastHasNext;
473                    }
474                } else {
475                    // Throw runtime exception.
476                    throw new InternalErrorException(
477                            "Encountered a ScheduleElement that "
478                                    + "is not an instance "
479                                    + "of Schedule or Firing.");
480                }
481
482                _advance = false;
483                _lastHasNext = true;
484                return _lastHasNext;
485            } else {
486                return _lastHasNext;
487            }
488        }
489
490        /** Return the next object in the iteration.
491         *
492         *  @exception InvalidStateException If the schedule
493         *   data structure has changed since this iterator
494         *   was created.
495         *  @return the next object in the iteration.
496         */
497        @Override
498        public Object next() throws NoSuchElementException {
499            if (!hasNext()) {
500                throw new NoSuchElementException("No element to return.");
501            } else if (_startingVersion != _getVersion()) {
502                throw new ConcurrentModificationException(
503                        "Schedule structure changed while iterator is active.");
504            } else {
505                _advance = true;
506                return _currentNode;
507            }
508        }
509
510        /** Throw an exception, since removal is not allowed. It really
511         *  doesn't make sense to remove a firing from the firing
512         *  sequence anyway, since there is not a 1-1 correspondence
513         *  between the firing in a firing iterator and a firing in the
514         *  schedule.
515         */
516        @Override
517        public void remove() {
518            throw new UnsupportedOperationException();
519        }
520
521        ///////////////////////////////////////////////////////////////////
522        ////                         private methods                 ////
523
524        /** Start at the specified node in the schedule tree and
525         *  move up the tree (towards the root node) until we
526         *  find a node the has children we have not iterated through
527         *  yet or children that we have not iterated through enough
528         *  times (not reached the maximum iteration count).
529         *
530         *  @param firingNode The starting node to backtrack from.
531         */
532        private Schedule _backTrack(ScheduleElement firingNode) {
533            if (_currentDepth == 0) {
534                // Don't backtrack past the root node.
535                return null;
536            }
537
538            _currentDepth--;
539
540            Schedule node = (Schedule) firingNode._parent;
541
542            if (node == null) {
543                return null;
544            } else if (node
545                    .size() > ++_horizontalNodePosition[_currentDepth + 1]) {
546                return node;
547            } else if (++_iterationCounts[_currentDepth] < node
548                    .getIterationCount()) {
549                _horizontalNodePosition[_currentDepth + 1] = 0;
550                return node;
551            }
552
553            _horizontalNodePosition[_currentDepth + 1] = 0;
554            _iterationCounts[_currentDepth] = 0;
555            return _backTrack(node);
556        }
557
558        /** Start at the specified node and move down the tree
559         *  (away from the root node) until we find the next Firing
560         *  in the iteration order. Through a runtime exception
561         *  if we encounter a leaf node that is not an instance of
562         *  Firing.
563         *
564         *  @param node The schedule node to start from.
565         */
566        private ScheduleElement _findLeafNode(Schedule node) {
567            // Check if we need to resize the arrays.
568            if (_iterationCounts.length - 1 < _currentDepth + 2) {
569                // Need to resize.
570                int[] temp = new int[_currentDepth + 2];
571
572                for (int i = 0; i < _iterationCounts.length; i++) {
573                    temp[i] = _iterationCounts[i];
574                }
575
576                _iterationCounts = temp;
577
578                int[] temp2 = new int[_currentDepth + 2];
579
580                for (int i = 0; i < _horizontalNodePosition.length; i++) {
581                    temp2[i] = _horizontalNodePosition[i];
582                }
583
584                _horizontalNodePosition = temp2;
585
586                // Set new max tree depth. Any new iterators will
587                // create state arrays with this length to avoid
588                // needing to resize in the future (provide the
589                // schedule structure is not modified).
590                _treeDepth = _currentDepth + 2;
591            }
592
593            if (node == null) {
594                return null;
595            } else if (node
596                    .size() > _horizontalNodePosition[_currentDepth + 1]) {
597                _currentDepth++;
598
599                ScheduleElement nodeElement = node
600                        .get(_horizontalNodePosition[_currentDepth]);
601
602                if (nodeElement instanceof Firing) {
603                    return nodeElement;
604                } else {
605                    return _findLeafNode((Schedule) nodeElement);
606                }
607            } else if (node.size() == 0) {
608                Schedule scheduleNode = _backTrack(_currentNode);
609                return _findLeafNode(scheduleNode);
610            } else if (_iterationCounts[_currentDepth] < node
611                    .getIterationCount()) {
612                ScheduleElement nodeElement = node.get(0);
613                _currentDepth++;
614
615                if (nodeElement instanceof Firing) {
616                    return nodeElement;
617                } else {
618                    return _findLeafNode((Schedule) nodeElement);
619                }
620            } else {
621                return null;
622            }
623        }
624
625        ///////////////////////////////////////////////////////////////////
626        ////                         private variables                 ////
627        private boolean _advance;
628
629        private ScheduleElement _currentNode;
630
631        private boolean _lastHasNext;
632
633        // The current depth in the schedule tree.
634        private int _currentDepth;
635
636        private long _startingVersion;
637
638        // This array contains the iteration counts of schedule elements
639        // indexed by tree depth.
640        // This array will grow to the depth of the schedule tree.
641        private int[] _iterationCounts;
642
643        private int[] _horizontalNodePosition;
644    }
645
646    ///////////////////////////////////////////////////////////////////
647    ////                         protected variables               ////
648
649    /** The list of schedule elements contained by this schedule. */
650    protected List<ScheduleElement> _schedule;
651
652    ///////////////////////////////////////////////////////////////////
653    ////                         private variables                 ////
654    // The list of Firings for this schedule.
655    //private List _firingList;
656    // The current version of the firing iterator list.
657    //private long _firingIteratorVersion;
658    // The depth of this schedule tree. This may grow.
659    private int _treeDepth;
660}