/*
* Mojito Distributed Hash Table (Mojito DHT)
* Copyright (C) 2006-2007 LimeWire LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.limewire.mojito.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.limewire.collection.CollectionUtils;
/**
* TimeAwareIterable has a maximum time for which it's valid
* and it takes the average time between next() and hasNext()
* calls respectively into consideration to select and return
* elements. The hasNext() method returns false if all elements
* have been returned or if there's not enough time remaining
* (i.e. if the average time between next()/hasNext() calls is
* higher than the remaining time).
* <p>
* The selection of elements looks about this. The gap between
* selected elements gets bigger and bigger as we're approaching
* the maximum time for which this Iterable is valid.
* <pre>
* a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z
* ^ ^ ^ ^ ^ ^ ^
* 0 ------------------- time --------------------> max
* </pre>
* Implementation detail: Unlike with regular Iterators where you
* may call next() multiple times in a row without checking whether
* or not it has more elements you must check on every iteration
* if there're more elements left.
*/
public class TimeAwareIterable<E> implements Iterable<E> {
private final int sampleSize;
private final long maxTime;
private final List<? extends E> elements;
/**
* Creates a TimeAwareIterable with a default sample size of 10.
*
* @param maxTime the maximum time this Iterable is valid
* @param elements the elements to process
*/
public TimeAwareIterable(long maxTime, Collection<? extends E> elements) {
this(10, maxTime, elements);
}
/**
* Creates a TimeAwareIterable.
*
* @param sampleSize the sample size to compute the average time between calls
* @param maxTime the maximum time this Iterable is valid
* @param elements the elements to process
*/
public TimeAwareIterable(int sampleSize, long maxTime,
Collection<? extends E> elements) {
this.sampleSize = sampleSize;
this.maxTime = maxTime;
this.elements = CollectionUtils.toList(elements);
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
public Iterator<E> iterator() {
return new TimeAwareIterator();
}
/**
* The actual Iterator.
*/
private class TimeAwareIterator implements Iterator<E> {
private final long[] samples = new long[sampleSize];
private int sampleCount = 0;
private int sampleIndex = 0;
private long startTime = -1L;
private long lastTime = -1L;
private int currentIndex = -1;
private int nextIndex = 0;
public boolean hasNext() {
if (currentIndex == -1 && nextIndex < elements.size()) {
long currentTime = System.currentTimeMillis();
long average = 0L;
if (startTime == -1L) {
startTime = currentTime;
}
if (lastTime != -1L) {
average = addSample(currentTime - lastTime);
}
lastTime = currentTime;
// If there's not enough time left then exit
long timeRemaining = maxTime - (currentTime - startTime);
if (timeRemaining >= average) {
currentIndex = nextIndex;
nextIndex++;
int elementsRemaining = elements.size() - nextIndex;
assert (elementsRemaining >= 0);
nextIndex += (int)(average*elementsRemaining/timeRemaining);
}
}
return currentIndex != -1;
}
public E next() {
if (startTime == -1L || currentIndex == -1) {
throw new NoSuchElementException();
}
int index = currentIndex;
currentIndex = -1;
return elements.get(index);
}
public void remove() {
throw new UnsupportedOperationException();
}
private long addSample(long time) {
if (time >= 0L) {
samples[sampleIndex] = time;
sampleIndex = (sampleIndex + 1) % samples.length;
if (sampleCount < samples.length) {
sampleCount++;
}
}
return getAverage();
}
private long getAverage() {
if (sampleCount == 0) {
return 0L;
}
long sum = 0L;
for (long time : samples) {
sum += time;
}
return sum/sampleCount;
}
}
}