package de.invesdwin.util.collections.iterable.buffer; import java.io.Closeable; import java.io.IOException; import java.util.Iterator; import java.util.NoSuchElementException; import javax.annotation.concurrent.NotThreadSafe; import de.invesdwin.util.collections.Lists; import de.invesdwin.util.collections.iterable.ICloseableIterator; import de.invesdwin.util.error.FastNoSuchElementException; import de.invesdwin.util.lang.Objects; /** * This iterator can be used to buffer another iterator. Useful to load from a file immediately to keep the file open as * shorty as possible, then serve the items from memory and removing them on the go to keep memory consumption low. * * Helpful to fix too many open files during iteration of lots of files in parallel without too much of a performance * overhead. This is not a replacement for classic lists, since those can be iterated faster if no item removal is * required. Though BufferingIterator beats HashSets in raw iteration speed. * * Also a faster alternative to any list when only iteration is needed. */ @NotThreadSafe public class BufferingIterator<E> implements IBufferingIterator<E> { private Node head; private Node tail; private int size = 0; public BufferingIterator() {} public BufferingIterator(final BufferingIterator<E> iterable) { addAll(iterable); } public BufferingIterator(final Iterator<? extends E> iterator) { addAll(iterator); } public BufferingIterator(final Iterable<? extends E> iterable) { addAll(iterable); } @Override public boolean hasNext() { return head != null; } @Override public boolean isEmpty() { return head == null; } @Override public E next() { final E value = getHead(); head = head.getNext(); size--; return value; } @Override public E getHead() { if (head == null) { return null; } else { return head.getValue(); } } @Override public E getTail() { if (tail == null) { return null; } else { return tail.getValue(); } } @Override public void prepend(final E element) { if (element == null) { throw new NullPointerException(); } final Node newHead = new Node(element); newHead.setNext(head); head = newHead; size++; } @Override public void add(final E element) { if (element == null) { throw new NullPointerException(); } final Node newTail = new Node(element); if (head == null) { head = newTail; } else { tail.setNext(newTail); } size++; tail = newTail; } @Override public boolean addAll(final Iterable<? extends E> iterable) { if (iterable == null) { return false; } else { return addAll(iterable.iterator()); } } @Override public boolean addAll(final BufferingIterator<E> iterable) { if (iterable == null) { return false; } else { return addAll(iterable.iterator()); } } @Override public boolean addAll(final Iterator<? extends E> iterator) { if (iterator == null) { return false; } else { Node prev = tail; final int sizeBefore = size; try { if (tail == null) { prev = new Node(iterator.next()); size++; } if (head == null) { head = prev; } while (true) { final Node next = new Node(iterator.next()); prev.setNext(next); prev = next; size++; } } catch (final NoSuchElementException e) { if (iterator instanceof Closeable) { final Closeable cIterator = (Closeable) iterator; try { cIterator.close(); } catch (final IOException e1) { throw new RuntimeException(e1); } } } tail = prev; return sizeBefore < size; } } @Override public boolean consume(final Iterable<? extends E> iterable) { if (iterable == null) { return false; } else if (iterable instanceof BufferingIterator) { @SuppressWarnings("unchecked") final BufferingIterator<E> cIterable = (BufferingIterator<E>) iterable; return consume(cIterable); } else { return addAll(iterable.iterator()); } } @Override public boolean consume(final Iterator<? extends E> iterator) { if (iterator == null) { return false; } else if (iterator instanceof BufferingIterator) { @SuppressWarnings("unchecked") final BufferingIterator<E> cIterable = (BufferingIterator<E>) iterator; return consume(cIterable); } else { return addAll(iterator); } } @Override public boolean consume(final BufferingIterator<E> iterator) { final int sizeBefore = size; size += iterator.size; if (head == null) { head = iterator.head; } else { tail.setNext(iterator.head); } tail = iterator.tail; iterator.clear(); return sizeBefore < size; } @Override public void close() { clear(); } @Override public void clear() { head = null; tail = null; size = 0; } @Override public int size() { return size; } private class Node { private final E value; private Node next; Node(final E value) { this.value = value; } Node(final Node copy) { this.value = copy.value; this.next = copy.next; } public E getValue() { return value; } public Node getNext() { return next; } public void setNext(final Node next) { this.next = next; } @Override public String toString() { return Objects.toString(value); } } @Override public String toString() { return Lists.toListWithoutHasNext(iterator()).toString(); } @Override public ICloseableIterator<E> iterator() { return new ICloseableIterator<E>() { private Node innerHead = head; @Override public boolean hasNext() { return innerHead != null; } @Override public E next() { if (!hasNext()) { throw new FastNoSuchElementException("BufferingIterator: hasNext is false"); } final E value = innerHead.getValue(); innerHead = innerHead.getNext(); return value; } @Override public void close() { innerHead = null; } }; } }