/*
Dijjer - A Peer to Peer HTTP Cache
Copyright (C) 2004,2005 Change.Tv, Inc
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package freenet.support;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import freenet.io.WritableToDataOutputStream;
import freenet.io.comm.Peer;
import freenet.keys.Key;
import freenet.keys.NodeCHK;
import freenet.keys.NodeSSK;
import freenet.node.NewPacketFormat;
/**
* @author ian
*
* To change the template for this generated type comment go to Window - Preferences - Java - Code Generation - Code and
* Comments
*/
public class Serializer {
public static final String VERSION = "$Id: Serializer.java,v 1.5 2005/09/15 18:16:04 amphibian Exp $";
/**
* Maximum bit array size in bits.
*/
public static final int MAX_BITARRAY_SIZE = 2048*8;
/**
* Maximum incoming array length in bytes.
*/
//Max packet format size - 4 to account for starting size integer.
public static final int MAX_ARRAY_LENGTH = NewPacketFormat.MAX_MESSAGE_SIZE - 4;
public static List<Object> readListFromDataInputStream(Class<?> elementType, DataInput dis) throws IOException {
LinkedList<Object> ret = new LinkedList<Object>();
int length = dis.readInt();
for (int x = 0; x < length; x++) {
ret.add(readFromDataInputStream(elementType, dis));
}
return ret;
}
/**
* Attempts to read an object of the specified type from the input.
* @param type type to read.
* @param dis input to read from.
* @return object read.
* @throws IOException if a read operation result in an IO error or an unexpected value is encountered.
*/
public static Object readFromDataInputStream(Class<?> type, DataInput dis) throws IOException {
if (type.equals(Boolean.class)) {
final byte bool = dis.readByte();
/* Using readByte() instead of readBoolean() because values other than 0 or 1 indicate
* problems: only 0 and 1 are written.
*/
switch (bool) {
case 1: return Boolean.TRUE;
case 0: return Boolean.FALSE;
default: throw new IOException("Boolean is non boolean value: " + bool);
}
} else if (type.equals(Byte.class)) {
return dis.readByte();
} else if (type.equals(Short.class)) {
return dis.readShort();
} else if (type.equals(Integer.class)) {
return dis.readInt();
} else if (type.equals(Long.class)) {
return dis.readLong();
} else if (type.equals(Double.class)) {
return dis.readDouble();
} else if (type.equals(Float.class)) {
return dis.readFloat();
} else if (type.equals(String.class)) {
final int length = dis.readInt();
//TODO: Track read size so far and limit based on that? Might not be necessary.
//TODO: Should these be IO Exceptions or IllegalArgumentExceptions?
if (length < 0 || length > MAX_ARRAY_LENGTH) {
throw new IOException("Invalid string length: " + length);
}
StringBuilder sb = new StringBuilder(length);
for (int x = 0; x < length; x++) {
sb.append(dis.readChar());
}
return sb.toString();
} else if (type.equals(Buffer.class)) {
return new Buffer(dis);
} else if (type.equals(ShortBuffer.class)) {
return new ShortBuffer(dis);
} else if (type.equals(Peer.class)) {
return new Peer(dis);
} else if (type.equals(BitArray.class)) {
return new BitArray(dis, MAX_BITARRAY_SIZE);
} else if (type.equals(NodeCHK.class)) {
// Use Key.read(...) rather than NodeCHK-specific method because write(...) writes the TYPE field.
return Key.read(dis);
} else if (type.equals(NodeSSK.class)) {
// Use Key.read(...) rather than NodeSSK-specific method because write(...) writes the TYPE field.
return Key.read(dis);
} else if (type.equals(Key.class)) {
return Key.read(dis);
} else if (type.equals(double[].class)) {
// & 0xFF for unsigned byte. Can be up to 255, no negatives.
double[] array = new double[dis.readByte() & 0xFF];
for (int i = 0; i < array.length; i++) array[i] = dis.readDouble();
return array;
} else if (type.equals(float[].class)) {
final short length = dis.readShort();
if (length < 0 || length > MAX_ARRAY_LENGTH/4) {
throw new IOException("Invalid flat array length: " + length);
}
float[] array = new float[length];
for (int i = 0; i < array.length; i++) array[i] = dis.readFloat();
return array;
} else {
throw new RuntimeException("Unrecognised field type: " + type);
}
}
public static void writeToDataOutputStream(Object object, DataOutputStream dos) throws IOException {
Class<?> type = object.getClass();
if (type.equals(Long.class)) {
dos.writeLong((Long) object);
} else if (type.equals(Boolean.class)) {
dos.writeBoolean((Boolean) object);
} else if (type.equals(Integer.class)) {
dos.writeInt((Integer) object);
} else if (type.equals(Short.class)) {
dos.writeShort((Short) object);
} else if (type.equals(Double.class)) {
dos.writeDouble((Double) object);
} else if (type.equals(Float.class)) {
dos.writeFloat((Float)object);
} else if (WritableToDataOutputStream.class.isAssignableFrom(type)) {
((WritableToDataOutputStream) object).writeToDataOutputStream(dos);
} else if (type.equals(String.class)) {
String s = (String) object;
dos.writeInt(s.length());
for (int x = 0; x < s.length(); x++) {
dos.writeChar(s.charAt(x));
}
} else if (type.equals(LinkedList.class)) {
LinkedList<?> ll = (LinkedList<?>) object;
synchronized (ll) {
dos.writeInt(ll.size());
for (Object o : ll) {
writeToDataOutputStream(o, dos);
}
}
} else if (type.equals(Byte.class)) {
dos.write((Byte) object);
} else if (type.equals(double[].class)) {
// writeByte() takes the eight lower-order bits - length capped to 255.
final double[] array = (double[])object;
if (array.length > 255) {
throw new IllegalArgumentException("Cannot serialize an array of more than 255 doubles; attempted to " +
"serialize " + array.length + ".");
}
dos.writeByte(array.length);
for (double element : array) dos.writeDouble(element);
} else if (type.equals(float[].class)) {
dos.writeShort(((float[])object).length);
for (float element : (float[])object) dos.writeFloat(element);
} else {
throw new RuntimeException("Unrecognised field type: " + type);
}
}
/** Only works for simple messages!! */
public static int length(Class<?> type, int maxStringLength) {
if (type.equals(Long.class)) {
return 8;
} else if (type.equals(Boolean.class)) {
return 1;
} else if (type.equals(Integer.class)) {
return 4;
} else if (type.equals(Short.class)) {
return 2;
} else if (type.equals(Double.class)) {
return 8;
} else if (WritableToDataOutputStream.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Unknown length for "+type);
} else if (type.equals(String.class)) {
return 4 + maxStringLength * 2; // Written as chars
} else if (type.equals(LinkedList.class)) {
throw new IllegalArgumentException("Unknown length for LinkedList");
} else if (type.equals(Byte.class)) {
return 1;
} else {
throw new RuntimeException("Unrecognised field type: " + type);
}
}
}