package de.jpaw.bonaparte.core;
import java.io.DataOutput;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Map;
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 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.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.util.ByteArray;
// improvement idea: in the constructor, a map (or lambda expression) could be passed which says which classes should be serialized as Map and which as object.
// composer which does the same as the standard compact composer, but adds field names
public class CompactJsonComposer extends AbstractCompactComposer {
private int arrayDepth = 0;
protected CompactJsonComposer(DataOutput out) {
super(out, ObjectReuseStrategy.NONE, false, true);
}
protected void resetArrayDepth() { // arrayDepth counts the level how deep we are in nested arrays / maps, which determines if field name output is required
arrayDepth = 0;
}
protected void optFieldNameOut(FieldDefinition di) throws IOException {
if (arrayDepth == 0)
super.stringOut(di.getName());
}
@Override
public void writeNull(FieldDefinition di) throws IOException {
// skip nulls, but only if not in array
if (arrayDepth != 0)
super.writeNull(di);
}
@Override
public void writeNullCollection(FieldDefinition di) throws IOException {
// skip nulls, but only if not in array
if (arrayDepth != 0)
super.writeNull(di);
}
@Override
public void startArray(FieldDefinition di, int currentMembers, int sizeOfElement) throws IOException {
optFieldNameOut(di);
super.startArray(di, currentMembers, sizeOfElement);
++arrayDepth;
}
@Override
public void startMap(FieldDefinition di, int currentMembers) throws IOException {
optFieldNameOut(di);
super.startMap(di, currentMembers);
++arrayDepth;
}
@Override
public void writeSuperclassSeparator() throws IOException {
// not contained in output
}
@Override
public void terminateMap() throws IOException {
super.terminateMap();
--arrayDepth;
}
@Override
public void terminateArray() throws IOException {
--arrayDepth;
}
// @Override
// public void terminateRecord() throws IOException {
// }
//
// @Override
// public void terminateTransmission() throws IOException {
// }
@Override
public void addField(AlphanumericElementaryDataItem di, String s) throws IOException {
if (s != null) {
optFieldNameOut(di);
super.stringOut(s);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(MiscElementaryDataItem di, boolean b) throws IOException {
optFieldNameOut(di);
super.addField(di, b);
}
@Override
public void addField(MiscElementaryDataItem di, char c) throws IOException {
optFieldNameOut(di);
super.addField(di, c);
}
@Override
public void addField(BasicNumericElementaryDataItem di, double d) throws IOException {
optFieldNameOut(di);
super.addField(di, d);
}
@Override
public void addField(BasicNumericElementaryDataItem di, float f) throws IOException {
optFieldNameOut(di);
super.addField(di, f);
}
@Override
public void addField(BasicNumericElementaryDataItem di, byte n) throws IOException {
optFieldNameOut(di);
super.addField(di, n);
}
@Override
public void addField(BasicNumericElementaryDataItem di, short n) throws IOException {
optFieldNameOut(di);
super.intOut(n);
}
@Override
public void addField(BasicNumericElementaryDataItem di, int n) throws IOException {
optFieldNameOut(di);
super.intOut(n);
}
@Override
public void addField(BasicNumericElementaryDataItem di, long n) throws IOException {
optFieldNameOut(di);
super.longOut(n);
}
@Override
public void addField(BasicNumericElementaryDataItem di, BigInteger n) throws IOException {
if (n != null) {
optFieldNameOut(di);
super.bigintOut(n);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(NumericElementaryDataItem di, BigDecimal n) throws IOException {
if (n != null) {
optFieldNameOut(di);
super.bigdecimalOut(n);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(MiscElementaryDataItem di, UUID n) throws IOException {
if (n != null) {
optFieldNameOut(di);
super.uuidOut(n);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(BinaryElementaryDataItem di, ByteArray b) throws IOException {
if (b != null) {
optFieldNameOut(di);
super.bytearrayOut(b);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(BinaryElementaryDataItem di, byte[] b) throws IOException {
if (b != null) {
optFieldNameOut(di);
super.bytesOut(b);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(TemporalElementaryDataItem di, LocalDate t) throws IOException {
if (t != null) {
optFieldNameOut(di);
super.localdateOut(t);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(TemporalElementaryDataItem di, LocalDateTime t) throws IOException {
if (t != null) {
optFieldNameOut(di);
super.localdatetimeOut(t);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(TemporalElementaryDataItem di, LocalTime t) throws IOException {
if (t != null) {
optFieldNameOut(di);
super.localtimeOut(t);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(TemporalElementaryDataItem di, Instant t) throws IOException {
if (t != null) {
optFieldNameOut(di);
super.longOut(t.getMillis());
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(ObjectReference di, BonaCustom obj) throws IOException {
// nested objects are output as maps as well
if (obj != null) {
optFieldNameOut(di);
writeObject(obj);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void startObject(ObjectReference di, BonaCustom obj) throws IOException {
// no op
}
@Override
public void terminateObject(ObjectReference di, BonaCustom obj) throws IOException {
// no op
}
// the enum methods will forward to one of the existing output methods, the decision logic stays the same
// @Override
// public void addEnum(EnumDataItem di, BasicNumericElementaryDataItem ord, BonaNonTokenizableEnum n) throws IOException {
// }
//
// @Override
// public void addEnum(EnumDataItem di, AlphanumericElementaryDataItem token, BonaTokenizableEnum n) throws IOException {
// }
//
// @Override
// public void addEnum(XEnumDataItem di, AlphanumericElementaryDataItem token, XEnum<?> n) throws IOException {
// }
@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) {
optFieldNameOut(di);
super.addField(di, obj);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void addField(ObjectReference di, Object obj) throws IOException {
if (obj != null) {
optFieldNameOut(di);
super.addField(di, obj);
} else if (arrayDepth != 0)
writeNull();
}
@Override
public void writeRecord(BonaCustom o) throws IOException {
startRecord(); // Noop by default
writeObject(o);
terminateRecord(); // Noop by default
}
// write an object as map without a field name - nested entry
@Override
public void writeObject(BonaCustom o) throws IOException {
if (o != null) {
out.write(OBJECT_BEGIN_JSON);
super.stringOut(MimeTypes.JSON_FIELD_PQON);
super.stringOut(o.ret$PQON());
// push array nesting
final int savedArrayDepth = arrayDepth;
arrayDepth = 0;
o.serializeSub(this);
arrayDepth = savedArrayDepth;
out.write(OBJECT_TERMINATOR); // terminate object....
} else if (arrayDepth != 0)
writeNull();
}
}