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 < 0 || index > 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}