package io.fathom.cloud.protobuf.mapper;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.stream.JsonReader;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.Type;
import com.google.protobuf.Message;
public class MessageMapper {
final List<FieldMapper> mappers;
final Map<String, FieldMapper> fields;
final Descriptor descriptor;
public MessageMapper(Descriptor descriptor, List<FieldMapper> mappers) {
this.descriptor = descriptor;
this.mappers = mappers;
this.fields = Maps.newHashMap();
}
void build() {
for (FieldMapper field : mappers) {
fields.put(field.jsonName, field);
}
}
public void write(Message src, ProtobufWriter json) throws IOException {
json.beginObject();
for (FieldMapper mapper : mappers) {
mapper.write(src, json);
}
json.endObject();
}
public void read(Message.Builder dest, JsonReader json) throws IOException {
json.beginObject();
while (true) {
switch (json.peek()) {
case NAME:
String name = json.nextName();
FieldMapper field = fields.get(name);
if (field == null) {
// TODO: Support ignoring fields?
throw new IOException("Found unhandled field: " + name);
}
field.read(dest, json);
break;
case END_OBJECT:
json.endObject();
return;
default:
throw new UnsupportedOperationException();
}
}
}
static Map<Descriptor, MessageMapper> descriptors = Maps.newHashMap();
static synchronized MessageMapper getMessageMapper(Message message) {
Descriptor descriptor = message.getDescriptorForType();
return getMessageMapper(descriptor);
}
public static synchronized MessageMapper getMessageMapper(Descriptor descriptor) {
MessageMapper messageMapper = descriptors.get(descriptor);
if (messageMapper == null) {
List<FieldMapper> mappers = Lists.newArrayList();
// We add to map before it's finished and then build the object up.
// A bit naughty, but needed for circular messages
messageMapper = new MessageMapper(descriptor, mappers);
descriptors.put(descriptor, messageMapper);
for (FieldDescriptor field : descriptor.getFields()) {
FieldMapper fieldMapper = buildMapper(field);
mappers.add(fieldMapper);
}
messageMapper.build();
}
return messageMapper;
}
private static FieldMapper buildMapper(FieldDescriptor field) {
if (field.isRepeated()) {
FieldMapper mapper = buildFieldMapper0(field, field.getType());
return new RepeatedFieldMapper(field, mapper);
} else {
return buildFieldMapper0(field, field.getType());
}
}
private static FieldMapper buildFieldMapper0(FieldDescriptor field, Type fieldType) {
switch (fieldType) {
case MESSAGE: {
Descriptor fieldDescriptor = field.getMessageType();
MessageMapper mapper = getMessageMapper(fieldDescriptor);
return new MessageFieldMapper(field, mapper);
}
case BOOL:
return new BooleanFieldMapper(field);
case STRING:
return new StringFieldMapper(field);
case INT32:
case INT64:
case FIXED32:
case FIXED64:
case UINT32:
case UINT64:
case SFIXED32:
case SFIXED64:
case SINT32:
case SINT64:
return new IntegerFieldMapper(field);
case DOUBLE:
case FLOAT:
return new FloatFieldMapper(field);
case BYTES:
return new BytesFieldMapper(field);
case ENUM:
return new EnumFieldMapper(field);
default:
throw new UnsupportedOperationException("Unhandled field type: " + fieldType);
}
}
}