package de.jpaw.bonaparte.core;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
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.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.AbstractXEnumBase;
import de.jpaw.enums.XEnumFactory;
import de.jpaw.util.ByteArray;
import de.jpaw.util.MapIterator;
// this parser will accept a subset of all possible convertable input types
// the following types can be assumed to be accepted:
// - the desired type itself
// - a string
// - the possible types created by the JsonParser for input originating from the corresponding type
public class MapParser extends AbstractMessageParser<MessageParserException> implements MessageParser<MessageParserException> {
private static final Logger LOGGER = LoggerFactory.getLogger(MapParser.class);
private final Map<String, Object> map;
private Iterator<Object> iter = null;
private String currentClass = "N/A";
protected final boolean instantInMillis;
protected final StringParserUtil spu;
protected MessageParserException err(int errno, FieldDefinition di) {
return new MessageParserException(errno, di.getName(), -1, currentClass);
}
public MapParser(Map<String, Object> map, boolean instantInMillis) {
this.map = map;
// currentClass = map.get(MimeTypes.JSON_FIELD_PQON);
this.instantInMillis = instantInMillis;
this.spu = new StringParserUtil(new ParsePositionProvider() {
@Override
public int getParsePosition() {
return -1;
}
@Override
public String getCurrentClassName() {
return currentClass;
}
}, instantInMillis);
}
// populate an existing object from a provided map
public static void populateFrom(BonaPortable obj, Map<String, Object> map) throws MessageParserException {
obj.deserialize(new MapParser(map, false));
}
private static BonaPortable allocObject(Map<String, Object> map, ObjectReference di) throws MessageParserException {
final ClassDefinition lowerBound = di.getLowerBound();
// create the object. Determine the type by the object reference, if that defines no subclassing
// otherwise, check for special fields like $PQON (partially qualified name), and @type (fully qualified name)
if (di.getAllowSubclasses() == false) {
// no parameter required. determine by reference
if (lowerBound == null)
throw new MessageParserException(MessageParserException.JSON_BAD_OBJECTREF);
return BonaPortableFactory.createObject(di.getLowerBound().getName()); // OK
}
// variable contents. see if we got a partially qualified name
Object pqon1 = map.get(MimeTypes.JSON_FIELD_PQON);
if (pqon1 != null && pqon1 instanceof String) {
// lucky day, we got the partially qualified name
return BonaPortableFactory.createObject((String)pqon1);
}
Object pqon2 = map.get(MimeTypes.JSON_FIELD_FQON);
if (pqon2 != null && pqon2 instanceof String) {
// also lucky, we got a fully qualified name
return BonaPortableFactory.createObjectByFqon((String)pqon2);
}
// fallback: use the lower bound of di, if provided
if (lowerBound == null) {
// also no lower bound? Cannot work around that!
throw new MessageParserException(MessageParserException.JSON_NO_PQON, di.getName(), -1, null);
}
if (LOGGER.isTraceEnabled()) {
// output the map we got
LOGGER.trace("Cannot convert Map to BonaPortable, Map dump is");
for (Map.Entry<String, Object> me : map.entrySet())
LOGGER.trace(" \"{}\": {}", me.getKey(), me.getValue() == null ? "null" : me.getValue().toString());
}
// severe issue if the base class is abstract
if (lowerBound.getIsAbstract()) {
LOGGER.warn("Parsed object cannot be determined, no type information provided and base object is abstract. {}: ({}...)", di.getName(), lowerBound.getName());
throw new MessageParserException(MessageParserException.JSON_NO_PQON, di.getName(), -1, null);
}
// issue a warning, at least, and use the base class
LOGGER.warn("Parsed object cannot be determined uniquely, no type information provided and subclasses allowed for {}: ({}...)", di.getName(), lowerBound.getName());
return BonaPortableFactory.createObject(lowerBound.getName());
}
// convert a map to BonaPortable, read class info from the Map ($PQON entries)
public static BonaPortable asBonaPortable(Map<String, Object> map, ObjectReference di) throws MessageParserException {
BonaPortable obj = allocObject(map, di);
populateFrom(obj, map);
return obj;
}
private Object getNext(FieldDefinition di) {
return (iter != null) ? iter.next() : map.get(di.getName());
}
private Object get(FieldDefinition di) throws MessageParserException {
Object z = getNext(di);
if (z == null && di.getIsRequired())
throw err(MessageParserException.ILLEGAL_EXPLICIT_NULL, di);
return z;
}
@Override
public MessageParserException enumExceptionConverter(IllegalArgumentException e) throws MessageParserException {
return new MessageParserException(MessageParserException.INVALID_ENUM_TOKEN, e.getMessage(), -1, currentClass);
}
@Override
public MessageParserException customExceptionConverter(String msg, Exception e) throws MessageParserException {
return new MessageParserException(MessageParserException.CUSTOM_OBJECT_EXCEPTION, e != null ? msg + e.toString() : msg, -1, currentClass);
}
@Override
public void setClassName(String newClassName) {
currentClass = newClassName;
}
@Override
public Character readCharacter(MiscElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof Character)
return (Character)z;
// must be string of length 1 otherwise
if (z instanceof String) {
return spu.readCharacter(di, (String)z);
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public UUID readUUID(MiscElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof UUID)
return (UUID)z;
if (z instanceof String) {
return spu.readUUID(di, (String)z);
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public Boolean readBoolean(MiscElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof Boolean)
return (Boolean)z;
if (z instanceof String) {
return spu.readBoolean(di, (String)z);
}
if (z instanceof Number) {
return ((Number)z).doubleValue() == 0.0 ? Boolean.TRUE : Boolean.FALSE;
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public Double readDouble(BasicNumericElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof Number) {
return Double.valueOf(((Number)z).doubleValue());
}
if (z instanceof String) {
return Double.valueOf(spu.readPrimitiveDouble(di, (String)z));
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public Float readFloat(BasicNumericElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof Number) {
return Float.valueOf(((Number)z).floatValue());
}
if (z instanceof String) {
return Float.valueOf(spu.readPrimitiveFloat(di, (String)z));
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public Long readLong(BasicNumericElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof Number) {
return Long.valueOf(((Number)z).longValue());
}
if (z instanceof String) {
return Long.valueOf(spu.readPrimitiveLong(di, (String)z));
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public Integer readInteger(BasicNumericElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof Number) {
return Integer.valueOf(((Number)z).intValue());
}
if (z instanceof String) {
return Integer.valueOf(spu.readPrimitiveInteger(di, (String)z));
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public Short readShort(BasicNumericElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof Number) {
return Short.valueOf(((Number)z).shortValue());
}
if (z instanceof String) {
return Short.valueOf(spu.readPrimitiveShort(di, (String)z));
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public Byte readByte(BasicNumericElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof Number) {
return Byte.valueOf(((Number)z).byteValue());
}
if (z instanceof String) {
return Byte.valueOf(spu.readPrimitiveByte(di, (String)z));
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public BigInteger readBigInteger(BasicNumericElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof BigInteger) // todo: check scale?
return (BigInteger)z;
if (z instanceof Number) {
// check int, long
if (z instanceof Integer)
return BigInteger.valueOf(((Integer)z).intValue());
if (z instanceof Long)
return BigInteger.valueOf(((Long)z).longValue());
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
if (z instanceof String) {
return spu.readBigInteger(di, (String)z);
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public BigDecimal readBigDecimal(NumericElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof BigDecimal) // todo: check scale?
return (BigDecimal)z;
if (z instanceof Number) {
// check double, int, long
if (z instanceof Integer)
return BigDecimal.valueOf(((Integer)z).intValue());
if (z instanceof Long)
return BigDecimal.valueOf(((Long)z).longValue());
if (z instanceof Double)
return BigDecimal.valueOf(((Double)z).doubleValue());
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
if (z instanceof String) {
return spu.readBigDecimal(di, (String)z);
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public String readAscii(AlphanumericElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof String) {
return spu.readAscii(di, (String)z);
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public String readString(AlphanumericElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof String) {
return spu.readString(di, (String)z);
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public ByteArray readByteArray(BinaryElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof ByteArray) {
final ByteArray zz = (ByteArray)z;
if (zz.length() > di.getLength())
throw err(MessageParserException.BINARY_TOO_LONG, di);
return zz;
}
if (z instanceof byte []) {
final byte [] zz = (byte [])z;
if (zz.length > di.getLength())
throw err(MessageParserException.BINARY_TOO_LONG, di);
return zz.length == 0 ? ByteArray.ZERO_BYTE_ARRAY : new ByteArray(zz);
}
if (z instanceof String) {
return spu.readByteArray(di, (String)z);
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public byte[] readRaw(BinaryElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof ByteArray) {
final ByteArray zz = (ByteArray)z;
if (zz.length() > di.getLength())
throw err(MessageParserException.BINARY_TOO_LONG, di);
return zz.getBytes();
}
if (z instanceof byte []) {
final byte [] zz = (byte [])z;
if (zz.length > di.getLength())
throw err(MessageParserException.BINARY_TOO_LONG, di);
return zz;
}
if (z instanceof String) {
return spu.readRaw(di, (String)z);
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public Instant readInstant(TemporalElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof Instant) {
return (Instant)z;
}
if (z instanceof String) {
return spu.readInstant(di, (String)z); // assumes precision = 1 second, with fractionals if ms
}
if (z instanceof Number) {
// convert number of seconds to Instant
return new Instant((long)(((Number)z).doubleValue() * 1000.0));
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public LocalDate readDay(TemporalElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof String) {
// cannot use spu, use JSON instead of Bonaparte formatting
try {
return LocalDate.parse((String)z, ISODateTimeFormat.date());
} catch (IllegalArgumentException e) {
throw err(MessageParserException.ILLEGAL_TIME, di);
}
}
if (z instanceof LocalDate) {
return (LocalDate)z;
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public LocalTime readTime(TemporalElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof String) {
// cannot use spu, use JSON instead of Bonaparte formatting
try {
//return LocalTime.parse((String)z, di.getFractionalSeconds() > 0 ? ISODateTimeFormat.time() : ISODateTimeFormat.timeNoMillis());
return LocalTime.parse((String)z, ISODateTimeFormat.timeParser()); // a more flexible parser
} catch (IllegalArgumentException e) {
throw err(MessageParserException.ILLEGAL_TIME, di);
}
}
if (z instanceof LocalTime) {
return (LocalTime)z;
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public LocalDateTime readDayTime(TemporalElementaryDataItem di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof String) {
// cannot use spu, use JSON instead of Bonaparte formatting
try {
// return LocalDateTime.parse((String)z, di.getFractionalSeconds() > 0 ? ISODateTimeFormat.dateTime() : ISODateTimeFormat.dateTimeNoMillis());
return LocalDateTime.parse((String)z, ISODateTimeFormat.dateTimeParser()); // a more flexible parser
} catch (IllegalArgumentException e) {
throw err(MessageParserException.ILLEGAL_TIME, di);
}
}
if (z instanceof LocalDateTime) {
return (LocalDateTime)z;
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public <R extends BonaPortable> R readObject(ObjectReference di, Class<R> type) throws MessageParserException {
// read of a nested object => uses a separate map!
Object z = get(di);
if (z == null)
return null;
BonaCustom obj;
if (z instanceof Map<?,?>) {
obj = asBonaPortable((Map<String, Object>)z, di); // recursive invocation
} else if (z instanceof BonaCustom) {
obj = (BonaCustom)z;
} else {
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
if (obj.getClass().equals(type))
return (R)obj;
if (!di.getAllowSubclasses() && !type.isAssignableFrom(obj.getClass()))
throw new MessageParserException(MessageParserException.BAD_CLASS, String.format("(got %s, expected %s or subclass for %s)",
obj.getClass().getSimpleName(), type.getSimpleName(), di.getName()), -1, currentClass);
return (R)obj;
}
@Override
public Map<String, Object> readJson(ObjectReference di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof Map<?,?>) {
return (Map<String, Object>)z; // no check possible due to Java type erasure
}
// reverse mapping: BonaPortable => Map<>. It would be weird to encounter here, but hey....
if (z instanceof BonaCustom) {
return MapComposer.marshal((BonaCustom)z);
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public List<Object> readArray(ObjectReference di) throws MessageParserException {
Object z = get(di);
if (z == null)
return null;
if (z instanceof List<?>) {
return (List<Object>)z; // no check possible due to Java type erasure
}
if (z instanceof Object []) {
return new ArrayList<Object>(Arrays.asList((Object [])z));
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public Object readElement(ObjectReference di) throws MessageParserException {
return get(di);
}
@Override
public int parseMapStart(FieldDefinition di) throws MessageParserException {
if (iter != null)
throw new RuntimeException("Nested collection should not happen, but occurred for Map " + currentClass + "." + di.getName());
Object z = getNext(di);
if (z == null) {
if (di.getIsAggregateRequired())
throw err(MessageParserException.NULL_COLLECTION_NOT_ALLOWED, di);
return -1;
}
if (z instanceof Map<?,?>) {
Map<Object,Object> m = (Map<Object,Object>)z;
iter = new MapIterator<Object>(m);
return m.size();
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public int parseArrayStart(FieldDefinition di, int sizeOfElement) throws MessageParserException {
if (iter != null)
throw new RuntimeException("Nested collection should not happen, but occurred for Collection " + currentClass + "." + di.getName());
Object z = getNext(di);
if (z == null) {
if (di.getIsAggregateRequired())
throw err(MessageParserException.NULL_COLLECTION_NOT_ALLOWED, di);
return -1;
}
if (z instanceof List<?>) {
List<Object> l = (List<Object>)z;
iter = l.iterator();
return l.size();
}
if (z instanceof Set<?>) {
Set<Object> s = (Set<Object>)z;
iter = s.iterator();
return s.size();
}
if (z instanceof Object []) {
Object [] a = (Object [])z;
iter = Arrays.asList(a).iterator();
return a.length;
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
@Override
public void parseArrayEnd() throws MessageParserException {
if (iter == null)
throw new RuntimeException("Cannot end collection when none has been started.");
if (iter.hasNext())
throw new RuntimeException("Should not end collection when iterator has not been exhausted.");
iter = null;
}
@Override
public BonaPortable readRecord() throws MessageParserException {
BonaPortable obj = allocObject(map, null);
obj.deserialize(this);
return obj;
}
@Override
public List<BonaPortable> readTransmission() throws MessageParserException {
throw new UnsupportedOperationException();
}
@Override
public void eatParentSeparator() throws MessageParserException {
}
@Override
public <T extends AbstractXEnumBase<T>> T readXEnum(XEnumDataItem di, XEnumFactory<T> factory) throws MessageParserException {
Object z = getNext(di);
if (z == null) {
T result = factory.getNullToken();
if (result == null && di.getIsRequired())
throw err(MessageParserException.EMPTY_BUT_REQUIRED_FIELD, di);
return result;
}
if (z instanceof String) {
final T value = factory.getByToken((String)z);
if (value == null)
throw err(MessageParserException.INVALID_ENUM_TOKEN, di);
return value;
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, di);
}
// special handling of enums: access the original name, not the one with $token suffix: take care when di and when edi is used!
@Override
public Integer readEnum(EnumDataItem edi, BasicNumericElementaryDataItem di) throws MessageParserException {
Object z = get(edi);
if (z == null)
return null;
if (z instanceof Number) {
return Integer.valueOf(((Number)z).intValue());
}
if (z instanceof String) {
return Integer.valueOf(spu.readPrimitiveInteger(di, (String)z));
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, edi);
}
// special handling of enums: access the original name, not the one with $token suffix
@Override
public String readEnum(EnumDataItem edi, AlphanumericElementaryDataItem di) throws MessageParserException {
Object z = get(edi);
if (z == null)
return null;
if (z instanceof String) {
return spu.readString(di, (String)z);
}
throw err(MessageParserException.UNSUPPORTED_CONVERSION, edi);
}
}