package com.peterhi.io;
import static com.peterhi.Util.bitsToBytes;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UTFDataFormatException;
import java.lang.reflect.Array;
import java.text.MessageFormat;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.peterhi.Integer24;
import com.peterhi.IntegerX;
import com.peterhi.Util;
import com.peterhi.int24;
import com.peterhi.intx;
import com.peterhi.uintx;
import com.peterhi.property.PropDescriptor;
import com.peterhi.property.PropException;
import com.peterhi.property.PropModel;
import com.peterhi.property.PropUtil;
public final class PmInputStream extends DataInputStream
implements PmIoConstants{
public PmInputStream(InputStream in) {
super(in);
}
@SuppressWarnings("unchecked")
public <T extends PropModel> T readModel() throws IOException,
PropException, ClassNotFoundException, IllegalAccessException,
InstantiationException {
int size = readSize(false);
byte[] buffer = new byte[size];
readFully(buffer);
String string = new String(buffer, "ascii");
TypeExpr expr = new TypeExpr(string);
int[] counts = new int[expr.size()];
for (int i = 0; i < counts.length; i++) {
counts[i] = readSize(false);
}
PmLookup lookup = new PmLookup(expr, counts);
for (int i = 0; i < lookup.count(); i++) {
PropModel m = lookup.get(i);
Class<?> t = m.getClass();
Set<PropDescriptor> ps = PropUtil.getDescriptors(t);
for (PropDescriptor p : ps) {
Class<?> pt = p.getType();
Object pv = readRecursively(p, pt, lookup);
p.set(m, pv);
}
}
return (T )lookup.getRootModel();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Object readRecursively(PropDescriptor desc, Class<?> type,
PmLookup lookup) throws IOException, ClassNotFoundException,
InstantiationException, IllegalAccessException {
if (type == boolean.class) {
return readBoolean();
} else if (type == byte.class) {
return readByte();
} else if (type == char.class) {
return readChar();
} else if (type == short.class) {
return readShort();
} else if (type == int.class) {
return readInt();
} else if (type == long.class) {
return readLong();
} else if (type == float.class) {
return readFloat();
} else if (type == double.class) {
return readDouble();
} else if (type == int24.class) {
return new int24(this);
} else if (type == intx.class) {
return new intx(this);
} else if (type == uintx.class) {
return new uintx(this);
} else if (type == Boolean.class) {
int b = readByte();
return (b == -1) ? null : (b != 0);
} else if (type == boolean[].class) {
int size = readSize(true);
if (size < 0) {
return null;
}
boolean[] booleans = new boolean[size];
byte[] buffer = new byte[bitsToBytes(size)];
readFully(buffer);
for (int i = 0; i < size; i++) {
int index = i / Byte.SIZE;
int shift = i % Byte.SIZE;
int bits = (buffer[index] >>> shift) & 0x01;
booleans[i] = (bits != 0);
}
return booleans;
} else if (type == byte[].class) {
int size = readSize(true);
if (size < 0) {
return null;
}
byte[] buffer = new byte[size];
readFully(buffer);
return buffer;
} else if (type == Class.class) {
Class<?>[] classes = (Class<?>[] )readRecursively(desc,
Class[].class, lookup);
return (classes == null) ? null : classes[0];
} else if (type == Class[].class) {
int size = readSize(true);
if (size < 0) {
return null;
}
byte[] buffer = new byte[size];
readFully(buffer);
String string = new String(buffer, "ascii");
TypeExpr expr = new TypeExpr(string);
return expr.getTypes();
} else if (type == String.class) {
int size = readSize(true);
if (size < 0) {
return null;
}
byte[] buffer = new byte[size];
readFully(buffer);
StringBuilder sb = new StringBuilder();
int processed = 0;
while (processed < size) {
int ch0 = buffer[processed];
int width = utfCharWidth(ch0);
if (width == 0) {
utfCorrupt(processed);
}
processed++;
if (width == 1) {
char ch = 0;
ch |= ch0;
sb.append(ch);
} else if (width == 2) {
int ch1 = buffer[processed];
if ((ch1 & 0xc0) != 0x80) {
utfCorrupt(processed);
}
processed++;
char ch = 0;
ch |= (ch0 & 0x1f) << 6;
ch |= (ch1 & 0x3f) << 0;
sb.append(ch);
} else if (width == 3) {
int ch1 = buffer[processed];
if ((ch1 & 0xc0) != 0x80) {
utfCorrupt(processed);
}
processed++;
int ch2 = buffer[processed];
if ((ch2 & 0xc0) != 0x80) {
utfCorrupt(processed);
}
processed++;
char ch = 0;
ch |= (ch0 & 0x0f) << 16;
ch |= (ch1 & 0x3f) << 6;
ch |= (ch2 & 0x3f) << 0;
sb.append(ch);
}
}
return sb.toString();
} else if (PropModel.class.isAssignableFrom(type)) {
return fromIndex(lookup);
} else if (type.isArray()) {
int size = readSize(true);
if (size < 0) {
return null;
}
Class<?> ctype = type.getComponentType();
Object a = Array.newInstance(ctype, size);
for (int i = 0; i < size; i++) {
Object e = readRecursively(desc, ctype, lookup);
Array.set(a, i, e);
}
return a;
} else if (type == HashSet.class || type == ArrayList.class) {
int size = readSize(true);
if (size < 0) {
return null;
}
Collection<Object> c = (Collection<Object> )type.newInstance();
for (int i = 0; i < size; i++) {
Object e = readRecursively(desc, Object.class, lookup);
c.add(e);
}
return c;
} else if (type == HashMap.class) {
int size = readSize(true);
if (size < 0) {
return null;
}
Map<Object, Object> m = new HashMap<Object, Object>();
for (int i = 0; i < size; i++) {
Object k = readRecursively(desc, Object.class, lookup);
Object v = readRecursively(desc, Object.class, lookup);
m.put(k, v);
}
return m;
} else if (Collection.class.isAssignableFrom(type)) {
int size = readSize(false);
Object[] a = new Object[size];
for (int i = 0; i < size; i++) {
a[i] = readRecursively(desc, Object.class, lookup);
}
return a;
} else if (Map.class.isAssignableFrom(type)) {
int size = readSize(false);
Map.Entry<?, ?>[] a = new Map.Entry<?, ?>[size];
for (int i = 0; i < size; i++) {
Object k = readRecursively(desc, Object.class, lookup);
Object v = readRecursively(desc, Object.class, lookup);
a[i] = new AbstractMap.SimpleEntry(k, v);
}
return a;
} else if (type == Object.class) {
int rt = readSize(false);
Class<?> rtype = runtimeType(rt);
Object value = null;
if (rtype == Array.class) {
int crt = readSize(false);
Class<?> ctype = runtimeType(crt);
int dimension = readSize(false);
Class<?> mtype = Util.multiDimArray(ctype, dimension);
value = initBlankElements(mtype);
fillElements(desc, value, lookup);
} else if (rtype != null) {
value = readRecursively(desc, rtype, lookup);
}
return value;
} else {
boolean notNull = readBoolean();
if (!notNull) {
return null;
}
if (type == Byte.class) {
return readRecursively(desc, byte.class, lookup);
} else if (type == Character.class) {
return readRecursively(desc, char.class, lookup);
} else if (type == Short.class) {
return readRecursively(desc, short.class, lookup);
} else if (type == Integer.class) {
return readRecursively(desc, int.class, lookup);
} else if (type == Long.class) {
return readRecursively(desc, long.class, lookup);
} else if (type == Float.class) {
return readRecursively(desc, float.class, lookup);
} else if (type == Double.class) {
return readRecursively(desc, double.class, lookup);
} else if (type == Integer24.class) {
return readRecursively(desc, int24.class, lookup);
} else if (type == IntegerX.Signed.class) {
return readRecursively(desc, intx.class, lookup);
} else if (type == IntegerX.Unsigned.class) {
return readRecursively(desc, uintx.class, lookup);
} else {
throw new IllegalArgumentException(MessageFormat.format(
"Unsupported type \"{0}\".", type));
}
}
}
private int utfCharWidth(int ch) {
int high = ch >> 4;
if (high >= 0 && high <= 7) {
return 1;
} else if (high == 12 && high == 13) {
return 2;
} else if (high == 14) {
return 3;
}
return 0;
}
private Object initBlankElements(Class<?> type) throws IOException {
Class<?> ctype = type.getComponentType();
if (ctype == null) {
return null;
}
int size = readSize(true);
if (size < 0) {
return null;
}
Object array = Array.newInstance(ctype, size);
for (int i = 0; i < size; i++) {
Object element = initBlankElements(ctype);
Array.set(array, i, element);
}
return array;
}
private void fillElements(PropDescriptor desc, Object array,
PmLookup lookup) throws IOException, IllegalAccessException,
InstantiationException, ClassNotFoundException {
Class<?> type = array.getClass();
Class<?> ctype = type.getComponentType();
int length = Array.getLength(array);
if (!ctype.isArray()) {
for (int i = 0; i < length; i++) {
Object element = readRecursively(desc, ctype, lookup);
Array.set(array, i, element);
}
} else for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
fillElements(desc, element, lookup);
}
}
private int readSize(boolean nullable) throws IOException {
uintx u = new uintx(this);
return u.intValue() - (nullable ? 1 : 0);
}
private PropModel fromIndex(PmLookup lookup) throws IOException {
return lookup.get(readSize(true));
}
private void utfCorrupt(int index) throws IOException {
throw new UTFDataFormatException(MessageFormat.format(
"Corrupted utf data at indeex \"{0}\".", index));
}
private Class<?> runtimeType(int rt) {
if (rt == RT_NULL) {
return null;
} else if (rt == RT_BOOLEAN) {
return Boolean.class;
} else if (rt == RT_BYTE) {
return Byte.class;
} else if (rt == RT_CHARACTER) {
return Character.class;
} else if (rt == RT_SHORT) {
return Short.class;
} else if (rt == RT_INTEGER) {
return Integer.class;
} else if (rt == RT_LONG) {
return Long.class;
} else if (rt == RT_FLOAT) {
return Float.class;
} else if (rt == RT_DOUBLE) {
return Double.class;
} else if (rt == RT_INTEGER24) {
return Integer24.class;
} else if (rt == RT_INTEGERX_SIGNED) {
return IntegerX.Signed.class;
} else if (rt == RT_INTEGERX_UNSIGNED) {
return IntegerX.Unsigned.class;
} else if (rt == RT_BOOLEANS) {
return boolean[].class;
} else if (rt == RT_BYTES) {
return byte[].class;
} else if (rt == RT_CHARS) {
return char[].class;
} else if (rt == RT_SHORTS) {
return short[].class;
} else if (rt == RT_INTS) {
return int[].class;
} else if (rt == RT_LONGS) {
return long[].class;
} else if (rt == RT_FLOATS) {
return float[].class;
} else if (rt == RT_DOUBLES) {
return double[].class;
} else if (rt == RT_INT24S) {
return int24[].class;
} else if (rt == RT_INTXS) {
return intx[].class;
} else if (rt == RT_UINTXS) {
return uintx[].class;
} else if (rt == RT_CLASS) {
return Class.class;
} else if (rt == RT_CLASSES) {
return Class[].class;
} else if (rt == RT_STRING) {
return String.class;
} else if (rt == RT_PROPMODEL) {
return PropModel.class;
} else if (rt == RT_HASHSET) {
return HashSet.class;
} else if (rt == RT_ARRAYLIST) {
return ArrayList.class;
} else if (rt == RT_HASHMAP) {
return HashMap.class;
} else if (rt == RT_ARRAY) {
return Array.class;
} else {
throw new IllegalArgumentException(MessageFormat.format(
"Unsupported type \"{0\"}.", rt));
}
}
}