/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.collections.timed; import com.jcwhatever.nucleus.collections.CircularQueue; import com.jcwhatever.nucleus.collections.wrap.ConversionIteratorWrapper; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.TimeScale; import javax.annotation.Nullable; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; /** * An element distributor that must be polled for the current element. The current element * changes to the next when its specified time span expires. The distributor is circular, * meaning once it reaches the end of the collection it will go back to the beginning. * * <p>The current element is passively changed. The {@link #current} method must be * invoked at interval for the distributor to be effective.</p> * * <p>Not thread safe.</p> */ public class TimedDistributor<E> implements Collection<E> { private final CircularQueue<Element<E>> _queue = new CircularQueue<>(); private final int _defaultTime; private final TimeScale _defaultTimeScale; private long _nextRotation; /** * Constructor. * * <p>Default rotation time is 3 seconds.</p> */ public TimedDistributor() { this(3, TimeScale.SECONDS); } /** * Constructor. * * @param defaultTime The default rotation time. * @param defaultTimeScale The default time scale. */ public TimedDistributor(int defaultTime, TimeScale defaultTimeScale) { PreCon.greaterThanZero(defaultTime); PreCon.notNull(defaultTimeScale); _defaultTime = defaultTime; _defaultTimeScale = defaultTimeScale; } /** * Constructor. * * @param defaultTime The default rotation time. * @param defaultTimeScale The default time scale. * @param collection The initial collection to start with. */ public TimedDistributor(int defaultTime, TimeScale defaultTimeScale, Collection<? extends E> collection) { PreCon.greaterThanZero(defaultTime); PreCon.notNull(defaultTimeScale); PreCon.notNull(collection); _defaultTime = defaultTime; _defaultTimeScale = defaultTimeScale; for (E value : collection) { add(value, _defaultTime, _defaultTimeScale, false); } } /** * Add an element to the head of the distributor. * * @param element The element to add. * @param timeSpan The number of ticks the element will be current for. */ public boolean add(@Nullable E element, int timeSpan, TimeScale timeScale) { PreCon.greaterThanZero(timeSpan); PreCon.notNull(timeScale); return add(element, timeSpan, timeScale, true); } /** * Get the current element. * * @return Null if no actions or the time slice cycles * are not started or have been stopped. */ @Nullable public E current() { if (_queue.isEmpty()) return null; Element<E> element; long currentTime = System.currentTimeMillis(); if (_nextRotation == 0) { element = _queue.element(); assert element != null; _nextRotation = currentTime + element.timeSpan; } else if (_nextRotation <= currentTime) { element = _queue.next(); assert element != null; _nextRotation = currentTime + element.timeSpan; } else { element = _queue.element(); assert element != null; } @SuppressWarnings("unchecked") E value = (E)element.value; return value; } /** * Manually move to the next element in the queue. * * <p>The time to next automatic rotation is reset using the * next elements time values.</p> * * @return The next element. */ @Nullable public E next() { if (_queue.isEmpty()) return null; Element<E> element = _queue.next(); assert element != null; _nextRotation = System.currentTimeMillis() + element.timeSpan; @SuppressWarnings("unchecked") E value = (E)element.value; return value; } @Override public int size() { return _queue.size(); } @Override public boolean isEmpty() { return _queue.isEmpty(); } @Override public boolean contains(Object o) { return _queue.contains(new Element<E>(o)); } @Override public Iterator<E> iterator() { return new Itr(); } @Override public Object[] toArray() { Object[] result = new Object[_queue.size()]; int index = 0; for (Element<E> element : _queue) { result[index] = element.value; index++; } return result; } @Override public <T1> T1[] toArray(T1[] a) { int index = 0; for (Element<E> element : _queue) { @SuppressWarnings("unchecked") T1 value = (T1)element.value; a[index] = value; index++; } return a; } @Override public boolean add(@Nullable E element) { return add(element, _defaultTime, _defaultTimeScale); } @Override public boolean remove(@Nullable Object o) { return _queue.removeFirstOccurrence(new Element<E>(o)); } @Override public boolean containsAll(Collection<?> c) { PreCon.notNull(c); for (Object o : c) { if (!contains(o)) return false; } return true; } /** * Add all items from the collection. The collection is * added to the current head of the distributor and the order * of the collection, if any, is maintained. */ @Override public boolean addAll(Collection<? extends E> c) { LinkedList<E> list = new LinkedList<>(c); while (!list.isEmpty()) { E element = list.removeLast(); add(element); } return true; } @Override public boolean removeAll(Collection<?> c) { PreCon.notNull(c); boolean isChanged = false; for (Object o : c) { isChanged = remove(o) || isChanged; } return isChanged; } @Override public boolean retainAll(Collection<?> c) { PreCon.notNull(c); Iterator<E> iterator = iterator(); while (iterator.hasNext()) { E element = iterator.next(); if (!c.contains(element)) iterator.remove(); } return false; } @Override public void clear() { _queue.clear(); } private boolean add(E element, int timeSpan, TimeScale timeScale, boolean first) { Element<E> timedElement = new Element<>(element, timeSpan, timeScale); if ((first && _queue.offerFirst(timedElement)) || (!first && _queue.offerLast(timedElement))) { _nextRotation = 0; return true; } return false; } private static class Element<T> { Object value; int timeSpan; Element(T value, int timeSpan, TimeScale timeScale) { this.value = value; this.timeSpan = timeSpan * timeScale.getTimeFactor(); } Element(Object value) { this.value = value; } @Override public int hashCode() { return value == null ? super.hashCode() : value.hashCode(); } public boolean equals(Object obj) { if (obj instanceof Element) { Element other = (Element)obj; if (other.value == null && value == null) { return true; } else if (other.value != null) { return other.value.equals(value); } } else { if (value == null && obj == null) { return true; } else if (value != null) { return value.equals(obj); } } return false; } } private class Itr extends ConversionIteratorWrapper<E, Element<E>> { Iterator<Element<E>> iterator = _queue.iterator(); @Override protected E convert(Element<E> internal) { @SuppressWarnings("unchecked") E result = (E)internal.value; return result; } @Override protected Iterator<Element<E>> iterator() { return iterator; } } }