package com.ctriposs.baiji.generic;
import com.ctriposs.baiji.exception.BaijiRuntimeException;
import com.ctriposs.baiji.io.Encoder;
import com.ctriposs.baiji.schema.EnumSchema;
import com.ctriposs.baiji.schema.RecordSchema;
import com.ctriposs.baiji.schema.Schema;
import com.ctriposs.baiji.schema.SchemaType;
import java.io.IOException;
import java.util.Map;
/**
* {@link DatumWriter} for generic Java objects.
*/
public class GenericDatumWriter<D> extends PreresolvingDatumWriter<D> {
public GenericDatumWriter(Schema schema) {
super(schema, new GenericArrayAccess(), new DefaultMapAccess());
}
@Override
protected void writeRecordFields(Object recordObj, RecordFieldWriter[] writers, Encoder encoder)
throws IOException {
GenericRecord record = (GenericRecord) recordObj;
for (RecordFieldWriter writer : writers) {
writer._fieldWriter.write(record.get(writer._field.getName()), encoder);
}
}
@Override
protected ItemWriter resolveEnum(EnumSchema es) {
return new EnumItemWriter(es);
}
private static class EnumItemWriter implements ItemWriter {
private final EnumSchema _schema;
public EnumItemWriter(EnumSchema schema) {
_schema = schema;
}
@Override
public void write(Object value, Encoder encoder) throws IOException {
if (value == null || !(value instanceof GenericEnum)
|| !(((GenericEnum) value).getSchema().equals(_schema))) {
throw typeMismatch(value, "enum", "GenericEnum");
}
encoder.writeEnum(_schema.ordinal(((GenericEnum) value).getValue()));
}
}
/*
* TODO: This method of determining the Union branch has problems. If the data is Map<String, Object>
* if there are two branches one with record schema and the other with map, it choose the first one. Similarly if
* the data is byte[] and there are fixed and bytes schemas as branches, it choose the first one that matches.
* Also it does not recognize the arrays of primitive types.
*/
@Override
protected boolean unionBranchMatches(Schema sc, Object obj) {
if (obj == null && sc.getType() != SchemaType.NULL) {
return false;
}
switch (sc.getType()) {
case NULL:
return obj == null;
case BOOLEAN:
return obj instanceof Boolean;
case INT:
return obj instanceof Integer;
case LONG:
return obj instanceof Long;
case FLOAT:
return obj instanceof Float;
case DOUBLE:
return obj instanceof Double;
case BYTES:
return obj instanceof byte[];
case STRING:
return obj instanceof String;
case RECORD:
return obj instanceof GenericRecord
&& ((GenericRecord) obj).getSchema().getSchemaName().equals(((RecordSchema) sc).getSchemaName());
case ENUM:
return obj instanceof GenericEnum
&& ((GenericEnum) obj).getSchema().getSchemaName().equals(((EnumSchema) sc).getSchemaName());
case ARRAY:
return obj.getClass().isArray() && !(obj instanceof byte[]);
case MAP:
return obj instanceof Map<?, ?>;
case UNION:
return false; // Union directly within another union not allowed!
default:
throw new BaijiRuntimeException("Unknown schema type: " + sc.getType());
}
}
private static class GenericArrayAccess implements ArrayAccess {
@Override
public void ensureArrayObject(Object value) {
if (value == null || !(value.getClass().isArray())) {
throw typeMismatch(value, "array", "Array");
}
}
@Override
public long getArrayLength(Object value) {
return ((Object[]) value).length;
}
public void writeArrayValues(Object array, ItemWriter valueWriter, Encoder encoder)
throws IOException {
Object[] arrayInstance = (Object[]) array;
for (int i = 0; i < arrayInstance.length; i++) {
encoder.startItem();
valueWriter.write(arrayInstance[i], encoder);
}
}
}
}