package de.jpaw.bonaparte.core;
import java.io.DataOutput;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.joda.time.Instant;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.joda.time.ReadablePartial;
import de.jpaw.bonaparte.enums.BonaNonTokenizableEnum;
import de.jpaw.bonaparte.enums.BonaTokenizableEnum;
import de.jpaw.bonaparte.pojos.meta.AlphanumericElementaryDataItem;
import de.jpaw.bonaparte.pojos.meta.BasicNumericElementaryDataItem;
import de.jpaw.bonaparte.pojos.meta.BinaryElementaryDataItem;
import de.jpaw.bonaparte.pojos.meta.ClassDefinition;
import de.jpaw.bonaparte.pojos.meta.EnumDataItem;
import de.jpaw.bonaparte.pojos.meta.FieldDefinition;
import de.jpaw.bonaparte.pojos.meta.MiscElementaryDataItem;
import de.jpaw.bonaparte.pojos.meta.NumericElementaryDataItem;
import de.jpaw.bonaparte.pojos.meta.ObjectReference;
import de.jpaw.bonaparte.pojos.meta.TemporalElementaryDataItem;
import de.jpaw.bonaparte.pojos.meta.XEnumDataItem;
import de.jpaw.enums.AbstractByteEnumSet;
import de.jpaw.enums.AbstractIntEnumSet;
import de.jpaw.enums.AbstractLongEnumSet;
import de.jpaw.enums.AbstractShortEnumSet;
import de.jpaw.enums.AbstractStringEnumSet;
import de.jpaw.enums.AbstractStringXEnumSet;
import de.jpaw.enums.AbstractXEnumBase;
import de.jpaw.enums.EnumSetMarker;
import de.jpaw.enums.TokenizableEnum;
import de.jpaw.enums.XEnum;
import de.jpaw.util.ByteArray;
public abstract class AbstractCompactComposer extends AbstractMessageComposer<IOException> implements CompactConstants {
private static Field unsafeString = calculateUnsafe();
static private Field calculateUnsafe() {
try {
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
return f;
} catch (Exception e) {
return null;
}
}
private final boolean useJsonForBonaCustomInElements; // if true, the subobjects inside Element will be output as JSON. This kills enums on deserialization, but allows to deserialize objects not known on receiver side.
private final boolean useCache;
private final Map<BonaCustom, Integer> objectCache;
private int numberOfObjectsSerialized;
private int numberOfObjectReuses;
// variables set by constructor
protected final DataOutput out;
protected final boolean recommendIdentifiable; // if true, then factoryId and classId will be used to identify the object (requires prior registration of factories before parsing)
protected boolean skipLowerBoundObjectDescription = true; // if true and the object to serialize corresponds to its lower bound, then do not output the class description
protected AbstractCompactComposer jsonComposer = null; // derived composer on same DataOutput, which also creates field names. Will be initialized on demand.
protected AbstractCompactComposer(DataOutput out, ObjectReuseStrategy reuseStrategy, boolean recommendIdentifiable, boolean useJsonForBonaCustomInElements) {
switch (reuseStrategy) {
case BY_CONTENTS:
this.objectCache = new HashMap<BonaCustom, Integer>(250);
this.useCache = true;
break;
case BY_REFERENCE:
this.objectCache = new IdentityHashMap<BonaCustom, Integer>(250);
this.useCache = true;
break;
default:
this.objectCache = null;
this.useCache = false;
break;
}
this.out = out;
this.recommendIdentifiable = recommendIdentifiable;
this.useJsonForBonaCustomInElements = useJsonForBonaCustomInElements;
numberOfObjectsSerialized = 0;
numberOfObjectReuses = 0;
}
public boolean isSkipLowerBoundObjectDescription() {
return skipLowerBoundObjectDescription;
}
public void setSkipLowerBoundObjectDescription(boolean skipLowerBoundObjectDescription) {
this.skipLowerBoundObjectDescription = skipLowerBoundObjectDescription;
}
// must be overridden / called if caching / reuse is active!
public void reset() {
numberOfObjectsSerialized = 0;
numberOfObjectReuses = 0;
if (useCache)
objectCache.clear();
}
// for statistics
public int getNumberOfObjectReuses() {
return numberOfObjectReuses;
}
protected void writeNull() throws IOException {
out.writeByte(NULL_FIELD);
}
@Override
public void writeNull(FieldDefinition di) throws IOException {
out.writeByte(NULL_FIELD);
}
@Override
public void writeNullCollection(FieldDefinition di) throws IOException {
out.writeByte(NULL_FIELD);
}
// write a non-empty string (using charAt())
protected void writeLongString(String s) throws IOException {
char maxCode = 0;
int numWith2Byte = 0;
int len = s.length();
for (int i = 0; i < len; ++ i) {
char c = s.charAt(i);
if (c > maxCode)
maxCode = c;
if (c > 127)
++numWith2Byte;
}
if (maxCode <= 255) { // no reason not to support a full 8 bit character set - this may save a bit of space for some west european texts
// pure ASCII String
if (len <= 16) {
out.writeByte(SHORT_ISO_STRING + len - 1);
} else {
out.writeByte(ISO_STRING);
intOut(len);
}
out.writeBytes(s);
} else if (maxCode < 2048) {
// UTF-8 out, with max. 2 byte sequences...
out.writeByte(UTF8_STRING);
intOut(len + numWith2Byte);
for (int i = 0; i < len; ++i) {
int c = s.charAt(i);
if (c < 128) {
out.writeByte(c);
} else {
// out.writeShort(((0xC0 | (c >> 6)) << 8) | (0x80 | (c & 0x3F)));
out.writeShort(0xC080 | ((c & 0x7c0) << 2) | (c & 0x3F)); // should be the same, but shorter
}
}
} else {
// UTF-16, due to possible 3 byte sequences
out.writeByte(UTF16_STRING);
intOut(len);
out.writeChars(s);
}
}
// write a non-empty string (using char[])
protected void writeLongStringArray(String s) throws IOException {
char maxCode = 0;
int numWith2Byte = 0;
int len = s.length();
char buff [] = s.toCharArray();
for (int i = 0; i < len; ++ i) {
char c = buff[i];
if (c > maxCode)
maxCode = c;
if (c > 127)
++numWith2Byte;
}
if (maxCode <= 255) {
// pure ASCII String
if (len <= 16) {
out.writeByte(SHORT_ISO_STRING + len - 1);
} else {
out.writeByte(ISO_STRING);
intOut(len);
}
for (int i = 0; i < len; ++i)
out.writeByte(buff[i]);
} else if (maxCode < 2048) {
// UTF-8 out, with max. 2 byte sequences...
out.writeByte(UTF8_STRING);
intOut(len + numWith2Byte);
for (int i = 0; i < len; ++i) {
int c = buff[i];
if (c < 128) {
out.writeByte(c);
} else {
// out.writeShort(((0xC0 | (c >> 6)) << 8) | (0x80 | (c & 0x3F)));
out.writeShort(0xC080 | ((c & 0x7c0) << 2) | (c & 0x3F)); // should be the same, but shorter
}
}
} else {
// UTF-16, due to possible 3 byte sequences
out.writeByte(UTF16_STRING);
intOut(len);
for (int i = 0; i < len; ++i)
out.writeShort(buff[i]);
}
}
// write a non-empty string (using char[])
protected void writeLongStringStealArray(String s) throws IOException {
if (unsafeString == null) {
writeLongStringArray(s);
return;
}
char buff[];
try {
buff = (char []) unsafeString.get(s);
} catch (Exception e) {
writeLongStringArray(s);
return;
}
char maxCode = 0;
int numWith2Byte = 0;
int len = buff.length;
for (int i = 0; i < len; ++ i) {
char c = buff[i];
if (c > maxCode)
maxCode = c;
if (c > 127)
++numWith2Byte;
}
if (maxCode <= 255) {
// pure ASCII String
if (len <= 16) {
out.writeByte(SHORT_ISO_STRING + len - 1);
} else {
out.writeByte(ISO_STRING);
intOut(len);
}
for (int i = 0; i < len; ++i)
out.writeByte(buff[i]);
} else if (maxCode < 2048) {
// UTF-8 out, with max. 2 byte sequences...
out.writeByte(UTF8_STRING);
intOut(len + numWith2Byte);
for (int i = 0; i < len; ++i) {
int c = buff[i];
if (c < 128) {
out.writeByte(c);
} else {
// out.writeShort(((0xC0 | (c >> 6)) << 8) | (0x80 | (c & 0x3F)));
out.writeShort(0xC080 | ((c & 0x7c0) << 2) | (c & 0x3F)); // should be the same, but shorter
}
}
} else {
// UTF-16, due to possible 3 byte sequences
out.writeByte(UTF16_STRING);
intOut(len);
for (int i = 0; i < len; ++i)
out.writeShort(buff[i]);
}
}
// output an integral value
protected void intOut(int n) throws IOException {
if (n >= 0) {
// bisect the cases...
if (n <= 60) {
// single byte
if (n < 32)
out.writeByte(n);
else
out.writeByte(0x80 - 32 + n);
} else if (n <= 0x7fff) {
if (n < 4096) {
// 2 byte integer
// out.writeByte(0xc0 + (n >> 8));
// out.writeByte(n);
out.writeShort(n | 0xc000); // the same, but faster
} else {
// 3 byte integer
out.writeByte(INT_2BYTE);
out.writeShort(n);
}
} else if (n <= 0x7fffff) {
// out.writeByte(INT_3BYTE);
// out.writeByte(n >> 16);
// out.writeShort(n);
out.writeInt(n | (INT_3BYTE << 24)); // the same, but faster
} else {
out.writeByte(INT_4BYTE);
out.writeInt(n);
}
} else {
if (n >= -32768) {
if (n >= -10)
out.writeByte(0xa0 - n);
else {
out.writeByte(INT_2BYTE);
out.writeShort(n);
}
} else if (n >= -0x800000) {
// out.writeByte(INT_3BYTE);
// out.writeByte(n >> 16);
// out.writeShort(n);
out.writeInt((n & 0xffffff) | (INT_3BYTE << 24)); // the same, but faster
} else {
out.writeByte(INT_4BYTE);
out.writeInt(n);
}
}
}
protected void charOut(char c) throws IOException {
// if it is a displayable ASCII character, there is a short form
if ((c & ~0x7f) == 0 && c >= 0x20) {
// 1:1 mapping! write it as a byte!
out.writeByte(c); // 1 byte
} else if ((c & 0xff00) == 0) {
// ISO char
out.writeByte(SHORT_ISO_STRING); // 2 byte
out.writeByte(c);
} else {
// something else. Write a single char
out.writeByte(UNICODE_CHAR); // 3 byte
out.writeShort((short)c);
}
}
// character
@Override
public void addField(MiscElementaryDataItem di, char c) throws IOException {
charOut(c);
}
protected void stringOut(String s) throws IOException {
if (s.length() == 0) {
out.writeByte(EMPTY_FIELD);
} else if (s.length() == 1) {
charOut(s.charAt(0));
} else if (s.length() > 8) {
writeLongStringStealArray(s);
} else {
writeLongString(s);
}
}
@Override
public void addField(AlphanumericElementaryDataItem di, String s) throws IOException {
if (s == null) {
writeNull();
} else {
stringOut(s);
}
}
// n is not null
protected void bigintOut(BigInteger n) throws IOException {
int l = n.bitLength();
// see if we fit into an int
if (l <= 31) {
// yes, then store as an int
intOut(n.intValue()); // intValueExact() requires Java 1.8
} else {
out.writeByte(COMPACT_BIGINTEGER);
byte [] tmp = n.toByteArray(); // TODO: do some dirty trick to avoid temporary array construction!
intOut(tmp.length);
out.write(tmp);
}
}
// n is not null
protected void bigdecimalOut(BigDecimal n) throws IOException {
int sgn = n.signum();
if (sgn == 0) {
out.writeByte(0); // special case, but no need to treat it separately while parsing, as general integers are accepted
} else {
int scale = n.scale();
if (scale > 0 || scale < -3) {
// is a fractional number
if (scale > 0 && scale <= 9) {
out.writeByte(COMPACT_BIGDECIMAL + scale);
} else {
out.writeByte(COMPACT_BIGDECIMAL);
intOut(scale);
}
bigintOut(n.unscaledValue());
} else {
// number is integral (and not with exponent HUGE)
bigintOut(n.toBigInteger());
}
}
}
// decimal
@Override
public void addField(NumericElementaryDataItem di, BigDecimal n) throws IOException {
if (n != null) {
bigdecimalOut(n);
} else {
writeNull();
}
}
// byte
@Override
public void addField(BasicNumericElementaryDataItem di, byte n) throws IOException {
intOut(n);
}
// short
@Override
public void addField(BasicNumericElementaryDataItem di, short n) throws IOException {
intOut(n);
}
// integer
@Override
public void addField(BasicNumericElementaryDataItem di, int n) throws IOException {
intOut(n);
}
// int(n)
@Override
public void addField(BasicNumericElementaryDataItem di, BigInteger n) throws IOException {
if (n != null) {
bigintOut(n);
} else {
writeNull();
}
}
// entry which does not need a reference
protected void longOut(long n) throws IOException {
int nn = (int)n;
if (nn == n)
intOut((int)n);
else {
if ((n & 0xffff0000L) == (n > 0 ? 0 : 0xffff0000L)) {
out.writeByte(INT_6BYTE);
out.writeShort((short)(n >> 32));
out.writeInt((int)n);
return;
}
// default
out.writeByte(INT_8BYTE); // TODO: optimize for 5 or 7 bytes here!
out.writeLong(n);
}
}
// long
@Override
public void addField(BasicNumericElementaryDataItem di, long n) throws IOException {
longOut(n);
}
// boolean
@Override
public void addField(MiscElementaryDataItem di, boolean b) throws IOException {
out.writeByte(b ? COMPACT_BOOLEAN_TRUE : COMPACT_BOOLEAN_FALSE);
}
// float
@Override
public void addField(BasicNumericElementaryDataItem di, float f) throws IOException {
int i = (int)f;
if (i == f) {
// integral number, can be stored in compact form
intOut(i);
} else {
out.writeByte(COMPACT_FLOAT);
out.writeFloat(f);
}
}
// double
@Override
public void addField(BasicNumericElementaryDataItem di, double d) throws IOException {
int i = (int)d;
if (i == d) {
// integral number, can be stored in compact form
intOut(i);
} else {
out.writeByte(COMPACT_DOUBLE);
out.writeDouble(d);
}
}
protected void uuidOut(UUID n) throws IOException {
out.writeByte(COMPACT_UUID);
out.writeLong(n.getMostSignificantBits());
out.writeLong(n.getLeastSignificantBits());
}
// UUID
@Override
public void addField(MiscElementaryDataItem di, UUID n) throws IOException {
if (n != null) {
uuidOut(n);
} else {
writeNull();
}
}
protected void bytearrayOut(ByteArray b) throws IOException {
out.writeByte(COMPACT_BINARY);
intOut(b.length());
if (b.length() > 0) {
b.writeToDataOutput(out);
}
}
// ByteArray: initial quick & dirty implementation
@Override
public void addField(BinaryElementaryDataItem di, ByteArray b) throws IOException {
if (b != null) {
bytearrayOut(b);
} else {
writeNull();
}
}
protected void bytesOut(byte [] b) throws IOException {
out.writeByte(COMPACT_BINARY);
intOut(b.length);
if (b.length > 0) {
out.write(b);
}
}
// raw. Almost the same as ByteArray...
@Override
public void addField(BinaryElementaryDataItem di, byte[] b) throws IOException {
if (b != null) {
bytesOut(b);
} else {
writeNull();
}
}
protected void localdateOut(LocalDate t) throws IOException {
out.writeByte(COMPACT_DATE);
intOut(t.getYear());
intOut(t.getMonthOfYear());
intOut(t.getDayOfMonth());
}
// converters for DAY und TIMESTAMP
@Override
public void addField(TemporalElementaryDataItem di, LocalDate t) throws IOException {
if (t != null) {
localdateOut(t);
} else {
writeNull();
}
}
protected void localdatetimeOut(LocalDateTime t) throws IOException {
int millis = t.getMillisOfSecond();
boolean fractional = millis != 0;
out.writeByte(!fractional ? COMPACT_DATETIME : COMPACT_DATETIME_MILLIS);
intOut(t.getYear());
intOut(t.getMonthOfYear());
intOut(t.getDayOfMonth());
if (fractional)
intOut(t.getMillisOfDay());
else
intOut(t.getMillisOfDay() / 1000);
}
@Override
public void addField(TemporalElementaryDataItem di, LocalDateTime t) throws IOException {
if (t != null) {
localdatetimeOut(t);
} else {
writeNull();
}
}
protected void localtimeOut(LocalTime t) throws IOException {
int millis = t.getMillisOfSecond();
if (millis != 0) {
out.writeByte(COMPACT_TIME_MILLIS);
intOut(t.getMillisOfDay());
} else {
out.writeByte(COMPACT_TIME);
intOut(t.getMillisOfDay() / 1000);
}
}
@Override
public void addField(TemporalElementaryDataItem di, LocalTime t) throws IOException {
if (t != null) {
localtimeOut(t);
} else {
writeNull();
}
}
@Override
public void addField(TemporalElementaryDataItem di, Instant t) throws IOException {
if (t != null) {
longOut(t.getMillis());
} else {
writeNull();
}
}
@Override
public void startTransmission() throws IOException {
}
@Override
public void terminateTransmission() throws IOException {
}
@Override
public void terminateRecord() throws IOException {
}
@Override
public void writeSuperclassSeparator() throws IOException {
out.writeByte(PARENT_SEPARATOR);
}
@Override
public void startRecord() throws IOException {
}
@Override
public void startMap(FieldDefinition di, int currentMembers) throws IOException {
out.writeByte(MAP_BEGIN);
intOut(currentMembers);
}
@Override
public void startArray(FieldDefinition di, int currentMembers, int sizeOfElement) throws IOException {
out.writeByte(ARRAY_BEGIN);
intOut(currentMembers);
}
// removed collections terminator because it conflicts with the intended use of parent / child serialization to separate tables
@Override
public void terminateArray() throws IOException {
// out.out.writeByte(COLLECTIONS_TERMINATOR);
}
@Override
public void terminateMap() throws IOException {
// out.out.writeByte(COLLECTIONS_TERMINATOR);
}
@Override
public void startObject(ObjectReference di, BonaCustom obj) throws IOException {
ClassDefinition meta = obj.ret$MetaData();
if (skipLowerBoundObjectDescription && di.getLowerBound() != null && di.getLowerBound().getName().equals(meta.getName())) {
if (recommendIdentifiable) {
out.writeByte(OBJECT_BEGIN_BASE);
} else {
out.writeByte(OBJECT_BEGIN_PQON);
out.writeByte(EMPTY_FIELD);
addField(REVISION_META, meta.getRevision());
}
} else {
if (recommendIdentifiable && meta.getFactoryId() > 0 && meta.getId() > 0) {
out.writeByte(OBJECT_BEGIN_ID);
intOut(meta.getFactoryId());
intOut(meta.getId());
} else {
out.writeByte(OBJECT_BEGIN_PQON);
writeLongStringStealArray(meta.getName());
addField(REVISION_META, meta.getRevision());
}
}
}
@Override
public void terminateObject(ObjectReference di, BonaCustom obj) throws IOException {
out.writeByte(OBJECT_TERMINATOR);
}
@Override
public void addField(ObjectReference di, BonaCustom obj) throws IOException {
if (obj == null) {
writeNull();
} else {
if (useCache) {
Integer previousIndex = objectCache.get(obj);
if (previousIndex != null) {
// reuse this instance
out.writeByte(OBJECT_AGAIN);
intOut(numberOfObjectsSerialized - previousIndex.intValue() - 1);
++numberOfObjectReuses;
return;
}
// add the new object to the cache of known objects. This is
// done despite we are not yet done with the object!
objectCache.put(obj, Integer.valueOf(numberOfObjectsSerialized++));
// fall through
}
// start a new object
startObject(di, obj);
// do all fields (now includes terminator)
obj.serializeSub(this);
// terminate the object
terminateObject(di, obj);
}
}
// enum with numeric expansion: delegate to Null/Int
@Override
public void addEnum(EnumDataItem di, BasicNumericElementaryDataItem ord, BonaNonTokenizableEnum n) throws IOException {
if (n == null)
writeNull(ord);
else
addField(ord, n.ordinal());
}
// enum with alphanumeric expansion: delegate to Null/String
@Override
public void addEnum(EnumDataItem di, AlphanumericElementaryDataItem token, BonaTokenizableEnum n) throws IOException {
if (n == null)
writeNull(token);
else
addField(token, n.getToken());
}
// xenum with alphanumeric expansion: delegate to Null/String
@Override
public void addEnum(XEnumDataItem di, AlphanumericElementaryDataItem token, XEnum<?> n) throws IOException {
if (n == null)
writeNull(token);
else
addField(token, n.getToken());
}
@Override
public boolean addExternal(ObjectReference di, Object obj) throws IOException {
return false; // perform conversion by default
}
@Override
public void addField(ObjectReference di, Map<String, Object> obj) throws IOException {
if (obj == null) {
writeNull(di);
return;
}
elementOut(obj);
}
@Override
public void addField(ObjectReference di, List<Object> l) throws IOException {
if (l == null) {
writeNull(di);
return;
}
elementOut(l);
}
@Override
public void addField(ObjectReference di, Object obj) throws IOException {
if (obj == null) {
writeNull(di);
return;
}
elementOut(obj);
}
// output an Object array - anonymous subroutine
protected void elementOut(Object [] l) throws IOException {
if (l == null) {
writeNull();
return;
}
out.writeByte(ARRAY_BEGIN);
intOut(l.length);
for (Object o : l)
elementOut(o);
}
// output a list - anonymous subroutine
protected void elementOut(List<Object> l) throws IOException {
if (l == null) {
writeNull();
return;
}
out.writeByte(ARRAY_BEGIN);
intOut(l.size());
for (Object o : l)
elementOut(o);
}
// output a map - anonymous subroutine
protected void elementOut(Map<String, Object> obj) throws IOException {
if (obj == null) {
writeNull();
return;
}
out.writeByte(MAP_BEGIN);
intOut(obj.size()); // number of members. Note: this implies we cannot skip nulls! OK as we assume they would have been cleared before if it was the intention to do so.
for (Map.Entry<String, Object> elem: obj.entrySet()) {
stringOut(elem.getKey());
elementOut(elem.getValue());
}
}
// output a generic object - anonymous subroutine
protected void elementOut(Object obj) throws IOException {
if (obj == null) {
writeNull();
return;
}
// check for types. here, we do not have primitive types
if (obj instanceof Number) {
// check numeric types
if (obj instanceof Integer) {
intOut(((Integer)obj).intValue());
return;
}
if (obj instanceof Long) {
longOut(((Long)obj).longValue());
return;
}
if (obj instanceof Byte) {
intOut(((Byte)obj).intValue());
return;
}
if (obj instanceof Short) {
intOut(((Short)obj).intValue());
return;
}
if (obj instanceof BigInteger) {
bigintOut((BigInteger)obj);
return;
}
if (obj instanceof BigDecimal) {
bigdecimalOut((BigDecimal)obj);
return;
}
if (obj instanceof Double) {
out.writeByte(COMPACT_DOUBLE); // no check for integral type here, as we have no type information
out.writeDouble(((Double)obj).doubleValue());
return;
}
if (obj instanceof Float) {
out.writeByte(COMPACT_FLOAT); // no check for integral type here, as we have no type information
out.writeFloat(((Float)obj).floatValue());
return;
}
throw new RuntimeException("Unrecognized type " + obj.getClass().getSimpleName() + " for compact number");
}
if (obj instanceof String) {
stringOut((String)obj);
return;
}
if (obj instanceof Boolean) {
out.writeByte((Boolean)obj ? COMPACT_BOOLEAN_TRUE : COMPACT_BOOLEAN_FALSE);
return;
}
if (obj instanceof UUID) {
uuidOut((UUID)obj);
return;
}
if (obj instanceof Character) {
charOut(((Character)obj).charValue());
return;
}
if (obj instanceof ByteArray) {
bytearrayOut((ByteArray)obj);
return;
}
if (obj instanceof Map<?,?>) {
elementOut((Map<String, Object>)obj);
return;
}
if (obj instanceof List<?>) {
elementOut((List<Object>)obj);
return;
}
if (obj instanceof EnumSetMarker) {
if (obj instanceof AbstractStringEnumSet<?>) {
stringOut(((AbstractStringEnumSet<?>)obj).getBitmap());
} else if (obj instanceof AbstractStringXEnumSet<?>) {
stringOut(((AbstractStringXEnumSet<?>)obj).getBitmap());
} else if (obj instanceof AbstractIntEnumSet<?>) {
intOut(((AbstractIntEnumSet<?>)obj).getBitmap());
} else if (obj instanceof AbstractLongEnumSet<?>) {
longOut(((AbstractLongEnumSet<?>)obj).getBitmap());
} else if (obj instanceof AbstractByteEnumSet<?>) {
intOut(((AbstractByteEnumSet<?>)obj).getBitmap());
} else if (obj instanceof AbstractShortEnumSet<?>) {
intOut(((AbstractShortEnumSet<?>)obj).getBitmap());
} else {
throw new RuntimeException("Cannot transform enum set of type " + obj.getClass().getSimpleName() + " to JSON");
}
return;
}
if (obj instanceof Set<?>) {
final Set<?> l = (Set<?>)obj;
out.writeByte(ARRAY_BEGIN);
intOut(l.size());
for (Object o : l)
elementOut(o);
return;
}
if (obj instanceof Enum) {
// distinguish Tokenizable
if (obj instanceof TokenizableEnum) {
stringOut(((TokenizableEnum)obj).getToken());
} else {
intOut(((Enum<?>)obj).ordinal());
}
return;
}
if (obj instanceof AbstractXEnumBase<?>) {
stringOut(((AbstractXEnumBase)obj).getToken());
return;
}
if (obj instanceof Instant) {
longOut(((Instant)obj).getMillis());
return;
}
if (obj instanceof ReadablePartial) {
if (obj instanceof LocalDate) {
localdateOut((LocalDate)obj);
return;
}
if (obj instanceof LocalTime) {
localtimeOut((LocalTime)obj);
return;
}
if (obj instanceof LocalDateTime) {
localdatetimeOut((LocalDateTime)obj);
return;
}
throw new RuntimeException("Cannot transform joda readable partial of type " + obj.getClass().getSimpleName() + " to JSON");
}
if (obj instanceof BonaCustom) {
if (useJsonForBonaCustomInElements) {
// create a special JSON composer on demand
if (jsonComposer == null)
jsonComposer = new CompactJsonComposer(out); // composer which adds ability to output field names
jsonComposer.writeObject((BonaCustom)obj);
} else {
addField(StaticMeta.INNER_BONAPORTABLE, (BonaCustom)obj); // output objects as object, even within JSON! (catch issues during deserialize)
}
return;
}
if (obj instanceof Object []) {
// any array of an object type (String, Boolean whatever...) except primitive arrays
elementOut((Object [])obj);
return;
}
if (obj.getClass().isArray()) {
outputPrimitiveArrays(obj);
return;
}
throw new RuntimeException("Cannot transform type " + obj.getClass().getSimpleName() + " to JSON");
}
// code moved out due to excessive length
private void outputPrimitiveArrays(Object obj) throws IOException {
if (obj instanceof byte []) {
bytesOut((byte [])obj);
return;
}
out.writeByte(ARRAY_BEGIN);
if (obj instanceof int []) {
final int [] array = (int [])obj;
final int len = array.length;
intOut(len);
for (int i = 0; i < len; ++i) {
intOut(array[i]);
}
return;
}
if (obj instanceof boolean []) {
final boolean [] array = (boolean [])obj;
final int len = array.length;
intOut(len);
for (int i = 0; i < len; ++i) {
out.writeByte(array[i] ? COMPACT_BOOLEAN_TRUE : COMPACT_BOOLEAN_FALSE);
}
return;
}
if (obj instanceof char []) {
final char [] array = (char [])obj;
final int len = array.length;
intOut(len);
for (int i = 0; i < len; ++i) {
stringOut(Character.toString(array[i])); // converts char [] to a string
}
return;
}
if (obj instanceof long []) {
final long [] array = (long [])obj;
final int len = array.length;
intOut(len);
for (int i = 0; i < len; ++i) {
longOut(array[i]);
}
return;
}
if (obj instanceof short []) {
final short [] array = (short [])obj;
final int len = array.length;
intOut(len);
for (int i = 0; i < len; ++i) {
intOut(array[i]);
}
return;
}
if (obj instanceof double []) {
final double [] array = (double [])obj;
final int len = array.length;
intOut(len);
for (int i = 0; i < len; ++i) {
out.writeByte(COMPACT_DOUBLE); // no check for integral type here, as we have no type information
out.writeDouble(array[i]);
}
return;
}
if (obj instanceof float []) {
final float [] array = (float [])obj;
final int len = array.length;
intOut(len);
for (int i = 0; i < len; ++i) {
out.writeByte(COMPACT_FLOAT); // no check for integral type here, as we have no type information
out.writeFloat(array[i]);
}
return;
}
throw new RuntimeException("Not yet supported: primitive array " + obj.getClass().getSimpleName());
}
}