001/* A count and a list of schedule elements.
002
003 Copyright (c) 2003-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.graph.sched;
029
030import java.util.ArrayList;
031import java.util.Collections;
032import java.util.ConcurrentModificationException;
033import java.util.HashMap;
034import java.util.Iterator;
035import java.util.LinkedList;
036import java.util.List;
037import java.util.Map;
038import java.util.NoSuchElementException;
039
040import ptolemy.kernel.util.InternalErrorException;
041import ptolemy.kernel.util.InvalidStateException;
042
043///////////////////////////////////////////////////////////////////
044//// Schedule
045
046/**
047 This class represents a static schedule of firing elements invocation.  An
048 instance of this class is returned by the scheduler of a model to
049 represent order of firing element firings in the model.  A schedule consists of
050 a list of schedule elements and the number of times the schedule
051 should repeat (called the <i>iteration count</i>). <p>
052
053 Each element of
054 the schedule is represented by an instance of the ScheduleElement
055 class.  Each element may correspond to a number of firings of a single
056 firing element (represented by the Firing class) or an entire sub-schedule
057 (represented by a hierarchically contained instance of this class).
058 This nesting allows this concise representation of looped schedules.
059 The nesting can be arbitrarily deep, but must be a tree where the
060 leaf nodes represent firings.  It is up to the scheduler to
061 enforce this requirement. <p>
062
063 The add() and remove() methods are used to add or
064 remove schedule elements. Only elements of type ScheduleElement (Schedule
065 or Firing) may be added to the schedule list.
066
067 The iteration count is set by the
068 setIterationCount() method. If this method is not invoked, a default value
069 of one will be used. <p>
070
071 As an example, suppose that we have an SDF graph containing actors
072 A, B, C, and D, with the firing order ABCBCBCDD.
073 This firing order can be represented by a simple looped schedule.
074 The code to create this schedule appears below.
075 <p>
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.setFiringElement(A);
085 *       S2.setIterationCount(3);
086 *       Firing S2_1 = new Firing();
087 *       Firing S2_2 = new Firing();
088 *       S2_1.setFiringElement(B);
089 *       S2_2.setFiringElement(C);
090 *       S2.add(S2_1);
091 *       S2.add(S2_2);
092 *       S3.setIterationCount(2);
093 *       S3.setFiringElement(D);
094 </pre>
095 <p>
096
097 Note that this implementation is not synchronized. It is therefore not safe
098 for a thread to make modifications to the schedule structure while
099 multiple threads are concurrently accessing the schedule.
100 <h1>References</h1>
101 S. S. Bhattacharyya, P K. Murthy, and E. A. Lee,
102 Software Syntheses from Dataflow Graphs, Kluwer Academic Publishers, 1996.
103
104 @since Ptolemy II 4.0
105 @Pt.ProposedRating red (shahrooz)
106 @Pt.AcceptedRating red (ssb)
107 @author Shahrooz Shahparnia, Mingyung Ko,
108 University of Maryland at College Park, based on a file by
109 Brian K. Vogel, Steve Neuendorffer
110 @version $Id$
111 @see ptolemy.graph.sched.Firing
112 @see ptolemy.graph.sched.Schedule
113 @see ptolemy.graph.sched.ScheduleElement
114 */
115public class Schedule extends ScheduleElement {
116    /** Construct a schedule with iteration count of one and an
117     *  empty schedule list. This constructor should be used when
118     *  creating a root schedule.
119     */
120    public Schedule() {
121        super();
122
123        // This list will contain the schedule elements.
124        _schedule = new LinkedList();
125
126        //_firingIteratorVersion = 0;
127        // Default tree depth to use for allocation state arrays
128        // for the firingIterator() method. The arrays will be
129        // dynamically resized as needed. 3 was an arbitrary
130        // choice. Any positive integer will do. The arrays are
131        // only resized while iterating through the first iterator
132        // that was created since the schedule was modified.
133        _treeDepth = 3;
134    }
135
136    /** Construct a schedule with iteration count of one and an
137     *  empty schedule list. This constructor should be used when
138     *  creating a root schedule. If this constructor is used,
139     *  {@link ScheduleElement}s containing firing elements of the given class
140     *  would only be accepted as next elements for this schedule.
141     *
142     *  @param firingElementClass The given class type.
143     */
144    public Schedule(Class firingElementClass) {
145        super(firingElementClass);
146
147        // This list will contain the schedule elements.
148        _schedule = new LinkedList();
149
150        //_firingIteratorVersion = 0;
151        // Default tree depth to use for allocation state arrays
152        // for the firingIterator() method. The arrays will be
153        // dynamically resized as needed. 3 was an arbitrary
154        // choice. Any positive integer will do. The arrays are
155        // only resized while iterating through the first iterator
156        // that was created since the schedule was modified.
157        _treeDepth = 3;
158    }
159
160    ///////////////////////////////////////////////////////////////////
161    ////                         public methods                    ////
162
163    /** Append the specified schedule element to the end of the schedule
164     *  list. This element must be an instance of ScheduleElement.
165     *
166     *  @param element The schedule element to add.
167     */
168    public void add(ScheduleElement element) {
169        add(_schedule.size(), element);
170    }
171
172    /** Insert the specified schedule element at the specified position in
173     *  the schedule list. This element must be an instance of
174     *  ScheduleElement. An exception is thrown if the index is out of
175     *  range.
176     *
177     *  @param index The index at which the specified element is to be
178     *   inserted.
179     *  @param element The schedule element to add.
180     *  @exception IndexOutOfBoundsException If the specified index is out of
181     *   range (index &lt; 0 || index &gt; size()).
182     */
183    public void add(int index, ScheduleElement element) {
184        // Give element a reference to this schedule so that it can
185        // notify this schedule (via _incrementVersions()) when
186        // element is modified.
187        if (this.firingElementClass() != null) {
188            Class firingElementClass = this.firingElementClass();
189            Class elementClass = element.firingElementClass();
190
191            if (elementClass != null) {
192                if (firingElementClass.isAssignableFrom(elementClass)) {
193                    element.setParent(this);
194                    _incrementVersion();
195                    _schedule.add(index, element);
196                } else {
197                    throw new RuntimeException("Attempt to add a non "
198                            + "authorized firing element");
199                }
200            } else {
201                throw new RuntimeException(
202                        "Attempt to add a non " + "authorized firing element");
203            }
204        } else {
205            element.setParent(this);
206            _incrementVersion();
207            _schedule.add(index, element);
208        }
209    }
210
211    /** The number of times the given firing element appears in the
212     *  schedule. Generally, firing elements represent program blocks.
213     *  Appearances of elements indicate how many copies of codes are
214     *  in the schedule. The more appearances, the larger program
215     *  space is required.
216     *
217     *  @param firingElement The given firing element.
218     *  @return The number of appearances.
219     */
220    public int appearanceCount(Object firingElement) {
221        return firings(firingElement).size();
222    }
223
224    /** Return the firing element invocation sequence of the schedule in the
225     *  form of a sequence of firing elements. For a valid schedule, all of the
226     *  lowest-level nodes should be an instance of Firing. If the
227     *  schedule is not valid, then the returned iterator will contain
228     *  null elements.
229     *  <p>
230     *  A runtime exception is thrown if the
231     *  underlying schedule structure is modified while the iterator
232     *  is active.
233     *
234     *  @return An iterator over a sequence of firing elements.
235     *  @exception ConcurrentModificationException If the
236     *   underlying schedule structure is modified while the iterator
237     *   is active.
238     */
239    @Override
240    public Iterator firingElementIterator() {
241        return new FiringElementIterator();
242    }
243
244    /** Return the Firing invocation sequence of this schedule in the form
245     *  of a sequence of firings. All of the lowest-level nodes of the
246     *  schedule should be an instance of Firing. Otherwise an exception will
247     *  occur at some point in the iteration.
248     *  <p>
249     *  A runtime exception is thrown if the
250     *  underlying schedule structure is modified while the iterator
251     *  is active.
252     *  <p>
253     *  Implementation note: This method is optimized to be memory
254     *  efficient. It walks the schedule tree structure as the
255     *  iterator methods are invoked.
256     *
257     *  @return An iterator over a sequence of firings.
258     *  @exception ConcurrentModificationException If the
259     *   underlying schedule structure is modified while the iterator
260     *   is active.
261     */
262    @Override
263    public Iterator firingIterator() {
264        return new FiringIterator(this);
265    }
266
267    /** Get firings for the firing element. This is used in determining
268     *  element appearances.
269     *
270     *  @param firingElement The given firing element.
271     *  @return A list of firings for the firing element.
272     */
273
274    //FIXME: It's probably suitable for 'protected' or 'private'.
275    //FIXME: The returned type can be replaced with un-ordered
276    //       collection, eg. Set, if the sequence of firings
277    //       doesn't matter.
278    public List firings(Object firingElement) {
279        Map firingElementFiringsMap = _getFiringElementFiringsMap();
280        return Collections.unmodifiableList(
281                (List) firingElementFiringsMap.get(firingElement));
282    }
283
284    /** Return the element at the specified position in the list.
285     *
286     * @param index The index of the element to return.
287     * @return The element at the specified position in the list.
288     */
289    public ScheduleElement get(int index) {
290        return (ScheduleElement) _schedule.get(index);
291    }
292
293    /** Return an iterator over the schedule elements of this schedule.
294     *  The ordering of elements in the iterator sequence is the order
295     *  of the schedule list. The elements of the iterator sequence are
296     *  instances of Firing or Schedule.
297     *  <p>
298     *  A runtime exception is thrown if the
299     *  underlying schedule structure is modified while the iterator
300     *  is active.
301     *
302     *  @return An iterator over the schedule elements of this schedule.
303     *  @exception ConcurrentModificationException If the
304     *   underlying schedule structure is modified while the iterator
305     *   is active.
306     */
307    public Iterator iterator() {
308        return _schedule.iterator();
309    }
310
311    /** Get the lexical order of firing elements. Only single appearance
312     *  schedules are qualified for this operation. It's meaningless to
313     *  have the order for multiple appearance schedules.
314     *
315     *  @return The lexical order of firing elements.
316     *  @exception IllegalStateException If this is not a single
317     *             appearance schedule.
318     */
319    public List lexicalOrder() {
320        List lexicalList = new ArrayList();
321        Iterator firingInstances = _getFiringInstancesList().iterator();
322
323        while (firingInstances.hasNext()) {
324            Firing firing = (Firing) firingInstances.next();
325            Object firingElement = firing.getFiringElement();
326
327            if (lexicalList.contains(firingElement)) {
328                throw new IllegalStateException(
329                        "Not a single appearance schedule to compute "
330                                + "the lexical order of nodes.");
331            } else {
332                lexicalList.add(firingElement);
333            }
334        }
335
336        return Collections.unmodifiableList(lexicalList);
337    }
338
339    /** Get the maximum appearance counts of all firing elements in
340     *  the schedule.
341     *
342     *  @return The maximum appearance counts.
343     */
344    public int maxAppearanceCount() {
345        int maxCount = 0;
346        Iterator firingLists = _getFiringElementFiringsMap().values()
347                .iterator();
348
349        while (firingLists.hasNext()) {
350            int firingListSize = ((List) firingLists.next()).size();
351
352            if (maxCount < firingListSize) {
353                maxCount = firingListSize;
354            }
355        }
356
357        return maxCount;
358    }
359
360    /** Remove the schedule element at the specified position in the
361     *  schedule list.
362     *
363     *  @param index The index of the schedule element to be removed.
364     *  @return The schedule element that was removed.
365     */
366    public ScheduleElement remove(int index) {
367        _incrementVersion();
368        return (ScheduleElement) _schedule.remove(index);
369    }
370
371    /** Return the number of elements in this list.
372     *
373     *  @return The number of elements in this list.
374     */
375    public int size() {
376        return _schedule.size();
377    }
378
379    /** Print the schedule in a nested parenthesis style. Please see
380     *  {@link ScheduleElement#toParenthesisString(Map, String)}.
381     *
382     *  @param nameMap The map from firing elements to their short names.
383     *  @param delimiter The delimiter between iterands.
384     *  @return A nested parenthesis expression of the schedule.
385     */
386    @Override
387    public String toParenthesisString(Map nameMap, String delimiter) {
388        StringBuffer result = new StringBuffer("(");
389        int iterations = getIterationCount();
390
391        if (iterations > 1) {
392            result.append(iterations);
393        }
394
395        Iterator schedules = iterator();
396
397        while (schedules.hasNext()) {
398            ScheduleElement schedule = (ScheduleElement) schedules.next();
399            result.append(delimiter
400                    + schedule.toParenthesisString(nameMap, delimiter));
401        }
402
403        result.append(")");
404        return result.toString();
405    }
406
407    /** Output a string representation of this Schedule.
408     *
409     *  @return Return a string representation of this Schedule.
410     */
411    @Override
412    public String toString() {
413        StringBuffer result = new StringBuffer("Execute Schedule{\n");
414        Iterator i = iterator();
415
416        while (i.hasNext()) {
417            ScheduleElement e = (ScheduleElement) i.next();
418            result.append(e + "\n");
419        }
420
421        result.append("}");
422
423        if (getIterationCount() > 1) {
424            result.append(" " + getIterationCount() + " times");
425        }
426
427        return result.toString();
428    }
429
430    ///////////////////////////////////////////////////////////////////
431    ////                         inner classes                     ////
432
433    /** An adapter class for iterating over the firing elements of this
434     *  schedule. An exception is thrown if the schedule structure
435     *  changes while this iterator is active.
436     */
437    private class FiringElementIterator implements Iterator {
438        /** Construct a ScheduleIterator.
439         */
440        public FiringElementIterator() {
441            _advance = true;
442            _firingIterator = firingIterator();
443            _currentVersion = _getVersion();
444            _currentIteration = 0;
445        }
446
447        /** Return true if the iteration has more elements.
448         *
449         *  @exception ConcurrentModificationException If the schedule
450         *  data structure has changed since this iterator was created.
451         *  @return True if the iterator has more elements.
452         */
453        @Override
454        public boolean hasNext() {
455            if (_currentVersion != _getVersion()) {
456                throw new ConcurrentModificationException(
457                        "Schedule structure changed while iterator is active.");
458            } else if (_advance == true) {
459                boolean returnValue;
460
461                if (_currentFiring == null) {
462                    returnValue = _firingIterator.hasNext();
463
464                    if (returnValue == true) {
465                        _currentFiring = (Firing) _firingIterator.next();
466                        _currentFiringElement = _currentFiring
467                                .getFiringElement();
468                        _currentIteration++;
469                        _advance = false;
470                        _lastHasNext = true;
471                        return _lastHasNext;
472                    } else {
473                        _advance = false;
474                        _lastHasNext = false;
475                        return _lastHasNext;
476                    }
477                } else {
478                    if (_currentIteration < _currentFiring
479                            .getIterationCount()) {
480                        _currentIteration++;
481                        _advance = false;
482                        _lastHasNext = true;
483                        return _lastHasNext;
484                    } else {
485                        _currentIteration = 0;
486                        returnValue = _firingIterator.hasNext();
487
488                        if (returnValue == true) {
489                            _currentFiring = (Firing) _firingIterator.next();
490                            _currentFiringElement = _currentFiring
491                                    .getFiringElement();
492                            _currentIteration++;
493                            _advance = false;
494                            _lastHasNext = true;
495                            return _lastHasNext;
496                        } else {
497                            _advance = false;
498                            _lastHasNext = false;
499                            return _lastHasNext;
500                        }
501                    }
502                }
503            } else {
504                return _lastHasNext;
505            }
506        }
507
508        /** Return the next object in the iteration.
509         *
510         *  @exception InvalidStateException If the schedule data structure
511         *  has changed since this iterator was created.
512         *  @return The next object in the iteration.
513         */
514        @Override
515        public Object next() throws NoSuchElementException {
516            if (!hasNext()) {
517                throw new NoSuchElementException("No element to return.");
518            } else if (_currentVersion != _getVersion()) {
519                throw new ConcurrentModificationException(
520                        "Schedule structure changed while iterator is active.");
521            } else {
522                _advance = true;
523                return _currentFiringElement;
524            }
525        }
526
527        /** Throw an exception, since removal is not allowed. It really
528         *  doesn't make sense to remove an actor from an actor invocation
529         *  sequence anyway.
530         */
531        @Override
532        public void remove() {
533            throw new UnsupportedOperationException();
534        }
535
536        private Iterator _firingIterator;
537
538        private Firing _currentFiring;
539
540        private Object _currentFiringElement;
541
542        private long _currentVersion;
543
544        private int _currentIteration;
545
546        private boolean _advance;
547
548        private boolean _lastHasNext;
549    }
550
551    /** An adapter class for iterating over the firings of this
552     *  schedule. An exception is thrown if the schedule structure
553     *  changes while this iterator is active. The iterator walks
554     *  the schedule tree as the hasNext() and next() methods are
555     *  invoked, using a small number of state variables.
556     */
557    private class FiringIterator implements Iterator {
558        /** Construct a ScheduleIterator.
559         */
560        public FiringIterator(Schedule schedule) {
561            // If _advance is true, then hasNext() can move
562            // to the next node when invoked.
563            _advance = true;
564            _startingVersion = _getVersion();
565
566            // state. This is the current position as we walk
567            // the schedule tree.
568            _currentNode = schedule;
569
570            // The depth of _currentNode in the schedule tree.
571            // Depth 0 corresponds to the level of the root node
572            // of the tree.
573            _currentDepth = 0;
574
575            // These state arrays are dynamically increased
576            // in size as we deeper into the tree. Their
577            // length is equal to the number of nesting levels
578            // in the schedule.
579            _iterationCounts = new int[_treeDepth];
580            _horizontalNodePosition = new int[_treeDepth];
581        }
582
583        /** Return true if the iteration has more elements.
584         *
585         *  @exception ConcurrentModificationException If the schedule
586         *  data structure has changed since this iterator was created.
587         *  @exception InternalErrorException If the schedule contains
588         *  any leaf nodes that are not an instance of Firing.
589         *  @return True if the iterator has more elements.
590         */
591        @Override
592        public boolean hasNext() {
593            // This code may look messy, but it simply walks the
594            // schedule tree.
595            if (_startingVersion != _getVersion()) {
596                throw new ConcurrentModificationException(
597                        "Schedule structure changed while iterator is active.");
598            } else if (_advance == true) {
599                // Try to go to the next firing node in the tree. Return
600                // false if we fail.
601                if (_currentNode instanceof Firing) {
602                    Schedule scheduleNode = _backTrack(_currentNode);
603                    _currentNode = _findLeafNode(scheduleNode);
604
605                    if (_currentNode == null) {
606                        // There are no more Firings in the tree.
607                        _advance = false;
608                        _lastHasNext = false;
609                        return _lastHasNext;
610                    }
611                } else if (_currentNode instanceof Schedule) {
612                    // This condition should only happen for the first element
613                    // in the iteration.
614                    _currentNode = _findLeafNode((Schedule) _currentNode);
615
616                    if (_currentNode == null) {
617                        // There are no more Firings in the tree at all.
618                        _advance = false;
619                        _lastHasNext = false;
620                        return _lastHasNext;
621                    }
622                } else {
623                    // Throw runtime exception.
624                    throw new RuntimeException(
625                            "Encountered a ScheduleElement that "
626                                    + "is not an instance "
627                                    + "of Schedule or Firing.");
628                }
629
630                _advance = false;
631                _lastHasNext = true;
632                return _lastHasNext;
633            } else {
634                return _lastHasNext;
635            }
636        }
637
638        /** Return the next object in the iteration.
639         *
640         *  @exception InvalidStateException If the schedule
641         *  data structure has changed since this iterator was created.
642         *  @return The next object in the iteration.
643         */
644        @Override
645        public Object next() throws NoSuchElementException {
646            if (!hasNext()) {
647                throw new NoSuchElementException("No element to return.");
648            } else if (_startingVersion != _getVersion()) {
649                throw new ConcurrentModificationException(
650                        "Schedule structure changed while iterator is active.");
651            } else {
652                _advance = true;
653                return _currentNode;
654            }
655        }
656
657        /** Throw an exception, since removal is not allowed. It really
658         *  doesn't make sense to remove a firing from the firing
659         *  sequence anyway, since there is not a 1-1 correspondence
660         *  between the firing in a firing iterator and a firing in the
661         *  schedule.
662         */
663        @Override
664        public void remove() {
665            throw new UnsupportedOperationException();
666        }
667
668        ///////////////////////////////////////////////////////////////////
669        ////                         private methods                 ////
670
671        /** Start at the specified node in the schedule tree and
672         *  move up the tree (towards the root node) until we
673         *  find a node the has children we have not iterated through
674         *  yet or children that we have not iterated through enough
675         *  times (not reached the maximum iteration count).
676         *
677         *  @param firingNode The starting node to backtrack from.
678         */
679        private Schedule _backTrack(ScheduleElement firingNode) {
680            if (_currentDepth == 0) {
681                // Don't backtrack past the root node.
682                return null;
683            }
684
685            _currentDepth--;
686
687            Schedule node = (Schedule) firingNode.getParent();
688
689            if (node == null) {
690                return null;
691            } else if (node
692                    .size() > ++_horizontalNodePosition[_currentDepth + 1]) {
693                return node;
694            } else if (++_iterationCounts[_currentDepth] < node
695                    .getIterationCount()) {
696                _horizontalNodePosition[_currentDepth + 1] = 0;
697                return node;
698            }
699
700            _horizontalNodePosition[_currentDepth + 1] = 0;
701            _iterationCounts[_currentDepth] = 0;
702            return _backTrack(node);
703        }
704
705        /** Start at the specified node and move down the tree
706         *  (away from the root node) until we find the next Firing
707         *  in the iteration order. Through a runtime exception
708         *  if we encounter a leaf node that is not an instance of
709         *  Firing.
710         *
711         *  @param node The schedule node to start from.
712         */
713        private ScheduleElement _findLeafNode(Schedule node) {
714            // Check if we need to resize the arrays.
715            if (_iterationCounts.length - 1 < _currentDepth + 2) {
716                // Need to resize.
717                int[] temp = new int[_currentDepth + 2];
718
719                for (int i = 0; i < _iterationCounts.length; i++) {
720                    temp[i] = _iterationCounts[i];
721                }
722
723                _iterationCounts = temp;
724
725                int[] temp2 = new int[_currentDepth + 2];
726
727                for (int i = 0; i < _horizontalNodePosition.length; i++) {
728                    temp2[i] = _horizontalNodePosition[i];
729                }
730
731                _horizontalNodePosition = temp2;
732
733                // Set new max tree depth. Any new iterators will
734                // create state arrays with this length to avoid
735                // needing to resize in the future (provide the
736                // schedule structure is not modified).
737                _treeDepth = _currentDepth + 2;
738            }
739
740            if (node == null) {
741                return null;
742            } else if (node
743                    .size() > _horizontalNodePosition[_currentDepth + 1]) {
744                _currentDepth++;
745
746                ScheduleElement nodeElement = node
747                        .get(_horizontalNodePosition[_currentDepth]);
748
749                if (nodeElement instanceof Firing) {
750                    return nodeElement;
751                } else {
752                    return _findLeafNode((Schedule) nodeElement);
753                }
754            } else if (node.size() == 0) {
755                Schedule scheduleNode = _backTrack(_currentNode);
756                return _findLeafNode(scheduleNode);
757            } else if (_iterationCounts[_currentDepth] < node
758                    .getIterationCount()) {
759                ScheduleElement nodeElement = node.get(0);
760                _currentDepth++;
761
762                if (nodeElement instanceof Firing) {
763                    return nodeElement;
764                } else {
765                    return _findLeafNode((Schedule) nodeElement);
766                }
767            } else {
768                return null;
769            }
770        }
771
772        ///////////////////////////////////////////////////////////////////
773        ////                         private variables                 ////
774        private boolean _advance;
775
776        private ScheduleElement _currentNode;
777
778        private boolean _lastHasNext;
779
780        // The current depth in the schedule tree.
781        private int _currentDepth;
782
783        private long _startingVersion;
784
785        // This array contains the iteration counts of schedule elements
786        // indexed by tree depth.
787        // This array will grow to the depth of the schedule tree.
788        private int[] _iterationCounts;
789
790        private int[] _horizontalNodePosition;
791    }
792
793    ///////////////////////////////////////////////////////////////////
794    ////                         private methods                   ////
795
796    /*  Get unique firing instances. Repeated firings generally
797     *  occur in the iterator returned by
798     *  {@link ptolemy.graph.sched.ScheduleElement#firingIterator()}.
799     *  This method removes duplications and keeps a list of
800     *  unique firings. Though not sure whether it matters for the
801     *  sequence, firings returned are in the form of <code>List</code>
802     *  rather than un-ordered <code>Collection</code>.
803     *
804     *  @return A list of distinct firings.
805     */
806    private List _getFiringInstancesList() {
807        /* Build _firingInstanceList if it does not exist. This cache
808         * style implementation ensures execution efficiency.
809         */
810        if (_firingInstancesList == null) {
811            _firingInstancesList = new ArrayList();
812
813            Iterator originalFirings = firingIterator();
814
815            while (originalFirings.hasNext()) {
816                Object firing = originalFirings.next();
817
818                if (!_firingInstancesList.contains(firing)) {
819                    _firingInstancesList.add(firing);
820                }
821            }
822        }
823
824        return _firingInstancesList;
825    }
826
827    /*  Get the map of firing elements to firings. Multiple firings
828     *  can be created for any single element and the firings are
829     *  obtained in a <code>List</code>. Therefore, the map returned
830     *  is actually a mapping from elements to <code>List</code>s
831     *  of firings.
832     *
833     *  @return The map from elements to firings.
834     */
835    private Map _getFiringElementFiringsMap() {
836        /* Build _nodeFiringsMap if it does not exist. This cache
837         * style implementation ensures execution efficiency.
838         */
839        if (_firingElementFiringsMap == null) {
840            _firingElementFiringsMap = new HashMap();
841
842            Iterator firingInstances = _getFiringInstancesList().iterator();
843
844            while (firingInstances.hasNext()) {
845                Firing firing = (Firing) firingInstances.next();
846                Object node = firing.getFiringElement();
847                List firingList = null;
848
849                if (!_firingElementFiringsMap.containsKey(node)) {
850                    firingList = new ArrayList();
851                    _firingElementFiringsMap.put(node, firingList);
852                } else {
853                    firingList = (List) _firingElementFiringsMap.get(node);
854                }
855
856                firingList.add(firing);
857            }
858        }
859
860        return Collections.unmodifiableMap(_firingElementFiringsMap);
861    }
862
863    ///////////////////////////////////////////////////////////////////
864    ////                         protected variables               ////
865
866    /** The list of schedule elements contained by this schedule. */
867    protected List _schedule;
868
869    ///////////////////////////////////////////////////////////////////
870    ////                         private variables                 ////
871
872    // The list of Firings for this schedule.
873    //private List _firingList;
874    // The current version of the firing iterator list.
875    //private long _firingIteratorVersion;
876    // The depth of this schedule tree. This may grow.
877    private int _treeDepth;
878
879    /* A list of all distinct firings. */
880    private List _firingInstancesList;
881
882    /* A map from firing elements to firings. */
883    private Map _firingElementFiringsMap;
884}