package org.limewire.bittorrent.bencoding;
import java.io.EOFException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import org.limewire.util.BEncoder;
public abstract class NumberToken<T extends Number> extends Token<T> {
private static final byte MINUS;
static {
byte minus = 0;
try {
minus = "-".getBytes(ASCII)[0];
} catch (UnsupportedEncodingException impossible) {
// hook to ErrorService
}
MINUS = minus;
}
/** Storage for the value of the token */
private StringBuilder sb = new StringBuilder();
private byte [] currentByte = new byte[1];
private ByteBuffer buf = ByteBuffer.wrap(currentByte);
/** -1 for negative values, 0 for 0, 1 for positive values */
protected int multiplier = 1;
/** whether this token has been parsed */
private boolean done;
/** The terminating character used to parse this token */
private final byte terminator;
/**
* Creates a new Token ready to parse a Long using the default terminating
* character.
*/
NumberToken(ReadableByteChannel chan) {
this(chan, BEncoder.E, (byte)0);
}
/**
* Make a new BELong object that can get the number from bencoded data like "i23e" or "5:".
*
* @param chan a ReadableByteChannel the new BELong can read from to get more data.
* @param terminator the character we'll look for to mark the end of the numerals, like "e" or ":".
* @param firstByte if you already ready the first digit from the channel, pass it here.
* If not, pass 0 for firstByte.
*/
NumberToken(ReadableByteChannel chan, byte terminator, byte firstByte) {
super(chan); // Save the channel this new object will read data from
this.terminator = terminator; // Save the character we'll watch for to end the number
if (firstByte != 0) { // The caller a
if (firstByte < ZERO || firstByte > NINE)
throw new IllegalArgumentException("invalid first byte");
sb.append(firstByte - ZERO);
}
}
@Override
public void handleRead() throws IOException {
if (done)
throw new IllegalStateException("this token is done. Don't read it!");
while(true) {
try {
int read = chan.read(buf);
if (read == -1)
throw new EOFException("Could not read Integer Token");
else if (read == 0) // no more to read - wait for next signal.
return;
} finally {
buf.clear();
}
if (currentByte[0] < ZERO || currentByte[0] > NINE) {
if (currentByte[0] == MINUS && sb.length() == 0 && multiplier != -1)
multiplier = -1;
else if (currentByte[0] == terminator && sb.length() != 0) {
try {
BigInteger b = new BigInteger(sb.toString());
result = getResult(b);
} catch (NumberFormatException impossible) {
throw new IOException(impossible.getMessage());
}
sb = null;
done = true;
return;
}
else
throw new IOException("invalid integer");
}
else if (currentByte[0] == ZERO) {
switch (sb.length()) {
case 0 :
if (multiplier == -1) throw new IOException("negative 0");
multiplier = 0;
break;
case 1 :
if (multiplier == 0) throw new IOException("leading 0s");
}
sb.append(0);
}
else {
if (multiplier == 0)
throw new IOException("leading 0s - wrong");
sb.append(currentByte[0] - ZERO);
}
}
}
protected abstract T getResult(BigInteger rawValue) throws IOException;
@Override
protected boolean isDone() {
return done;
}
}