package com.bigdata.striterator; import java.util.Arrays; import java.util.NoSuchElementException; import org.apache.log4j.Logger; import com.bigdata.btree.IIndex; /** * Supports the chunk-at-a-time filter and conversion operations. * Chunk-at-a-time operations are potentially much more efficient as they allow * for ordered reads and ordered writes on {@link IIndex}s. * * @version $Id$ * * @param <E> * The generic type of the source elements. * @param <F> * The generic type of the converted elements. * * @see IChunkConverter * * @todo This class is redundent with the {@link ChunkedFilter} */ public class ChunkedConvertingIterator<E, F> implements IChunkedOrderedIterator<F> { private final static Logger log = Logger .getLogger(ChunkedConvertingIterator.class); private final IChunkedOrderedIterator<E> src; private final IChunkConverter<E, F> converter; /** * The converted {@link IKeyOrder}. */ private final IKeyOrder<F> keyOrder; private F[] converted = null; private int pos = 0; /** * Ctor when the element type is NOT being changed during conversion. This * ctor uses the {@link IKeyOrder} reported by the source * {@link IChunkedOrderedIterator}. This will result in runtime exceptions * if the generic types of the source and converted iterators differ. * * @param src * The source iterator. * @param converter * The chunk-at-a-time converter. */ public ChunkedConvertingIterator(final IChunkedOrderedIterator<E> src, final IChunkConverter<E, F> converter) { this(src, converter, (IKeyOrder<F>) src.getKeyOrder()); } /** * Variant ctor when you are also converting the element type. * * @param src * The source iterator. * @param converter * The chunk-at-a-time converter. * @param keyOrder * The {@link IKeyOrder} for the converted chunks -or- * <code>null</code> iff not known. */ public ChunkedConvertingIterator(final IChunkedOrderedIterator<E> src, final IChunkConverter<E, F> converter, final IKeyOrder<F> keyOrder) { if (src == null) throw new IllegalArgumentException(); if (converter == null) throw new IllegalArgumentException(); this.src = src; this.converter = converter; // MAY be null. this.keyOrder = keyOrder; } /** * Applies the chunk-at-a-time converter. The converted chunk MAY contain a * different number of elements. If it does not contain any elements then * another chunk will be fetched from the source iterator by * {@link #hasNext()}. * * @param src * A chunk of elements from the source iterator. * * @return A converted chunk of elements. */ protected F[] convert(final IChunkedOrderedIterator<E> src) { final F[] tmp = converter.convert(src); if (tmp == null) throw new AssertionError("Converter returns null: " + converter.getClass()); if (log.isInfoEnabled()) { log.info("Converted: chunkSize=" + tmp.length + " : chunk=" + Arrays.toString(tmp)); } return tmp; } @Override public void close() { src.close(); } @Override public F next() { if (!hasNext()) throw new NoSuchElementException(); // if ((converted == null || pos >= converted.length) && src.hasNext()) { // // // convert the next chunk // converted = convert(src.nextChunk()); // // pos = 0; // // } if (log.isInfoEnabled()) log.info("returning converted[" + pos + "]"); return converted[pos++]; } /* * @return true iff there is a non-empty chunk and pos is LT the length of * that chunk. */ @Override public boolean hasNext() { /* * Note: This loops until we either have a non-empty chunk or the source * is exhausted. This allows for the possibility that a converter can * return an empty chunk, e.g., because all elements in the chunk were * filtered out. */ while ((converted == null || pos >= converted.length) && src.hasNext()) { // convert the next chunk converted = convert(src); pos = 0; } final boolean hasNext = converted != null && pos < converted.length; if (log.isInfoEnabled()) log.info(hasNext); // StringWriter sw = new StringWriter(); // new Exception("stack trace").printStackTrace(new PrintWriter(sw)); // log.info(sw.toString()); return hasNext; } @Override public IKeyOrder<F> getKeyOrder() { return keyOrder; } @Override public F[] nextChunk() { if (!hasNext()) throw new NoSuchElementException(); // if (pos >= converted.length && src.hasNext()) { // // // convert the next chunk // converted = convert(src.nextChunk()); // // pos = 0; // // } if (pos > 0) { /* * The chunk is partly consumed so we need to make it dense. */ final int remaining = converted.length - pos; final F[] chunk = (F[]) java.lang.reflect.Array.newInstance( // converted[0].getClass(), converted.getClass().getComponentType(), remaining); System.arraycopy(converted, pos, chunk, 0, remaining); converted = chunk; } final F[] nextChunk = converted; converted = null; pos = 0; return nextChunk; } @Override public F[] nextChunk(final IKeyOrder<F> keyOrder) { if (keyOrder == null) throw new IllegalArgumentException(); final F[] chunk = nextChunk(); if (!keyOrder.equals(this.keyOrder)) { Arrays.sort(chunk, keyOrder.getComparator()); } return chunk; } /** * Not supported. * * @throws UnsupportedOperationException * always. */ @Override public void remove() { throw new UnsupportedOperationException(); } }