package protobuf.codec.json;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Map;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParser;
import protobuf.codec.AbstractCodec;
import protobuf.codec.Codec;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
import com.google.protobuf.UnknownFieldSet;
/**
* Json-protobuf serializer/deserializer.Relies on jackson as the underlying parser.
* Supports {@link UnknownFieldSet}, extension field names prefixed with {@link Feature#EXTENSION_FIELD_NAME_PREFIX} and a "-"/
* ex: "extension-name" idenfies an extension file by type name
* In case the {@link Feature#UNKNOWN_FIELD_ELEM_NAME} is enabled ( this feature is enabled by default) each json object in
* could contain a field by name "unknownfields" or whatever value the {@value Feature.UNKNOWN_FIELD_ELEM_NAME} is
* set to. The value of this field is hex encoded byte string.
*
* @author sijuv
*
*/
public class JsonCodec extends AbstractCodec {
public JsonCodec() {
}
/**
* Fills in a message from the data from the stream, The stream is not closed once the message is read in, the extn field names
* need to be boxed. {@link UnknownFieldSet} passed is passed in the field defined by {@value Feature.UNKNOWN_FIELD_ELEM_NAME}.
* The {@link UnknownFieldSet} provided is parsed in only if the {@link Feature#SUPPORT_UNKNOWN_FIELDS} is enabled.
* The value passed in as unknown field should be corresponding hex encoded byte[]
* The {@link Feature#EXTENSION_FIELD_NAME_PREFIX} controlls how the extension field names need to be.
* @param builder the message builder
* @param reader the input stream,
* @param extnRegistry the extension registry to use
* @see Codec#toMessage(Class, Reader)
* @see AbstractCodec#mergeUnknownFieldsFromString(Builder, ExtensionRegistry, String)
*/
@Override
protected Message readFromStream(Builder builder, Reader reader, ExtensionRegistry extnRegistry) throws IOException {
JsonFactory jsonFactory = new JsonFactory();
JsonParser parser = jsonFactory.createJsonParser(reader);
return JacksonJsonReader.parse(builder, parser, extnRegistry, getAllFeaturesSet());
}
/**
* Writes out the messages as a json object the provided stream.
* The stream is not closed once the message is written out. {@link UnknownFieldSet} is serialized out as a hex byte string.
* @param message the provided protobuf message
* @param writer the output stream writer onto which the message is written to.
* @see Codec#toMessage(Class, InputStream)
* Sample response:<br>
* {"id":1,"name":"elton john","extension-bar":1,"extension-id":24,"extension-place":"london"}
* bar and place are extension fields.
* @see AbstractCodec#encodeUnknownFieldsToString(UnknownFieldSet)
*
*/
@Override
protected void writeToStream(Message message, Writer writer) throws IOException {
JsonFactory jsonFactory = new JsonFactory();
JsonGenerator generator = jsonFactory.createJsonGenerator(writer);
JacksonJsonWriter.generateJSONFields(message, generator, getAllFeaturesSet());
generator.close();
}
@Override
public void validateAndSetFeature(Feature feature, Object value) {
switch (feature) {
case SUPPORT_UNKNOWN_FIELDS:
case PRETTY_PRINT:
case CLOSE_STREAM:
if (!(Boolean.TRUE.equals(value) || Boolean.FALSE.equals(value))) {
throw new IllegalArgumentException(String.format("Unsupported value [%s] for feature [%s]", value, feature));
}
break;
case UNKNOWN_FIELD_ELEM_NAME:
case EXTENSION_FIELD_NAME_PREFIX:
if (value == null || !(String.class).isAssignableFrom(value.getClass())
|| ((String) value).trim().equals("")) {
throw new IllegalArgumentException(String.format("Feature [%s] expected to be a non null string", feature));
}
break;
case FIELD_NAME_READ_SUBSTITUTES:
case FIELD_NAME_WRITE_SUBSTITUTES:
case STRIP_FIELD_NAME_UNDERSCORES:
//noop Already handled
break;
default:
throw new IllegalArgumentException(String.format("Unsupported feature [%s]", feature));
}
}
}