package edu.ualberta.med.biobank.common.util;
import gov.nih.nci.system.applicationservice.ApplicationException;
import gov.nih.nci.system.applicationservice.ApplicationService;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.Semaphore;
import org.springframework.util.Assert;
/**
* ListProxy with transparent paging: <br>
* <ul>
* <li>Read only</li><br>
* <li>Non-searchable</li><br>
* </ul>
*/
public abstract class AbstractBiobankListProxy<E> implements List<E>,
Serializable {
private static enum Shown {
BUSY,
DONE
};
private Shown lastShown = Shown.DONE;
private static final long serialVersionUID = 1L;
private static final int REAL_SIZE_UNKNOWN = -1;
protected final int pageSize;
private final Semaphore loadingNextPage = new Semaphore(1);
protected transient ApplicationService appService;
private Page<Object> page;
private Page<Object> nextPage;
private int realSize;
private IBusyListener listener;
public AbstractBiobankListProxy(ApplicationService appService) {
page = new Page<Object>();
nextPage = new Page<Object>();
pageSize = appService.getMaxRecordsCount();
this.appService = appService;
this.realSize = REAL_SIZE_UNKNOWN;
}
protected abstract List<Object> getChunk(Integer firstRow)
throws ApplicationException;
@Override
public boolean add(Object e) {
throw new UnsupportedOperationException();
}
@Override
public void add(int index, Object element) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends E> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public boolean contains(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public E get(int index) {
init();
Assert.isTrue(index >= 0);
updateListChunk(index);
Object element = page.get(index);
if (element != null) {
@SuppressWarnings("unchecked")
E tmp = (E) element;
return getRowObject(tmp);
}
return null;
}
private void swapPages() {
Page<Object> tmp = nextPage;
nextPage = page;
page = tmp;
}
private void updateRealSize(Page<?> page) {
if (page.list.size() < pageSize && realSize == REAL_SIZE_UNKNOWN) {
realSize = page.offset + page.list.size();
}
}
/**
* Loads a chunk into the given page, starting from the given offset.
*
* @param offset
* @param page
*/
private void loadChunk(int offset, Page<Object> page)
throws ApplicationException {
page.list = getChunk(offset);
page.offset = offset;
updateRealSize(page);
}
private void showBusy() {
if (lastShown != Shown.BUSY && listener != null) {
listener.showBusy();
lastShown = Shown.BUSY;
}
}
private void showDone() {
if (lastShown != Shown.DONE && listener != null) {
listener.done();
lastShown = Shown.DONE;
}
}
private void updateListChunk(int index) {
if (!page.hasElement(index)) {
boolean acquired = loadingNextPage.tryAcquire();
if (!acquired || !nextPage.hasElement(index)) {
showBusy();
}
try {
if (!acquired)
loadingNextPage.acquire();
if (!nextPage.hasElement(index)) {
int offset = (index / pageSize) * pageSize;
loadChunk(offset, page);
} else {
swapPages();
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
showDone();
loadingNextPage.release();
}
} else
preLoadList(index);
}
private void preLoadList(final int i) {
if (!loadingNextPage.tryAcquire()) {
return;
}
int nextOffset;
if ((i - page.offset) > (pageSize / 2)) {
nextOffset = page.offset + pageSize;
} else
nextOffset = page.offset - pageSize;
boolean alreadyLoaded = nextPage.offset != null
&& nextPage.offset.equals(nextOffset);
boolean afterStart = nextOffset >= 0;
boolean beforeEnd =
(realSize == REAL_SIZE_UNKNOWN || nextOffset < realSize);
if (!alreadyLoaded && afterStart && beforeEnd) {
final int finalNextOffset = nextOffset;
Thread t = new Thread() {
@Override
public void run() {
try {
loadChunk(finalNextOffset, nextPage);
} catch (ApplicationException e) {
throw new RuntimeException(e);
} finally {
loadingNextPage.release();
}
}
};
t.start();
} else {
loadingNextPage.release();
}
}
@Override
public int indexOf(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean isEmpty() {
init();
return page.list.isEmpty();
}
@Override
public Iterator<E> iterator() {
return new AbstractBiobankListProxyIterator<E>(this);
}
@Override
public int lastIndexOf(Object o) {
throw new UnsupportedOperationException();
}
@Override
public ListIterator<E> listIterator() {
throw new UnsupportedOperationException();
}
@Override
public ListIterator<E> listIterator(int index) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public E remove(int index) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public E set(int index, Object element) {
throw new UnsupportedOperationException();
}
@Override
public int size() {
return realSize;
}
@SuppressWarnings("unchecked")
@Override
public List<E> subList(int fromIndex, int toIndex) {
init();
assert (fromIndex >= 0 && toIndex >= 0);
assert (fromIndex <= toIndex);
updateListChunk(fromIndex);
List<E> subList = new ArrayList<E>(toIndex - fromIndex);
// for (int i = fromIndex; i < toIndex; i++) {
// subList.add(get(i));
// }
if (realSize != REAL_SIZE_UNKNOWN && toIndex > realSize)
toIndex = realSize;
for (Object o : page.list.subList(fromIndex - page.offset,
Math.min(page.list.size(), toIndex - page.offset))) {
subList.add(getRowObject((E) o));
}
if (page.offset + pageSize < toIndex && page.list.size() == pageSize) {
subList.addAll(subList(page.offset + pageSize, toIndex));
}
return subList;
}
protected E getRowObject(E object) {
return object;
}
@Override
public java.lang.Object[] toArray() {
throw new UnsupportedOperationException();
}
@Override
public <T> T[] toArray(T[] a) {
throw new UnsupportedOperationException();
}
public void setAppService(ApplicationService as) {
this.appService = as;
}
// TODO: rename to "setBusyListener"?
public void addBusyListener(IBusyListener l) {
this.listener = l;
}
public AbstractBiobankListProxy<?> init() {
if (!page.isInitialized()) {
updateListChunk(0);
}
return this;
}
private class Page<V> implements Serializable, NotAProxy {
private static final long serialVersionUID = 7230323011529770077L;
public Integer offset;
public List<V> list;
/**
*
* @param index
* @return true if this page could hold the given index, otherwise
* false.
*/
public boolean hasElement(int index) {
return isInitialized() && index - offset < pageSize
&& index >= offset;
}
/**
*
* @param index
* @return the element at the given index, otherwise null.
*/
public V get(int index) {
V result = null;
if (offset != null && list != null) {
if (list.size() > 0 && list.size() > index - offset) {
result = list.get(index - offset);
}
}
return result;
}
public boolean isInitialized() {
return offset != null && list != null;
}
}
}