package com.peterhi.io;
import static com.peterhi.Util.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.peterhi.Integer24;
import com.peterhi.IntegerX;
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 PmOutputStream extends DataOutputStream
implements PmIoConstants {
public PmOutputStream(OutputStream out) {
super(out);
}
public void writeModel(PropModel model) throws IOException, PropException {
if (model == null) {
throw new IllegalArgumentException("Null model.");
}
PmLookup lookup = new PmLookup(model);
Class<?> mtype = model.getClass();
Class<?>[] types = lookup.getTypes();
TypeExpr expr = new TypeExpr().types(types).mainType(mtype);
String string = expr.toString();
writeSize(string.length(), false);
writeBytes(string);
for (List<PropModel> list : lookup.values()) {
writeSize(list.size(), false);
}
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 = p.get(m);
writeRecursively(p, pt, pv, lookup);
}
}
}
private void writeRecursively(PropDescriptor desc, Class<?> type,
Object value, PmLookup lookup) throws IOException {
if (type == boolean.class) {
writeBoolean((Boolean )value);
} else if (type == byte.class) {
writeByte((Byte )value);
} else if (type == char.class) {
writeChar((Character )value);
} else if (type == short.class) {
writeShort((Short )value);
} else if (type == int.class) {
writeInt((Integer )value);
} else if (type == long.class) {
writeLong((Long )value);
} else if (type == float.class) {
writeFloat((Float )value);
} else if (type == double.class) {
writeDouble((Double )value);
} else if (type == int24.class) {
write(((int24 )value).toByteArray());
} else if (type == intx.class) {
write(((intx )value).toByteArray());
} else if (type == uintx.class) {
write(((uintx )value).toByteArray());
} else if (type == Boolean.class) {
Boolean b = (Boolean )value;
write(b == null ? -1 : (b ? 1 : 0));
} else if (type == boolean[].class) {
boolean[] booleans = (boolean[] )value;
int size = sizeof(booleans);
if (!writeSize(size, true)) {
return;
}
byte[] bytes = new byte[bitsToBytes(size)];
for (int i = 0; i < size; i++) {
int index = i / Byte.SIZE;
int shift = i % Byte.SIZE;
int bits = booleans[i] ? 1 : 0;
bytes[index] |= bits << shift;
}
write(bytes);
} else if (type == byte[].class) {
byte[] bytes = (byte[] )value;
int size = sizeof(bytes);
if (!writeSize(size, true)) {
return;
}
write(bytes);
} else if (type == Class.class) {
Class<?> clazz = (Class<?> )value;
Class<?>[] classes = (value == null) ? null :
new Class<?>[] { clazz };
writeRecursively(desc, Class[].class, classes, lookup);
} else if (type == Class[].class) {
TypeExpr expr = (value == null) ? null :
new TypeExpr().types((Class<?>[] )value);
String string = (expr == null) ? null : expr.toString();
int size = sizeof(string);
if (!writeSize(size, true)) {
return;
}
writeBytes(string);
} else if (type == String.class) {
String string = (String )value;
int size = sizeofUtf(string);
if (!writeSize(size, true)) {
return;
}
for (int i = 0; i < string.length(); i++) {
char ch = string.charAt(i);
if (ch >= 0x0001 && ch <= 0x007f) {
write(ch);
} else if (ch > 0x07ff) {
write(0x30 | ((ch >> 12) & 0x0f));
write(0x80 | ((ch >> 6) & 0x3f));
write(0x80 | ((ch >> 0) & 0x3f));
} else {
write(0xc0 | ((ch >> 6) & 0x1f));
write(0x80 | ((ch >> 0) & 0x3f));
}
}
} else if (PropModel.class.isAssignableFrom(type)) {
PropModel model = (PropModel )value;
writeIndex(model, lookup);
} else if (type.isArray()) {
int size = sizeof(value);
if (!writeSize(size, true)) {
return;
}
Class<?> ctype = type.getComponentType();
for (int i = 0; i < size; i++) {
Object e = at(value, i);
writeRecursively(desc, ctype, e, lookup);
}
} else if (type == HashSet.class || type == ArrayList.class) {
int size = sizeof(value);
if (!writeSize(size, true)) {
return;
}
for (int i = 0; i < size; i++) {
Object e = at(value, i);
writeRecursively(desc, Object.class, e, lookup);
}
} else if (type == HashMap.class) {
int size = sizeof(value);
if (!writeSize(size, true)) {
return;
}
for (int i = 0; i < size; i++) {
Map.Entry<?, ?> e = (Map.Entry<?, ?> )at(value, i);
Object k = e.getKey();
Object v = e.getValue();
writeRecursively(desc, Object.class, k, lookup);
writeRecursively(desc, Object.class, v, lookup);
}
} else if (Collection.class.isAssignableFrom(type)) {
int size = sizeof(value);
for (int i = 0; i < size; i++) {
Object e = at(value, i);
writeRecursively(desc, Object.class, e, lookup);
}
} else if (Map.class.isAssignableFrom(type)) {
int size = sizeof(value);
for (int i = 0; i < size; i++) {
Map.Entry<?, ?> e = (Map.Entry<?, ?> )at(value, i);
Object k = e.getKey();
Object v = e.getValue();
writeRecursively(desc, Object.class, k, lookup);
writeRecursively(desc, Object.class, v, lookup);
}
} else if (type == Object.class) {
int rt = runtimeType(value);
if (!writeRuntimeType(rt)) {
return;
}
Class<?> rtype = value.getClass();
if (rt == RT_ARRAY) {
Class<?> ctype = componentType(rtype);
int crt = runtimeType(ctype);
int dimension = dimension(rtype);
writeRuntimeType(crt);
writeSize(dimension, false);
writeSizes(value, dimension);
writeElements(desc, ctype, value, dimension, lookup);
} else if (rt != RT_NULL) {
writeRecursively(desc, rtype, value, lookup);
}
} else {
boolean notNull = (value != null);
writeBoolean(notNull);
if (!notNull) {
return;
}
if (type == Byte.class) {
writeRecursively(desc, byte.class, value, lookup);
} else if (type == Character.class) {
writeRecursively(desc, char.class, value, lookup);
} else if (type == Short.class) {
writeRecursively(desc, short.class, value, lookup);
} else if (type == Integer.class) {
writeRecursively(desc, int.class, value, lookup);
} else if (type == Long.class) {
writeRecursively(desc, long.class, value, lookup);
} else if (type == Float.class) {
writeRecursively(desc, float.class, value, lookup);
} else if (type == Double.class) {
writeRecursively(desc, double.class, value, lookup);
} else if (type == Integer24.class) {
writeRecursively(desc, int24.class,
new int24((Integer24 )value), lookup);
} else if (type == IntegerX.Signed.class) {
writeRecursively(desc, intx.class,
new intx((IntegerX.Signed )value), lookup);
} else if (type == IntegerX.Unsigned.class) {
writeRecursively(desc, uintx.class,
new uintx((IntegerX.Unsigned )value), lookup);
} else {
throw new IllegalArgumentException(MessageFormat.format(
"Unsupported type \"{0}\".", type));
}
}
}
private void writeSizes(Object value, int dimension)
throws IOException {
int size = sizeof(value);
if (!writeSize(size, true)) {
return;
}
boolean multidimensional = dimension > 1;
if (!multidimensional) {
return;
}
for (int i = 0; i < size; i++) {
Object e = at(value, i);
writeSizes(e, dimension - 1);
}
}
private void writeElements(PropDescriptor desc, Class<?> ctype,
Object value, int dimension, PmLookup lookup) throws IOException {
boolean multidimensional = dimension > 1;
int size = sizeof(value);
for (int i = 0; i < size; i++) {
Object e = at(value, i);
if (multidimensional) {
writeElements(desc, ctype, e, dimension - 1, lookup);
} else {
writeRecursively(desc, ctype, e, lookup);
}
}
}
private int dimension(Class<?> type) {
if (type == null) {
throw new IllegalArgumentException("Null type.");
}
if (!type.isArray()) {
return 0;
}
return dimension(type.getComponentType()) + 1;
}
private Class<?> componentType(Class<?> type) {
if (type == null) {
throw new IllegalArgumentException("Null type.");
}
if (!type.isArray()) {
return type;
}
return componentType(type.getComponentType());
}
private int runtimeType(Object value) {
return runtimeType(value == null ? null : value.getClass());
}
private int runtimeType(Class<?> type) {
if (type == null) {
return RT_NULL;
} else if (type == Boolean.class) {
return RT_BOOLEAN;
} else if (type == Byte.class) {
return RT_BYTE;
} else if (type == Character.class) {
return RT_CHARACTER;
} else if (type == Short.class) {
return RT_SHORT;
} else if (type == Integer.class) {
return RT_INTEGER;
} else if (type == Long.class) {
return RT_LONG;
} else if (type == Float.class) {
return RT_FLOAT;
} else if (type == Double.class) {
return RT_DOUBLE;
} else if (type == Integer24.class) {
return RT_INTEGER24;
} else if (type == IntegerX.Signed.class) {
return RT_INTEGERX_SIGNED;
} else if (type == IntegerX.Unsigned.class) {
return RT_INTEGERX_UNSIGNED;
} else if (type == boolean[].class) {
return RT_BOOLEANS;
} else if (type == byte[].class) {
return RT_BYTES;
} else if (type == char[].class) {
return RT_CHARS;
} else if (type == short[].class) {
return RT_SHORTS;
} else if (type == int[].class) {
return RT_INTS;
} else if (type == long[].class) {
return RT_LONGS;
} else if (type == float[].class) {
return RT_FLOATS;
} else if (type == double[].class) {
return RT_DOUBLES;
} else if (type == int24[].class) {
return RT_INT24S;
} else if (type == intx[].class) {
return RT_INTXS;
} else if (type == uintx[].class) {
return RT_UINTXS;
} else if (type == Class.class) {
return RT_CLASS;
} else if (type == Class[].class) {
return RT_CLASSES;
} else if (type == String.class) {
return RT_STRING;
} else if (PropModel.class.isAssignableFrom(type)) {
return RT_PROPMODEL;
} else if (type == HashSet.class) {
return RT_HASHSET;
} else if (type == ArrayList.class) {
return RT_ARRAYLIST;
} else if (type == HashMap.class) {
return RT_HASHMAP;
} else if (type.isArray()) {
runtimeType(componentType(type));
return RT_ARRAY;
} else {
throw new IllegalArgumentException(MessageFormat.format(
"Unsupported type \"{0}\".", type));
}
}
private boolean writeSize(int size, boolean nullable) throws IOException {
uintx u = new uintx(size + (nullable ? 1 : 0));
write(u.toByteArray());
return nullable ? (size != -1) : true;
}
private void writeIndex(PropModel model, PmLookup lookup)
throws IOException {
int index = lookup.indexOf(model);
writeSize(index, true);
}
private boolean writeRuntimeType(int rt) throws IOException {
writeSize(rt, false);
return (rt != RT_NULL);
}
private int sizeofUtf(String string) {
if (string == null) {
return -1;
}
int size = 0;
for (int i = 0; i < string.length(); i++) {
char ch = string.charAt(i);
if (ch >= 0x0001 && ch <= 0x007f) {
size += 1;
} else if (ch > 0x07ff) {
size += 3;
} else {
size += 2;
}
}
return size;
}
}