package xxl.core.collections.queues.io; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import xxl.core.collections.containers.Container; import xxl.core.collections.containers.io.BlockFileContainer; import xxl.core.collections.containers.io.ConverterContainer; import xxl.core.collections.queues.AbstractQueue; import xxl.core.io.converters.BooleanConverter; import xxl.core.io.converters.Converter; import xxl.core.io.converters.IntegerConverter; import xxl.core.io.converters.LongConverter; /** * This class implements a queue interface. * The aim of this class is to use a queue with a convereterContainer ({@link ConverterContainer}) (the usage is not restricted to this container class, other container hierarchy could be used). * The queue is implemented as a double linked list of buckets (pages, blocks). Buckets are persisted in container that manages block (or byte arrays). * Queue buckets are mapped to byte arrays using internal converter and user provided {@link ConverterContainer}. * For initializing a queue three parameter needed: Container, serialized size of an entry and block size of lowest container: * * The following initialization pattern should be implemented, assume we want to manage a queue of Integer values: * 1. we define a container, for example, {@link BlockFileContainer} * Container blockFile = new {@link BlockFileContainer} (fileName, blockSize); * 2. we need to provide a {@link Converter} for a objects stored in a queue * Converter<Integer> dataConverter = {@link IntegerConverter#DEFAULT_INSTANCE}; * 3. we create a ConverterContainer * Container queueConatainer = new {@link ConverterContainer}(blockFile, @link #getPageConverter(dataConverter)}); * 4. we crete queue * {@link QueueBuffer}( queueConatainer, {@link IntegerConverter#SIZE}, blockSize ); * * @author achakeye * * @param <E> */ public class QueueBuffer<E> extends AbstractQueue<E> { /** * pointer size */ public static final int POINTER_SIZE = 9; /** * number of bytes needed to store the number of elements per page */ public static final int LENGTH_SIZE = 4; /** * * @param entryConverter * @return */ public static <E> Converter<SimplePage<E>> getPageConverter(final Converter<E> entryConverter){ return new PageConverter<>(entryConverter); } /** * */ protected Converter<Long> idConverter; /** * */ protected Container entryContainer; /** * */ protected int entrySerializedSize; /** * */ protected Long headId; /** * */ protected Long tailId; /** * */ protected Long next; /** * */ protected Long lastNotInitPageNumber; /** * */ protected boolean lastPageNotInit; /** * */ protected int index; /** * */ protected int tailOffSet; /** * */ private int maxEntriesPerPage; /** * * Creates a new queue * * @param entryContainer * @param entrySerializedSize * @param blockSize */ public QueueBuffer(Container entryContainer, int entrySerializedSize, int blockSize) { super(); this.entryContainer = entryContainer; this.entrySerializedSize = entrySerializedSize; // init first page this.headId = null; this.tailId = (Long) this.entryContainer.reserve(null); this.index = 0; this.tailOffSet = 0; this.size = 0; this.next = null; lastPageNotInit = true; this.maxEntriesPerPage = (blockSize-2*POINTER_SIZE - LENGTH_SIZE)/entrySerializedSize; } /** * * @return */ private SimplePage<E> createPage(){ return new SimplePage<E>(new ArrayList<E>()); } /* * (non-Javadoc) * @see xxl.core.collections.queues.AbstractQueue#enqueueObject(java.lang.Object) */ @Override public void enqueueObject(E object) throws IllegalStateException { SimplePage<E> page = null; if(tailOffSet == 0){ page = createPage(); page.entries.add(object); // add to page page.next = next; // set link to next entryContainer.update(tailId, page); // update entry lastPageNotInit = false; tailOffSet++; }else{ page = (SimplePage<E>) entryContainer.get(tailId, false); page.entries.add(object); tailOffSet++; if (page.entries.size() >= maxEntriesPerPage){ next = tailId; tailId = (Long) entryContainer.reserve(null); // allocate new page link tailOffSet=0; // reset offset lastNotInitPageNumber = tailId; // store as last not init page number page.prev = tailId; lastPageNotInit = true; entryContainer.update(next, page); }else{ entryContainer.update(tailId, page); } } if(headId == null){ // not init head if(lastPageNotInit){ headId = next; index = page.entries.size()-1; } else{ headId = tailId; index = tailOffSet-1; } } //size++; is managed by super class } /* * (non-Javadoc) * @see xxl.core.collections.queues.AbstractQueue#dequeueObject() */ @SuppressWarnings("unchecked") @Override public E dequeueObject() throws IllegalStateException, NoSuchElementException { if(isEmpty()){ throw new NoSuchElementException("Queue is empty"); } E entry = null; SimplePage<E> head = (SimplePage<E>) entryContainer.get(headId, true); try{ entry = head.entries.get(index); }catch(Exception ex){ // throw new RuntimeException(ex); } index++; if(index >= head.entries.size() ){ // init head index = 0; if(headId.compareTo(tailId) != 0 ){ entryContainer.remove(headId); } headId = head.prev; } // size--; return entry; } /* * (non-Javadoc) * @see xxl.core.collections.queues.Queue#clear() */ @Override public void clear() { // simple strategy // delete all dequeue all objects while(!this.isEmpty()){ this.dequeue(); } } /* * (non-Javadoc) * @see xxl.core.collections.queues.AbstractQueue#peekObject() */ @Override protected E peekObject() { throw new UnsupportedOperationException(); } /** * Internal bucket/page. * * */ public static class SimplePage<E>{ public List<E> entries; public Long prev; public Long next; public SimplePage(List<E> triples) { super(); this.entries = triples; } public List<E> getTriples() { return entries; } public void setTriples(List<E> triples) { this.entries = triples; } @Override public String toString() { return "SimplePage [entries=" + entries + ", prev=" + prev + ", next=" + next + "]"; } } /** * Page converter for a internal page representation; * * see {@link ConverterContainer}. * * * @author achakeye * * @param <E> */ public static class PageConverter<E> extends Converter<SimplePage<E>>{ Converter<E> entryConverter; protected PageConverter(Converter<E> entryConverter){ this.entryConverter = entryConverter; } @Override public SimplePage<E> read(DataInput dataInput, SimplePage<E> object) throws IOException { int listSize = IntegerConverter.DEFAULT_INSTANCE.readInt(dataInput); List<E> entries = new ArrayList<>(listSize); for(int i = 0; i < listSize; i++){ entries.add(entryConverter.read(dataInput)); } SimplePage<E> page = new SimplePage<>(entries); boolean haslink = BooleanConverter.DEFAULT_INSTANCE.readBoolean(dataInput); if(!haslink){ page.next = new Long(LongConverter.DEFAULT_INSTANCE.readLong(dataInput)); } haslink = BooleanConverter.DEFAULT_INSTANCE.readBoolean(dataInput); if(!haslink){ page.prev = new Long(LongConverter.DEFAULT_INSTANCE.readLong(dataInput)); } return page; } @Override public void write(DataOutput dataOutput, SimplePage<E> object) throws IOException { IntegerConverter.DEFAULT_INSTANCE.writeInt(dataOutput, object.entries.size()); for(E entry: object.entries){ entryConverter.write(dataOutput, entry); } BooleanConverter.DEFAULT_INSTANCE.writeBoolean(dataOutput, object.next==null); if( object.next!=null){ LongConverter.DEFAULT_INSTANCE.writeLong(dataOutput, object.next); } BooleanConverter.DEFAULT_INSTANCE.writeBoolean(dataOutput, object.prev==null); if( object.prev!=null){ LongConverter.DEFAULT_INSTANCE.writeLong(dataOutput, object.prev); } } } }