001/* A queue with constant capacity and optional history.
002
003 Copyright (c) 1997-2018 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
028 */
029package ptolemy.domains.sdf.kernel;
030
031import java.util.Collections;
032import java.util.Enumeration;
033import java.util.LinkedList;
034import java.util.List;
035import java.util.NoSuchElementException;
036
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.kernel.util.InternalErrorException;
039import ptolemy.kernel.util.Nameable;
040
041///////////////////////////////////////////////////////////////////
042//// ArrayFIFOQueue
043
044/**
045 A first-in, first-out (FIFO) queue with variable capacity and optional
046 history. Objects are appended to the queue with the put() method,
047 and removed from the queue with the take() method. The object
048 removed is the oldest one in the queue. By default, the capacity is
049 infinite, but it can be set to any nonnegative size. If the history
050 capacity is greater than zero (or infinite, by setting the capacity to
051 INFINITE_CAPACITY), then objects removed from the queue are transferred
052 to a history queue rather than simply removed. By default, the history
053 capacity is zero.
054 <p>
055 This queue is implemented as a circular array.  When the array becomes full,
056 it is transparently doubled in size.
057
058 @author Steve Neuendorffer, contributor: Brian Hudson
059 @version $Id$
060 @since Ptolemy II 0.2
061 @Pt.ProposedRating Yellow (neuendor)
062 @Pt.AcceptedRating Yellow (johnr)
063 */
064public final class ArrayFIFOQueue implements Cloneable {
065    /** Construct an empty queue with no container, and an infinite capacity.
066     */
067    public ArrayFIFOQueue() {
068        _queueArray = new Object[STARTING_ARRAYSIZE];
069        _queueMaxCapacity = INFINITE_CAPACITY;
070    }
071
072    /** Construct an empty queue with no container and the given capacity.
073     *  @param size The size of the queue.
074     */
075    public ArrayFIFOQueue(int size) {
076        _queueArray = new Object[size];
077        _queueMaxCapacity = size;
078    }
079
080    /** Construct an empty queue with the specified container. The
081     *  container is only used for error reporting.
082     *  @param container The container of the queue.
083     */
084    public ArrayFIFOQueue(Nameable container) {
085        this();
086        _container = container;
087    }
088
089    /** Construct an empty queue with the specified container and the
090     *  given size. The container is only used for error reporting.
091     *  @param container The container of the queue.
092     *  @param size The size of the queue.
093     */
094    public ArrayFIFOQueue(Nameable container, int size) {
095        this(size);
096        _container = container;
097    }
098
099    /** Copy constructor. Create a copy of the specified queue, but
100     *  with no container. This is useful to permit enumerations over
101     *  a queue while the queue continues to be modified. The objects
102     *  in the queue themselves are not cloned.
103     *  @param model The queue to be copied.
104     */
105    public ArrayFIFOQueue(ArrayFIFOQueue model) {
106        this();
107
108        synchronized (model) {
109            _queueSize = model._queueSize;
110            _queueArray = new Object[model._queueArray.length];
111            _queueFront = model._queueFront;
112            _queueBack = model._queueBack;
113            System.arraycopy(model._queueArray, 0, _queueArray, 0,
114                    _queueArray.length);
115            if (model._historyList != null) {
116                _historyList = new LinkedList(model._historyList);
117            }
118        }
119    }
120
121    ///////////////////////////////////////////////////////////////////
122    ////                         public methods                    ////
123
124    /** Clear this queue of any contained objects.
125     */
126    public void clear() {
127        _queueFront = 0;
128        _queueBack = 0;
129        _queueSize = 0;
130    }
131
132    /** Clone this queue. The cloned queue has no container. The
133     *  objects in the queue themselves are not cloned.
134     *  @return A clone of this queue
135     */
136    @Override
137    public Object clone() {
138        return new ArrayFIFOQueue(this);
139    }
140
141    /** Enumerate the objects in the queue, beginning with the oldest.
142     *  @return An enumeration of objects.
143     */
144    public Enumeration elements() {
145        return Collections.enumeration(elementList());
146    }
147
148    /** Return a list containing all the elements in the queue, beginning
149     *  with the oldest.
150     *  @return A list of objects
151     */
152    public List elementList() {
153        LinkedList l = new LinkedList();
154        int i;
155
156        if (_queueFront < _queueBack || isFull()) {
157            for (i = _queueBack; i < _queueArray.length; i++) {
158                l.addLast(_queueArray[i]);
159            }
160
161            for (i = 0; i < _queueFront; i++) {
162                l.addLast(_queueArray[i]);
163            }
164        } else {
165            for (i = _queueBack; i < _queueFront; i++) {
166                l.addLast(_queueArray[i]);
167            }
168        }
169
170        return l;
171    }
172
173    /** Return an object in the queue or history. The object is not
174     *  removed from the queue or history. If the offset argument is
175     *  zero, return the oldest object in the queue. If the offset is
176     *  1, return the second oldest object, etc. If there is no such
177
178     *  object in the queue (the offset is greater than or equal to
179     *  the current queue size), throw an exception. If the argument
180     *  is -1, return the most recent object that was put in the
181     *  history. If the argument is -2, return the second most recent
182     *  object in the history, etc. If there is no such object in the
183     *  history (the history capacity is zero or the absolute value
184     *  of the offset is greater than the current size of the history
185     *  queue), throw an exception.
186     *  @param offset The position of the desired object.
187     *  @return The desired object in the queue or history.
188     *  @exception NoSuchElementException If the offset is out of range.
189     */
190    public Object get(int offset) throws NoSuchElementException {
191        Object object = null;
192
193        if (offset >= 0) {
194            if (offset >= size()) {
195                String message = ".";
196
197                if (_container != null) {
198                    message = " contained by " + _container.getFullName();
199                }
200
201                throw new NoSuchElementException("No object at offset " + offset
202                        + " in the FIFOQueue" + message);
203            }
204
205            int location = _queueBack + offset;
206
207            if (location >= _queueArray.length) {
208                location = location % _queueArray.length;
209            }
210
211            object = _queueArray[location];
212        } else {
213            if (_historyList != null) {
214                try {
215                    object = _historyList.get(historySize() + offset);
216                } catch (Exception ex) {
217                    String message = ".";
218
219                    if (_container != null) {
220                        message = " contained by " + _container.getFullName();
221                    }
222
223                    throw new NoSuchElementException("No object at offset "
224                            + offset + " in the FIFOQueue" + message);
225                }
226            } else {
227                String message = "";
228
229                if (_container != null) {
230                    message = " contained by " + _container.getFullName();
231                }
232
233                throw new NoSuchElementException("No object at offset " + offset
234                        + " in the FIFOQueue" + message + " has no history.");
235            }
236        }
237
238        return object;
239    }
240
241    /** Return the queue capacity.
242     *  This will be INFINITE_CAPACITY if the capacity is infinite.
243     *  @return The capacity of the queue.
244     *  @see #setCapacity(int)
245     */
246    public int getCapacity() {
247        return _queueMaxCapacity;
248    }
249
250    /** Return the container of the queue, or null if there is none.
251     *  @return The container of the queue.
252     *  @see #setContainer(Nameable)
253     */
254    public Nameable getContainer() {
255        return _container;
256    }
257
258    /** Return the capacity of the history queue.
259     *  This will be zero if the history mechanism is disabled and
260     *  INFINITE_CAPACITY if the history capacity is infinite.
261     *  @return The capacity of the history queue.
262     *  @see #setHistoryCapacity(int)
263     */
264    public int getHistoryCapacity() {
265        return _historyCapacity;
266    }
267
268    /** Enumerate the objects in the history, which are the N most recent
269     *  objects taken from the queue, beginning with the oldest, where
270     *  N is less than or equal to the history capacity. If the history
271     *  capacity is infinite, then the enumeration includes all objects
272     *  previously taken from the queue. If the history capacity is zero,
273     *  then return an empty enumeration.
274     *  @return An enumeration of objects in the history.
275     */
276    public Enumeration historyElements() {
277        return Collections.enumeration(
278                _historyList != null ? _historyList : Collections.EMPTY_LIST);
279    }
280
281    /** Return the number of objects in the history.
282     *  @return The current number of objects in the history.
283     */
284    public int historySize() {
285        return _historyList != null ? _historyList.size() : 0;
286    }
287
288    /** Return true if the number of objects in the queue is zero.
289     *  @return A boolean indicating whether the queue is empty.
290     */
291    public boolean isEmpty() {
292        return _queueSize == 0;
293    }
294
295    /** Return true if the number of objects in the queue equals the
296     *  queue capacity.
297     *  @return A boolean indicating whether the queue is full.
298     */
299    public boolean isFull() {
300        return _queueSize >= _queueArray.length;
301    }
302
303    /** Put an object in the queue and return true if this will not
304     *  cause the capacity to be exceeded. Otherwise, do not put
305     *  the object in the queue and return false.
306     *  @param element An object to be put in the queue.
307     *  @return A boolean indicating success.
308     */
309    public boolean put(Object element) {
310        if (_queueArray.length - _queueSize >= 1) {
311            _queueArray[_queueFront] = element;
312            _queueFront += 1;
313
314            if (_queueFront >= _queueArray.length) {
315                _queueFront = _queueFront % _queueArray.length;
316            }
317
318            _queueSize++;
319            return true;
320        } else {
321            if (_queueMaxCapacity == INFINITE_CAPACITY) {
322                _resizeArray(_queueArray.length * 2);
323                return put(element);
324            } else {
325                return false;
326            }
327        }
328    }
329
330    /** Put an array of objects in the queue and return true if this will not
331     *  cause the capacity to be exceeded. Otherwise, do not put
332     *  any of the object in the queue and return false.
333     *  @param element An array of objects to be put in the queue.
334     *  @return A boolean indicating success.
335     */
336    public boolean putArray(Object[] element) {
337        int count = element.length;
338        return putArray(element, count);
339    }
340
341    /** Put an array of objects in the queue and return true if this will not
342     *  cause the capacity to be exceeded. Otherwise, do not put
343     *  any of the object in the queue and return false. The
344     *  specified number of objects from the array will be put
345     *  in the queue.
346     *
347     *  @param element An array of objects to be put in the queue.
348     *  @param count The number of objects to be put in the queue.
349     *  @return A boolean indicating success.
350     */
351    public boolean putArray(Object[] element, int count) {
352        if (_queueArray.length - _queueSize >= count) {
353            if (count <= _queueArray.length - _queueFront) {
354                System.arraycopy(element, 0, _queueArray, _queueFront, count);
355                _queueFront += count;
356
357                if (_queueFront >= _queueArray.length) {
358                    _queueFront = _queueFront % _queueArray.length;
359                }
360
361                _queueSize += count;
362            } else {
363                System.arraycopy(element, 0, _queueArray, _queueFront,
364                        _queueArray.length - _queueFront);
365                System.arraycopy(element, _queueArray.length - _queueFront,
366                        _queueArray, 0,
367                        count - (_queueArray.length - _queueFront));
368                _queueFront += count;
369
370                if (_queueFront >= _queueArray.length) {
371                    _queueFront = _queueFront % _queueArray.length;
372                }
373
374                _queueSize += count;
375            }
376
377            return true;
378        } else {
379            if (_queueMaxCapacity == INFINITE_CAPACITY) {
380                try {
381                    _resizeArray(_queueArray.length * 2);
382                } catch (Exception e) {
383                    e.printStackTrace();
384                }
385
386                return putArray(element, count);
387            } else {
388                return false;
389            }
390        }
391    }
392
393    /** Set queue capacity. Use INFINITE_CAPACITY to indicate unbounded
394     *  capacity (which is the default). If the current size of the
395     *  queue exceeds the desired capacity, throw an exception.
396     *  @param capacity The desired capacity.
397     *  @exception IllegalActionException If the queue contains more
398     *   objects than the proposed capacity or the proposed capacity
399     *   is illegal.
400     *  @see #getCapacity()
401     */
402    public void setCapacity(int capacity) throws IllegalActionException {
403        if (capacity == INFINITE_CAPACITY) {
404            _queueMaxCapacity = INFINITE_CAPACITY;
405            return;
406        }
407
408        if (capacity < -1) {
409            throw new IllegalActionException(_container,
410                    "Queue Capacity cannot be negative");
411        }
412
413        if (size() > capacity) {
414            throw new IllegalActionException(_container, "Queue contains "
415                    + "more elements than the proposed capacity.");
416        }
417
418        _queueMaxCapacity = capacity;
419        _resizeArray(capacity);
420    }
421
422    /** Set the container of the queue. The container is only used
423     *  for error reporting.
424     *  @param container The container of this queue.
425     *  @see #getContainer()
426     */
427    public void setContainer(Nameable container) {
428        _container = container;
429    }
430
431    /** Set the capacity of the history queue. Use 0 to disable the
432     *  history mechanism and INFINITE_CAPACITY to make the history
433     *  capacity unbounded. If the size of the history queue exceeds
434     *  the desired capacity, remove the oldest objects from the
435     *  history queue until its size equals the proposed capacity.
436     *  Note that this can be used to clear the history queue by
437     *  supplying 0 as the argument.
438     *  @param capacity The desired capacity of the history queue.
439     *  @exception IllegalActionException If the desired capacity
440     *   is illegal.
441     *  @see #getHistoryCapacity()
442     */
443    public void setHistoryCapacity(int capacity) throws IllegalActionException {
444        if (capacity > 0) {
445            if (_historyList == null) {
446                _historyList = new LinkedList();
447            }
448            while (_historyList.size() > capacity) {
449                _historyList.removeFirst();
450            }
451        } else if (capacity == 0) {
452            _historyList.clear();
453            _historyList = null;
454        } else if (capacity != INFINITE_CAPACITY) {
455            throw new IllegalActionException(_container,
456                    "Cannot set history capacity to " + capacity);
457        }
458
459        _historyCapacity = capacity;
460    }
461
462    /** Return the number of objects in the queue.
463     *  @return The number of objects in the queue.
464     */
465    public int size() {
466        return _queueSize;
467    }
468
469    /** Remove the oldest object from the queue and return it.
470     *  If there is no such object in the queue (the queue is empty),
471     *  throw an exception. If the history mechanism is enabled,
472     *  then put the taken object in the history queue. If the capacity
473     *  of the history queue would be exceeded by this, then first remove
474     *  the oldest object in the history queue.
475     *  @return An object from the queue.
476     *  @exception NoSuchElementException If the queue is empty.
477     */
478    public Object take() {
479        Object object = null;
480
481        if (isEmpty()) {
482            String message = "";
483
484            if (_container != null) {
485                message = " contained by " + _container.getFullName();
486            }
487
488            throw new NoSuchElementException(
489                    "The FIFOQueue" + message + " is empty!");
490        }
491
492        // Remove it from the buffer.
493        object = _queueArray[_queueBack];
494        _queueArray[_queueBack] = null;
495        _queueBack++;
496
497        if (_queueBack >= _queueArray.length) {
498            _queueBack = _queueBack % _queueArray.length;
499        }
500
501        _queueSize--;
502
503        // Add it to the history buffer.
504        if (_historyCapacity != 0) {
505            if (_historyList == null) {
506                _historyList = new LinkedList();
507            }
508            if (_historyCapacity == _historyList.size()) {
509                _historyList.removeFirst();
510            }
511
512            _historyList.addLast(object);
513        }
514
515        return object;
516    }
517
518    /** Remove the count oldest objects from the queue and return them.
519     *  If there is no such object in the queue (the queue is empty),
520     *  throw an exception. If the history mechanism is enabled,
521     *  then put the taken object in the history queue. If the capacity
522     *  of the history queue would be exceeded by this, then first remove
523     *  the oldest object in the history queue.
524     *
525     *  @param objects An array of objects from the queue.
526     *  @exception NoSuchElementException If the queue is empty.
527     */
528    public void takeArray(Object[] objects) throws NoSuchElementException {
529        int count = objects.length;
530        takeArray(objects, count);
531    }
532
533    /** Remove the count oldest objects from the queue and return them.
534     *  If there is no such object in the queue (the queue is empty),
535     *  throw an exception. If the history mechanism is enabled,
536     *  then put the taken object in the history queue. If the capacity
537     *  of the history queue would be exceeded by this, then first remove
538     *  the oldest object in the history queue.
539     *
540     *  @param objects An array of objects from the queue.
541     *  @param count The number of objects to return.
542     *  @exception NoSuchElementException If the queue is empty.
543     */
544    public void takeArray(Object[] objects, int count)
545            throws NoSuchElementException {
546        if (size() < count) {
547            String message = "";
548
549            if (_container != null) {
550                message = " contained by " + _container.getFullName();
551            }
552
553            throw new NoSuchElementException("The FIFOQueue" + message
554                    + " does not contain enough elements!");
555        }
556
557        if (count <= _queueArray.length - _queueBack) {
558            System.arraycopy(_queueArray, _queueBack, objects, 0, count);
559        } else {
560            System.arraycopy(_queueArray, _queueBack, objects, 0,
561                    _queueArray.length - _queueBack);
562            System.arraycopy(_queueArray, 0, objects,
563                    _queueArray.length - _queueBack,
564                    count - (_queueArray.length - _queueBack));
565        }
566
567        _queueBack += count;
568
569        if (_queueBack >= _queueArray.length) {
570            _queueBack = _queueBack % _queueArray.length;
571        }
572
573        _queueSize -= count;
574
575        if (_historyCapacity != 0) {
576            if (_historyCapacity == _historyList.size()) {
577                _historyList.removeFirst();
578            }
579
580            _historyList.addLast(objects);
581        }
582    }
583
584    ///////////////////////////////////////////////////////////////////
585    ////                         public variables                  ////
586
587    /** Used to indicate that the size of the queue or the history
588     *  queue is infinite.
589     */
590    public static final int INFINITE_CAPACITY = -1;
591
592    /**
593     * The default capacity of the queue.
594     */
595    public static final int DEFAULT_CAPACITY = INFINITE_CAPACITY;
596
597    /**
598     * The starting size of the circular buffer, if the capacity is
599     * infinite.
600     */
601    public static final int STARTING_ARRAYSIZE = 4;
602
603    /**
604     * The default capacity of the history queue.
605     */
606    public static final int DEFAULT_HISTORY_CAPACITY = 0;
607
608    ///////////////////////////////////////////////////////////////////
609    ////                         private methods                   ////
610
611    /** Resize the internal circular array to have the given size.
612     *  @exception InternalErrorException If the proposed size is greater than
613     *   the declared maximum size, or if the queue contains more
614     *   objects than the proposed size or the proposed size
615     *   is illegal.
616     */
617    private void _resizeArray(int newSize) {
618        if (newSize < 0) {
619            throw new InternalErrorException(
620                    "Buffer size of " + newSize + " is less than zero.");
621        }
622
623        if (size() > newSize) {
624            throw new InternalErrorException("Queue contains "
625                    + "more elements than the proposed array size.");
626        }
627
628        if (_queueMaxCapacity != INFINITE_CAPACITY
629                && newSize > _queueMaxCapacity) {
630            throw new InternalErrorException("The proposed"
631                    + " array size exceeds the maximum declared queue size.");
632        }
633
634        Object[] newArray = new Object[newSize];
635
636        if (newSize == 0) {
637            _queueFront = 0;
638        } else if (_queueFront < _queueBack || isFull()) {
639            System.arraycopy(_queueArray, _queueBack, newArray, 0,
640                    _queueArray.length - _queueBack);
641            System.arraycopy(_queueArray, 0, newArray,
642                    _queueArray.length - _queueBack, _queueFront);
643            _queueFront = _queueArray.length - _queueBack + _queueFront;
644
645            // NOTE: The following is probably not needed, but paranoid programming.
646            if (_queueFront >= newArray.length) {
647                _queueFront = _queueFront % newArray.length;
648            }
649        } else {
650            System.arraycopy(_queueArray, _queueBack, newArray, 0,
651                    _queueFront - _queueBack);
652            _queueFront = _queueFront - _queueBack;
653
654            if (_queueFront >= newArray.length) {
655                _queueFront = _queueFront % newArray.length;
656            }
657        }
658
659        _queueArray = newArray;
660        _queueBack = 0;
661    }
662
663    ///////////////////////////////////////////////////////////////////
664    ////                         private variables                 ////
665
666    /** The container, if there is one. */
667    private Nameable _container = null;
668
669    /** The maximum capacity of the queue. */
670    private int _queueMaxCapacity = INFINITE_CAPACITY;
671
672    /** The list of objects currently in the queue. */
673    private Object[] _queueArray;
674
675    /** The location of the next place to insert in _queueArray. */
676    private int _queueFront = 0;
677
678    /** The location of the next place to remove from _queueArray. */
679    private int _queueBack = 0;
680
681    /** The number of elements in the queue. */
682    private int _queueSize = 0;
683
684    /** The capacity of the history queue, defaulting to zero. */
685    private int _historyCapacity = DEFAULT_HISTORY_CAPACITY;
686
687    /** The list of objects recently removed from the queue. */
688    private LinkedList _historyList = null;
689}