Taco
3 years ago
6 changed files with 1051 additions and 1 deletions
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
package se.dimovski.rencode; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
|
||||
public class Rencode |
||||
{ |
||||
|
||||
public static Object decode(byte[] data) throws IOException |
||||
{ |
||||
final InputStream is = new ByteArrayInputStream(data); |
||||
final RencodeInputStream inputStream = new RencodeInputStream(is); |
||||
|
||||
final Object decoded = inputStream.readObject(); |
||||
inputStream.close(); |
||||
|
||||
return decoded; |
||||
} |
||||
|
||||
public static byte[] encode(Object obj) throws IOException |
||||
{ |
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
final RencodeOutputStream output = new RencodeOutputStream(baos); |
||||
output.writeObject(obj); |
||||
final byte[] encoded = baos.toByteArray(); |
||||
output.close(); |
||||
return encoded; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,494 @@
@@ -0,0 +1,494 @@
|
||||
package se.dimovski.rencode; |
||||
|
||||
import java.io.DataInput; |
||||
import java.io.EOFException; |
||||
import java.io.FilterInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.math.BigDecimal; |
||||
import java.math.BigInteger; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.TreeMap; |
||||
|
||||
public class RencodeInputStream extends FilterInputStream implements DataInput |
||||
{ |
||||
/** |
||||
* The charset that is being used for {@link String}s. |
||||
*/ |
||||
private final String charset; |
||||
|
||||
/** |
||||
* Whether or not all byte-Arrays should be decoded as {@link String}s. |
||||
*/ |
||||
private final boolean decodeAsString; |
||||
|
||||
/** |
||||
* Creates a {@link RencodeInputStream} with the default encoding. |
||||
*/ |
||||
public RencodeInputStream(InputStream in) |
||||
{ |
||||
this(in, Utils.UTF_8, false); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link RencodeInputStream} with the given encoding. |
||||
*/ |
||||
public RencodeInputStream(InputStream in, String charset) |
||||
{ |
||||
this(in, charset, false); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link RencodeInputStream} with the default encoding. |
||||
*/ |
||||
public RencodeInputStream(InputStream in, boolean decodeAsString) |
||||
{ |
||||
this(in, Utils.UTF_8, decodeAsString); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link RencodeInputStream} with the given encoding. |
||||
*/ |
||||
public RencodeInputStream(InputStream in, String charset, boolean decodeAsString) |
||||
{ |
||||
super(in); |
||||
|
||||
if (charset == null) |
||||
{ |
||||
throw new IllegalArgumentException("charset is null"); |
||||
} |
||||
|
||||
this.charset = charset; |
||||
this.decodeAsString = decodeAsString; |
||||
} |
||||
|
||||
/** |
||||
* Returns the charset that is used to decode {@link String}s. The default |
||||
* value is UTF-8. |
||||
*/ |
||||
public String getCharset() |
||||
{ |
||||
return charset; |
||||
} |
||||
|
||||
/** |
||||
* Returns true if all byte-Arrays are being turned into {@link String}s. |
||||
*/ |
||||
public boolean isDecodeAsString() |
||||
{ |
||||
return decodeAsString; |
||||
} |
||||
|
||||
/** |
||||
* Reads and returns an {@link Object}. |
||||
*/ |
||||
public Object readObject() throws IOException |
||||
{ |
||||
int token = readToken(); |
||||
|
||||
return readObject(token); |
||||
} |
||||
|
||||
/** |
||||
* Reads and returns an {@link Object}. |
||||
*/ |
||||
protected Object readObject(int token) throws IOException |
||||
{ |
||||
if (token == TypeCode.DICTIONARY) |
||||
{ |
||||
return readMap0(Object.class); |
||||
} |
||||
else if (Utils.isFixedDictionary(token)) |
||||
{ |
||||
return readMap0(Object.class, token); |
||||
} |
||||
else if (token == TypeCode.LIST) |
||||
{ |
||||
return readList0(Object.class); |
||||
} |
||||
else if (Utils.isFixedList(token)) |
||||
{ |
||||
return readList0(Object.class, token); |
||||
} |
||||
else if (Utils.isNumber(token)) |
||||
{ |
||||
return readNumber0(token); |
||||
} |
||||
else if (token == TypeCode.FALSE || token == TypeCode.TRUE) |
||||
{ |
||||
return readBoolean0(token); |
||||
} |
||||
else if (token == TypeCode.NULL) |
||||
{ |
||||
return null; |
||||
} |
||||
else if (Utils.isDigit(token) || Utils.isFixedString(token)) |
||||
{ |
||||
return readString(token, charset); |
||||
} |
||||
|
||||
throw new IOException("Not implemented: " + token); |
||||
} |
||||
|
||||
/** |
||||
* Reads and returns a {@link Map}. |
||||
*/ |
||||
public Map<String, ?> readMap() throws IOException |
||||
{ |
||||
return readMap(Object.class); |
||||
} |
||||
|
||||
/** |
||||
* Reads and returns a {@link Map}. |
||||
*/ |
||||
public <T> Map<String, T> readMap(Class<T> clazz) throws IOException |
||||
{ |
||||
int token = readToken(); |
||||
|
||||
if (token != TypeCode.DICTIONARY) |
||||
{ |
||||
throw new IOException(); |
||||
} |
||||
|
||||
return readMap0(clazz); |
||||
} |
||||
|
||||
private <T> Map<String, T> readMap0(Class<T> clazz) throws IOException |
||||
{ |
||||
Map<String, T> map = new TreeMap<String, T>(); |
||||
int token = -1; |
||||
while ((token = readToken()) != TypeCode.END) |
||||
{ |
||||
readMapItem(clazz, token, map); |
||||
} |
||||
|
||||
return map; |
||||
} |
||||
|
||||
private <T> Map<String, T> readMap0(Class<T> clazz, int token) throws IOException |
||||
{ |
||||
Map<String, T> map = new TreeMap<String, T>(); |
||||
|
||||
int count = token - TypeCode.EMBEDDED.DICT_START; |
||||
for (int i = 0; i < count; i++) |
||||
{ |
||||
readMapItem(clazz, readToken(), map); |
||||
} |
||||
|
||||
return map; |
||||
} |
||||
|
||||
private <T> void readMapItem(Class<T> clazz, int token, Map<String, T> map) throws UnsupportedEncodingException, |
||||
IOException |
||||
{ |
||||
String key = readString(token, charset); |
||||
T value = clazz.cast(readObject()); |
||||
|
||||
map.put(key, value); |
||||
} |
||||
|
||||
public int readToken() throws IOException |
||||
{ |
||||
int token = super.read(); |
||||
if (token == -1) |
||||
{ |
||||
throw new EOFException(); |
||||
} |
||||
return token; |
||||
} |
||||
|
||||
/** |
||||
* Reads and returns a {@link List}. |
||||
*/ |
||||
public List<?> readList() throws IOException |
||||
{ |
||||
return readList(Object.class); |
||||
} |
||||
|
||||
/** |
||||
* Reads and returns a {@link List}. |
||||
*/ |
||||
public <T> List<T> readList(Class<T> clazz) throws IOException |
||||
{ |
||||
int token = readToken(); |
||||
|
||||
if (token != TypeCode.LIST) |
||||
{ |
||||
throw new IOException(); |
||||
} |
||||
|
||||
return readList0(clazz); |
||||
} |
||||
|
||||
private <T> List<T> readList0(Class<T> clazz) throws IOException |
||||
{ |
||||
List<T> list = new ArrayList<T>(); |
||||
int token = -1; |
||||
while ((token = readToken()) != TypeCode.END) |
||||
{ |
||||
list.add(clazz.cast(readObject(token))); |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
private <T> List<T> readList0(Class<T> clazz, int token) throws IOException |
||||
{ |
||||
List<T> list = new ArrayList<T>(); |
||||
int length = token - TypeCode.EMBEDDED.LIST_START; |
||||
for (int i = 0; i < length; i++) |
||||
{ |
||||
list.add(clazz.cast(readObject())); |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
public boolean readBoolean() throws IOException |
||||
{ |
||||
return readBoolean0(readToken()); |
||||
} |
||||
|
||||
public boolean readBoolean0(int token) throws IOException |
||||
{ |
||||
if (token == TypeCode.FALSE) |
||||
{ |
||||
return false; |
||||
} |
||||
else if (token == TypeCode.TRUE) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
throw new IOException(); |
||||
} |
||||
|
||||
public byte readByte() throws IOException |
||||
{ |
||||
return (byte) readToken(); |
||||
} |
||||
|
||||
public char readChar() throws IOException |
||||
{ |
||||
return (char) readToken(); |
||||
} |
||||
|
||||
public double readDouble() throws IOException |
||||
{ |
||||
return readNumber().doubleValue(); |
||||
} |
||||
|
||||
public float readFloat() throws IOException |
||||
{ |
||||
return readNumber().floatValue(); |
||||
} |
||||
|
||||
public void readFully(byte[] dst) throws IOException |
||||
{ |
||||
readFully(dst, 0, dst.length); |
||||
} |
||||
|
||||
public void readFully(byte[] dst, int off, int len) throws IOException |
||||
{ |
||||
int total = 0; |
||||
|
||||
while (total < len) |
||||
{ |
||||
int r = read(dst, total, len - total); |
||||
if (r == -1) |
||||
{ |
||||
throw new EOFException(); |
||||
} |
||||
|
||||
total += r; |
||||
} |
||||
} |
||||
|
||||
public int readInt() throws IOException |
||||
{ |
||||
return readNumber().intValue(); |
||||
} |
||||
|
||||
public String readLine() throws IOException |
||||
{ |
||||
return readString(); |
||||
} |
||||
|
||||
public long readLong() throws IOException |
||||
{ |
||||
return readNumber().longValue(); |
||||
} |
||||
|
||||
public short readShort() throws IOException |
||||
{ |
||||
return readNumber().shortValue(); |
||||
} |
||||
|
||||
public String readUTF() throws IOException |
||||
{ |
||||
return readString(Utils.UTF_8); |
||||
} |
||||
|
||||
public int readUnsignedByte() throws IOException |
||||
{ |
||||
return readByte() & 0xFF; |
||||
} |
||||
|
||||
public int readUnsignedShort() throws IOException |
||||
{ |
||||
return readShort() & 0xFFFF; |
||||
} |
||||
|
||||
/** |
||||
* Reads and returns a {@link Number}. |
||||
*/ |
||||
public Number readNumber() throws IOException |
||||
{ |
||||
int token = readToken(); |
||||
|
||||
if (!Utils.isNumber(token)) |
||||
{ |
||||
throw new IOException(); |
||||
} |
||||
|
||||
return readNumber0(token); |
||||
} |
||||
|
||||
private Number readNumber0(int token) throws IOException |
||||
{ |
||||
switch (token) |
||||
{ |
||||
case TypeCode.BYTE: |
||||
return (int) readToBuffer(1).get(); |
||||
case TypeCode.SHORT: |
||||
return (int) readToBuffer(2).getShort(); |
||||
case TypeCode.INT: |
||||
return readToBuffer(4).getInt(); |
||||
case TypeCode.LONG: |
||||
return readToBuffer(8).getLong(); |
||||
case TypeCode.FLOAT: |
||||
return readToBuffer(4).getFloat(); |
||||
case TypeCode.DOUBLE: |
||||
return readToBuffer(8).getDouble(); |
||||
|
||||
case TypeCode.NUMBER: |
||||
return readNumber0(); |
||||
} |
||||
if (Utils.isNegativeFixedNumber(token)) |
||||
{ |
||||
return TypeCode.EMBEDDED.INT_NEG_START - 1 - token; |
||||
} |
||||
else if (Utils.isPositiveFixedNumber(token)) |
||||
{ |
||||
return TypeCode.EMBEDDED.INT_POS_START + token; |
||||
} |
||||
|
||||
throw new IOException("Unknown number. TypeCode: " + token); |
||||
} |
||||
|
||||
private ByteBuffer readToBuffer(int count) throws IOException |
||||
{ |
||||
return ByteBuffer.wrap(readBytesFixed(count)); |
||||
} |
||||
|
||||
private Number readNumber0() throws IOException |
||||
{ |
||||
StringBuilder buffer = new StringBuilder(); |
||||
|
||||
boolean decimal = false; |
||||
|
||||
int token = -1; |
||||
while ((token = readToken()) != TypeCode.END) |
||||
{ |
||||
if (token == '.') |
||||
{ |
||||
decimal = true; |
||||
} |
||||
|
||||
buffer.append((char) token); |
||||
} |
||||
|
||||
try |
||||
{ |
||||
if (decimal) |
||||
{ |
||||
return new BigDecimal(buffer.toString()); |
||||
} |
||||
else |
||||
{ |
||||
return new BigInteger(buffer.toString()); |
||||
} |
||||
} |
||||
catch (NumberFormatException err) |
||||
{ |
||||
throw new IOException("NumberFormatException", err); |
||||
} |
||||
} |
||||
|
||||
public int skipBytes(int n) throws IOException |
||||
{ |
||||
return (int) skip(n); |
||||
} |
||||
|
||||
/** |
||||
* Reads and returns a byte-Array. |
||||
*/ |
||||
public byte[] readBytes() throws IOException |
||||
{ |
||||
int token = readToken(); |
||||
|
||||
return readBytes(token); |
||||
} |
||||
|
||||
/** |
||||
* Reads and returns a {@link String}. |
||||
*/ |
||||
public String readString() throws IOException |
||||
{ |
||||
return readString(charset); |
||||
} |
||||
|
||||
private String readString(String encoding) throws IOException |
||||
{ |
||||
return readString(readToken(), encoding); |
||||
} |
||||
|
||||
private String readString(int token, String charset) throws IOException |
||||
{ |
||||
if (Utils.isFixedString(token)) |
||||
{ |
||||
int length = token - TypeCode.EMBEDDED.STR_START; |
||||
return new String(readBytesFixed(length), charset); |
||||
} |
||||
return new String(readBytes(token), charset); |
||||
} |
||||
|
||||
private byte[] readBytes(int token) throws IOException |
||||
{ |
||||
int length = readLength(token); |
||||
return readBytesFixed(length); |
||||
} |
||||
|
||||
private byte[] readBytesFixed(int count) throws IOException |
||||
{ |
||||
byte[] data = new byte[count]; |
||||
readFully(data); |
||||
return data; |
||||
} |
||||
|
||||
private int readLength(int token) throws IOException |
||||
{ |
||||
StringBuilder buffer = new StringBuilder(); |
||||
buffer.append((char) token); |
||||
|
||||
while ((token = readToken()) != TypeCode.LENGTH_DELIM) |
||||
{ |
||||
|
||||
buffer.append((char) token); |
||||
} |
||||
|
||||
return Integer.parseInt(buffer.toString()); |
||||
} |
||||
} |
@ -0,0 +1,404 @@
@@ -0,0 +1,404 @@
|
||||
package se.dimovski.rencode; |
||||
|
||||
import java.io.DataOutput; |
||||
import java.io.FilterOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
import java.lang.reflect.Array; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.Collection; |
||||
import java.util.Map; |
||||
import java.util.SortedMap; |
||||
import java.util.TreeMap; |
||||
|
||||
public class RencodeOutputStream extends FilterOutputStream implements DataOutput |
||||
{ |
||||
|
||||
/** |
||||
* The {@link String} charset. |
||||
*/ |
||||
private final String charset; |
||||
|
||||
/** |
||||
* Creates a {@link RencodeOutputStream} with the default charset. |
||||
*/ |
||||
public RencodeOutputStream(OutputStream out) |
||||
{ |
||||
this(out, Utils.UTF_8); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link RencodeOutputStream} with the given encoding. |
||||
*/ |
||||
public RencodeOutputStream(OutputStream out, String charset) |
||||
{ |
||||
super(out); |
||||
|
||||
if (charset == null) |
||||
{ |
||||
throw new NullPointerException("charset"); |
||||
} |
||||
|
||||
this.charset = charset; |
||||
} |
||||
|
||||
/** |
||||
* Returns the charset that is used to encode {@link String}s. The default |
||||
* value is UTF-8. |
||||
*/ |
||||
public String getCharset() |
||||
{ |
||||
return charset; |
||||
} |
||||
|
||||
/** |
||||
* Writes an {@link Object}. |
||||
*/ |
||||
public void writeObject(Object value) throws IOException |
||||
{ |
||||
if (value == null) |
||||
{ |
||||
writeNull(); |
||||
} |
||||
else if (value instanceof byte[]) |
||||
{ |
||||
writeBytes((byte[]) value); |
||||
} |
||||
else if (value instanceof Boolean) |
||||
{ |
||||
writeBoolean((Boolean) value); |
||||
|
||||
} |
||||
else if (value instanceof Character) |
||||
{ |
||||
writeChar((Character) value); |
||||
|
||||
} |
||||
else if (value instanceof Number) |
||||
{ |
||||
writeNumber((Number) value); |
||||
|
||||
} |
||||
else if (value instanceof String) |
||||
{ |
||||
writeString((String) value); |
||||
|
||||
} |
||||
else if (value instanceof Collection<?>) |
||||
{ |
||||
writeCollection((Collection<?>) value); |
||||
|
||||
} |
||||
else if (value instanceof Map<?, ?>) |
||||
{ |
||||
writeMap((Map<?, ?>) value); |
||||
|
||||
} |
||||
else if (value instanceof Enum<?>) |
||||
{ |
||||
writeEnum((Enum<?>) value); |
||||
|
||||
} |
||||
else if (value.getClass().isArray()) |
||||
{ |
||||
writeArray(value); |
||||
|
||||
} |
||||
else |
||||
{ |
||||
writeCustom(value); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Writes a null value |
||||
*/ |
||||
public void writeNull() throws IOException |
||||
{ |
||||
write(TypeCode.NULL); |
||||
} |
||||
|
||||
/** |
||||
* Overwrite this method to write custom objects. The default implementation |
||||
* throws an {@link IOException}. |
||||
*/ |
||||
protected void writeCustom(Object value) throws IOException |
||||
{ |
||||
throw new IOException("Cannot encode " + value); |
||||
} |
||||
|
||||
/** |
||||
* Writes the given byte-Array |
||||
*/ |
||||
public void writeBytes(byte[] value) throws IOException |
||||
{ |
||||
writeBytes(value, 0, value.length); |
||||
} |
||||
|
||||
/** |
||||
* Writes the given byte-Array |
||||
*/ |
||||
public void writeBytes(byte[] value, int offset, int length) throws IOException |
||||
{ |
||||
write(value, offset, length); |
||||
} |
||||
|
||||
/** |
||||
* Writes a boolean |
||||
*/ |
||||
public void writeBoolean(boolean value) throws IOException |
||||
{ |
||||
write(value ? TypeCode.TRUE : TypeCode.FALSE); |
||||
} |
||||
|
||||
/** |
||||
* Writes a char |
||||
*/ |
||||
public void writeChar(int value) throws IOException |
||||
{ |
||||
writeByte(value); |
||||
} |
||||
|
||||
/** |
||||
* Writes a byte |
||||
*/ |
||||
public void writeByte(int value) throws IOException |
||||
{ |
||||
write(TypeCode.BYTE); |
||||
write(value); |
||||
} |
||||
|
||||
/** |
||||
* Writes a short |
||||
*/ |
||||
public void writeShort(int value) throws IOException |
||||
{ |
||||
write(TypeCode.SHORT); |
||||
ByteBuffer buffer = ByteBuffer.allocate(Utils.SHORT_BYTES).putShort((short) value); |
||||
write(buffer.array()); |
||||
} |
||||
|
||||
/** |
||||
* Writes an int |
||||
*/ |
||||
public void writeInt(int value) throws IOException |
||||
{ |
||||
write(TypeCode.INT); |
||||
ByteBuffer buffer = ByteBuffer.allocate(Utils.INTEGER_BYTES).putInt(value); |
||||
write(buffer.array()); |
||||
} |
||||
|
||||
/** |
||||
* Writes a long |
||||
*/ |
||||
public void writeLong(long value) throws IOException |
||||
{ |
||||
write(TypeCode.LONG); |
||||
ByteBuffer buffer = ByteBuffer.allocate(Utils.LONG_BYTES).putLong(value); |
||||
write(buffer.array()); |
||||
} |
||||
|
||||
/** |
||||
* Writes a float |
||||
*/ |
||||
public void writeFloat(float value) throws IOException |
||||
{ |
||||
write(TypeCode.FLOAT); |
||||
ByteBuffer buffer = ByteBuffer.allocate(Utils.FLOAT_BYTES).putFloat(value); |
||||
write(buffer.array()); |
||||
} |
||||
|
||||
/** |
||||
* Writes a double |
||||
*/ |
||||
public void writeDouble(double value) throws IOException |
||||
{ |
||||
write(TypeCode.DOUBLE); |
||||
ByteBuffer buffer = ByteBuffer.allocate(Utils.DOUBLE_BYTES).putDouble(value); |
||||
write(buffer.array()); |
||||
} |
||||
|
||||
/** |
||||
* Writes a {@link Number} |
||||
*/ |
||||
public void writeNumber(Number num) throws IOException |
||||
{ |
||||
if (num instanceof Float) |
||||
{ |
||||
writeFloat(num.floatValue()); |
||||
} |
||||
else if (num instanceof Double) |
||||
{ |
||||
writeDouble(num.doubleValue()); |
||||
} |
||||
if (0 <= num.intValue() && num.intValue() < TypeCode.EMBEDDED.INT_POS_COUNT) |
||||
{ |
||||
write(TypeCode.EMBEDDED.INT_POS_START + num.intValue()); |
||||
} |
||||
else if (-TypeCode.EMBEDDED.INT_NEG_COUNT <= num.intValue() && num.intValue() < 0) |
||||
{ |
||||
write(TypeCode.EMBEDDED.INT_NEG_START - 1 - num.intValue()); |
||||
} |
||||
else if (Byte.MIN_VALUE <= num.intValue() && num.intValue() < Byte.MAX_VALUE) |
||||
{ |
||||
writeByte(num.byteValue()); |
||||
} |
||||
else if (Short.MIN_VALUE <= num.intValue() && num.intValue() < Short.MAX_VALUE) |
||||
{ |
||||
writeShort(num.shortValue()); |
||||
} |
||||
else if (Integer.MIN_VALUE <= num.longValue() && num.longValue() < Integer.MAX_VALUE) |
||||
{ |
||||
writeInt(num.intValue()); |
||||
} |
||||
else if (Long.MIN_VALUE <= num.longValue() && num.longValue() < Long.MAX_VALUE) |
||||
{ |
||||
writeLong(num.longValue()); |
||||
} |
||||
else |
||||
{ |
||||
String number = num.toString(); |
||||
write(TypeCode.NUMBER); |
||||
write(number.getBytes(charset)); |
||||
write(TypeCode.END); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Writes a {@link String} |
||||
*/ |
||||
public void writeString(String value) throws IOException |
||||
{ |
||||
int len = value.length(); |
||||
if (len < TypeCode.EMBEDDED.STR_COUNT) |
||||
{ |
||||
write(TypeCode.EMBEDDED.STR_START + len); |
||||
} |
||||
else |
||||
{ |
||||
String lenString = Integer.toString(len); |
||||
writeBytes(lenString.getBytes(charset)); |
||||
write(TypeCode.LENGTH_DELIM); |
||||
} |
||||
|
||||
writeBytes(value.getBytes(charset)); |
||||
} |
||||
|
||||
/** |
||||
* Writes a {@link Collection}. |
||||
*/ |
||||
public void writeCollection(Collection<?> value) throws IOException |
||||
{ |
||||
boolean useEndToken = value.size() >= TypeCode.EMBEDDED.LIST_COUNT; |
||||
if (useEndToken) |
||||
{ |
||||
write(TypeCode.LIST); |
||||
} |
||||
else |
||||
{ |
||||
write(TypeCode.EMBEDDED.LIST_START + value.size()); |
||||
} |
||||
|
||||
for (Object element : value) |
||||
{ |
||||
writeObject(element); |
||||
} |
||||
|
||||
if (useEndToken) |
||||
{ |
||||
write(TypeCode.END); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Writes a {@link Map}. |
||||
*/ |
||||
public void writeMap(Map<?, ?> map) throws IOException |
||||
{ |
||||
if (!(map instanceof SortedMap<?, ?>)) |
||||
{ |
||||
map = new TreeMap<Object, Object>(map); |
||||
} |
||||
|
||||
boolean untilEnd = map.size() >= TypeCode.EMBEDDED.DICT_COUNT; |
||||
|
||||
if (untilEnd) |
||||
{ |
||||
write(TypeCode.DICTIONARY); |
||||
} |
||||
else |
||||
{ |
||||
write(TypeCode.EMBEDDED.DICT_START + map.size()); |
||||
} |
||||
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) |
||||
{ |
||||
writeObject(entry.getKey()); |
||||
writeObject(entry.getValue()); |
||||
} |
||||
|
||||
if (untilEnd) |
||||
{ |
||||
write(TypeCode.END); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Writes an {@link Enum}. |
||||
*/ |
||||
public void writeEnum(Enum<?> value) throws IOException |
||||
{ |
||||
writeString(value.name()); |
||||
} |
||||
|
||||
/** |
||||
* Writes an array |
||||
*/ |
||||
public void writeArray(Object value) throws IOException |
||||
{ |
||||
int length = Array.getLength(value); |
||||
boolean useEndToken = length >= TypeCode.EMBEDDED.LIST_COUNT; |
||||
if (useEndToken) |
||||
{ |
||||
write(TypeCode.LIST); |
||||
} |
||||
else |
||||
{ |
||||
write(TypeCode.EMBEDDED.LIST_START + length); |
||||
} |
||||
|
||||
for (int i = 0; i < length; i++) |
||||
{ |
||||
writeObject(Array.get(value, i)); |
||||
} |
||||
|
||||
if (useEndToken) |
||||
{ |
||||
write(TypeCode.END); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Writes the given {@link String} |
||||
*/ |
||||
public void writeBytes(String value) throws IOException |
||||
{ |
||||
writeString(value); |
||||
} |
||||
|
||||
/** |
||||
* Writes the given {@link String} |
||||
*/ |
||||
public void writeChars(String value) throws IOException |
||||
{ |
||||
writeString(value); |
||||
} |
||||
|
||||
/** |
||||
* Writes an UTF encoded {@link String} |
||||
*/ |
||||
public void writeUTF(String value) throws IOException |
||||
{ |
||||
writeBytes(value.getBytes(Utils.UTF_8)); |
||||
} |
||||
} |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
package se.dimovski.rencode; |
||||
|
||||
public class TypeCode |
||||
{ |
||||
// The bencode 'typecodes' such as i, d, etc have been
|
||||
// extended and relocated on the base-256 character set.
|
||||
public static final char LIST = 59; |
||||
public static final char DICTIONARY = 60; |
||||
public static final char NUMBER = 61; |
||||
public static final char BYTE = 62; |
||||
public static final char SHORT = 63; |
||||
public static final char INT = 64; |
||||
public static final char LONG = 65; |
||||
public static final char FLOAT = 66; |
||||
public static final char DOUBLE = 44; |
||||
public static final char TRUE = 67; |
||||
public static final char FALSE = 68; |
||||
public static final char NULL = 69; |
||||
public static final char END = 127; |
||||
public static final char LENGTH_DELIM = ':'; |
||||
|
||||
/* |
||||
* TypeCodes with embedded values/lengths |
||||
*/ |
||||
public static class EMBEDDED |
||||
{ |
||||
// Positive integers
|
||||
public static final int INT_POS_START = 0; |
||||
public static final int INT_POS_COUNT = 44; |
||||
|
||||
// Negative integers
|
||||
public static final int INT_NEG_START = 70; |
||||
public static final int INT_NEG_COUNT = 32; |
||||
|
||||
// Dictionaries
|
||||
public static final int DICT_START = 102; |
||||
public static final int DICT_COUNT = 25; |
||||
|
||||
// Strings
|
||||
public static final int STR_START = 128; |
||||
public static final int STR_COUNT = 64; |
||||
|
||||
// Lists
|
||||
public static final int LIST_START = STR_START + STR_COUNT; |
||||
public static final int LIST_COUNT = 64; |
||||
} |
||||
} |
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
package se.dimovski.rencode; |
||||
|
||||
public class Utils |
||||
{ |
||||
// Character Encodings
|
||||
public final static String UTF_8 = "UTF-8"; |
||||
public final static String ISO_8859 = "ISO-8859-1"; |
||||
|
||||
// Byte-lengths for types
|
||||
public static final int SHORT_BYTES = Short.SIZE / Byte.SIZE; |
||||
public static final int INTEGER_BYTES = Integer.SIZE / Byte.SIZE; |
||||
public static final int LONG_BYTES = Long.SIZE / Byte.SIZE; |
||||
public static final int FLOAT_BYTES = Float.SIZE / Byte.SIZE; |
||||
public static final int DOUBLE_BYTES = Double.SIZE / Byte.SIZE; |
||||
|
||||
// Maximum length of integer when written as base 10 string.
|
||||
public static final int MAX_INT_LENGTH = 64; |
||||
|
||||
private static boolean tokenInRange(int token, int start, int count) |
||||
{ |
||||
return start <= token && token < (start + count); |
||||
} |
||||
|
||||
public static boolean isNumber(int token) |
||||
{ |
||||
switch (token) |
||||
{ |
||||
case TypeCode.NUMBER: |
||||
case TypeCode.BYTE: |
||||
case TypeCode.SHORT: |
||||
case TypeCode.INT: |
||||
case TypeCode.LONG: |
||||
case TypeCode.FLOAT: |
||||
case TypeCode.DOUBLE: |
||||
return true; |
||||
} |
||||
return isFixedNumber(token); |
||||
} |
||||
|
||||
public static boolean isFixedNumber(int token) |
||||
{ |
||||
return isPositiveFixedNumber(token) || isNegativeFixedNumber(token); |
||||
} |
||||
|
||||
public static boolean isPositiveFixedNumber(int token) |
||||
{ |
||||
return tokenInRange(token, TypeCode.EMBEDDED.INT_POS_START, TypeCode.EMBEDDED.INT_POS_COUNT); |
||||
} |
||||
|
||||
public static boolean isNegativeFixedNumber(int token) |
||||
{ |
||||
return tokenInRange(token, TypeCode.EMBEDDED.INT_NEG_START, TypeCode.EMBEDDED.INT_NEG_COUNT); |
||||
} |
||||
|
||||
public static boolean isFixedList(int token) |
||||
{ |
||||
return tokenInRange(token, TypeCode.EMBEDDED.LIST_START, TypeCode.EMBEDDED.LIST_COUNT); |
||||
} |
||||
|
||||
public static boolean isFixedDictionary(int token) |
||||
{ |
||||
return tokenInRange(token, TypeCode.EMBEDDED.DICT_START, TypeCode.EMBEDDED.DICT_COUNT); |
||||
} |
||||
|
||||
public static boolean isFixedString(int token) |
||||
{ |
||||
return tokenInRange(token, TypeCode.EMBEDDED.STR_START, TypeCode.EMBEDDED.STR_COUNT); |
||||
} |
||||
|
||||
public static boolean isDigit(int token) |
||||
{ |
||||
return '0' <= token && token <= '9'; |
||||
} |
||||
} |
Loading…
Reference in new issue