package com.limegroup.gnutella.dime;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.nio.statemachine.ReadState;
import org.limewire.util.BufferUtils;
import org.limewire.util.ByteUtils;
public class AsyncDimeRecordReader extends ReadState {
private static final Log LOG = LogFactory.getLog(AsyncDimeRecordReader.class);
private ByteBuffer header;
private static final int OPTIONS = 0;
private static final int OPTIONS_P = 1;
private static final int ID = 2;
private static final int ID_P = 3;
private static final int TYPE = 4;
private static final int TYPE_P = 5;
private static final int DATA = 6;
private static final int DATA_P = 7;
private static final int TOTAL = 8;
private ByteBuffer[] parts;
public AsyncDimeRecordReader() {
header = ByteBuffer.allocate(12);
}
/**
* Returns the next record if it can be constructed or null if it isn't
* processed yet.
*
* @throws IOException
*/
public DIMERecord getRecord() throws DIMEException {
if(parts == null || parts[DATA].hasRemaining() || parts[DATA_P].hasRemaining()) {
return null;
} else {
try {
return new DIMERecord(header.get(0), header.get(1),
parts[OPTIONS].array(), parts[ID].array(),
parts[TYPE].array(), parts[DATA].array());
} catch(IllegalArgumentException iae) {
throw new DIMEException(iae);
}
}
}
@Override
protected boolean processRead(ReadableByteChannel rc, ByteBuffer buffer) throws IOException {
// Header must be completely read before continuing...
if(fill(header, rc, buffer)) {
LOG.debug("Header not full, leaving.");
return true;
}
// If we haven't created things yet, create them.
if(parts == null)
createOtherStructures();
for(int i = 0; i < TOTAL; i++) {
if(i == 0 || !parts[i-1].hasRemaining()) {
if(fill(parts[i], rc, buffer))
return true;
}
}
return false;
}
/**
* Attempts to read as much data as possible into 'current'.
* Data will be transferred from 'buffer' into 'current' and then
* read from 'channel' into 'current'.
*
* @throws IOException if more data needs to be read but the last read returned -1
* @return true if current still has space to read
*/
private boolean fill(ByteBuffer current, ReadableByteChannel rc, ByteBuffer buffer) throws IOException {
int read = BufferUtils.readAll(buffer, rc, current);
LOG.debug("Filling current. Left: " + current.remaining());
if(current.hasRemaining()) {
if(read == -1)
throw new IOException("EOF");
else
return true;
} else {
return false;
}
}
/**
* Validates the header bytes & constructs options, id, type and data.
* @throws IOException
*/
private void createOtherStructures() throws DIMEException {
try {
DIMERecord.validateFirstBytes(header.get(0), header.get(1));
} catch (IllegalArgumentException iae) {
throw new DIMEException(iae);
}
byte[] headerArr = header.array();
int optionsLength = ByteUtils.beb2int(headerArr, 2, 2);
int idLength = ByteUtils.beb2int(headerArr, 4, 2);
int typeLength = ByteUtils.beb2int(headerArr, 6, 2);
int dataLength = ByteUtils.beb2int(headerArr, 8, 4);
if(LOG.isDebugEnabled()) {
LOG.debug("creating dime record." +
" optionsLength: " + optionsLength +
", idLength: " + idLength +
", typeLength: " + typeLength +
", dataLength: " + dataLength);
}
// The DIME specification allows this to be a 32-bit unsigned field,
// which in Java would be a long -- but in order to hold the array
// of the data, we can only read up to 16 unsigned bits (an int), in order
// to size the array correctly.
if (dataLength < 0)
throw new DIMEException("data too big.");
parts = new ByteBuffer[TOTAL];
parts[OPTIONS] = createBuffer(optionsLength);
parts[OPTIONS_P] = createBuffer(DIMERecord.calculatePaddingLength(optionsLength));
parts[ID] = createBuffer(idLength);
parts[ID_P] = createBuffer(DIMERecord.calculatePaddingLength(idLength));
parts[TYPE] = createBuffer(typeLength);
parts[TYPE_P] = createBuffer(DIMERecord.calculatePaddingLength(typeLength));
parts[DATA] = createBuffer(dataLength);
parts[DATA_P] = createBuffer(DIMERecord.calculatePaddingLength(dataLength));
}
private ByteBuffer createBuffer(int length) {
if(length == 0)
return BufferUtils.getEmptyBuffer();
else
return ByteBuffer.allocate(length);
}
public long getAmountProcessed() {
long read = header.position();
if(parts != null) {
for(int i = 0; i < TOTAL; i++)
read += parts[i].position();
}
return read;
}
}