Thursday, May 16, 2013

BufferedIterator


The idea is to process an item or a set of items after another and behind the scenes make only the necessary/minimum calls to load those items without worrying about it. If I want to process 1000 videos, I surely don't want to have to make 1000 web service calls but I also do not want to load 1000 videos at once.



It would look like this:
for (Iterator<MyItem> i = .....; i.hasNext(); ) {
 MyItem oItem = i.next();
 ....process oItem....
}

I can batch them by say 10 or 20. It would look like this:
int oFrom = 1;
int oTo = 10;
int oInc = 10;
FindParameters oParams = new FindParameters(oFrom, oTo).with(...some filters...).sortBy(....some sort...);
Findings<Video> oFindings = mVideoBO.find(oParams);
while (!oFindings.getResults().isEmpty()) {
 for (Video v : oFindings.getResults()) {
  ...process item...
 }
 oFrom += oInc;
 oTo += oInc;
 oParams.from(oFrom).to(oTo);
 oFindings = mVideoBO.find(oParams);
}

That's a lot of code: I need over 10 lines of error-prone code and logic before processing any item. It's difficult to maintain and impossible to reuse (without a copy-paste-tweak).

The simple idea is to refactor all this and end up with something like the following:
FindParameters oParams = new FindParameters().with(...some filters...).sortBy(....some sort...);
mVideoBO.findAndProcess(oParams, new IProcessor<Video>() {
 private void process(final Video pItem) {
  ...process item...
 }
});
There are different ways to accomplish this and I chose to work with the iterator and lazy loading.
I first define my own extension to Iterator<T>:
public interface IIterator<T> extends Iterator<T> {}
I then create my PaginationIterator<T> which basically gets more items at every call to next():
public abstract class PaginationIterator<T> implements IIterator<T> {
 private long mTo;
 private int mIncrement;
 private long mCursor;
 
 public PaginationIterator(long pFrom, long pTo, int pIncrement) {
  mTo = pTo;
  mIncrement = pIncrement;
  mCursor = pFrom;
 }

 public T next() {
  if (!hasNext()) return null;
  long oFrom = mCursor;
  long oTo = Math.min(mTo, mCursor + mIncrement - 1);  
  T oItems = getItems(oFrom, oTo);
  mCursor += mIncrement;
  return oItems;
 }
 
 public boolean hasNext() {
  return (mCursor < mTo);  
 }
 
 public void remove() {
  throw new RuntimeException("Not supported");
 }
 
 protected abstract T getItems(long pFrom, long pTo);
 
 protected void setTo(int pValue) {
  mTo = pValue;
 }
 
 public long getCursor() {
  return mCursor;
 }
 public int getIncrement() {
  return mIncrement;
 }
 public long getTo() {
  return mTo;
 }
}


I can now create a concrete extension of that iterator, specifying how the items are loaded given a pFrom and pTo.
I have now something like the following:

for (Iterator<List<Video>> i = new MyVideoIterator(1, 100, 10); i.hasNext(); ) {
 List<Video> o10Videos = i.next();
 for (Video v : o10Videos) {
  ....process v....
 }
}
I'm here processing a set of items instead of each item individually. The next step is therefore to create another iterator reusing the logic of that PaginationIterator:
public class BufferedIterator<T, U extends Iterable<T>> implements IIterator<T> {
 
 private Iterator<T> mCurrentBufferedIterator;
 private IIterator<U> mIterator;
 
 public BufferedIterator(IIterator<U> pIterator) {
  mIterator = pIterator;
 }
 
 public boolean hasNext() {
  return mIterator.hasNext() || (mCurrentBufferedIterator != null && mCurrentBufferedIterator.hasNext());
 }
 
 public T next() {
  if ((mCurrentBufferedIterator == null || !mCurrentBufferedIterator.hasNext()) && hasNext()) getNextBatch(mIterator.next());
  return mCurrentBufferedIterator == null ? null : mCurrentBufferedIterator.next();
 }
 
 private void getNextBatch(U pNext) {
  if (pNext == null) return;
  mCurrentBufferedIterator = pNext.iterator();
 }
 
 public void remove() {
  throw new RuntimeException("Not supported.");
 }
}
With this we now have:
for (Iterator<Video> i = new BufferedIterator<Video>(new MyPaginationIterator(1, 100, 10)); i.hasNext(); ) {
 Video v = i.next();
 ....process v....
}
I can now embed the whole thing in a method of my BOs:
public void findAndProcess(final FindParameters pParams, final IProcessor<T> pProcessor) throws CoreException {
 MyPaginationIterator<T> oIterator = new MyPaginationIterator<T>(this, pParams, 1, 100, 10);
 BufferedIterator<T, List<T>> oBufferedIterator = new BufferedIterator<T, List<T>>(oIterator);
 for (Iterator<T> i = oBufferedIterator; i.hasNext;) {
  pProcessor.process(i.next());
 }
}
And I finally end up with what I wanted earlier:
FindParameters oParams = new FindParameters().with(...).sortBy(...);
mVideoBO.findAndProcess(oParams, new IProcessor<Video>() {
 private void process(final Video pItem) {
  ...process item...
 }
});

No comments:

Post a Comment