Skip to content

Instantly share code, notes, and snippets.

@mescortes
Last active January 17, 2017 19:09
Show Gist options
  • Save mescortes/fe25951aadeddfc4ac062f75a6db9464 to your computer and use it in GitHub Desktop.
Save mescortes/fe25951aadeddfc4ac062f75a6db9464 to your computer and use it in GitHub Desktop.
/*
* Copyright 2013 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.indigofair.wire;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import okio.ByteString;
/**
* A {@link com.google.gson.TypeAdapter} that may be used to serialize and deserialize
* {@link ByteString} values using the GSON Json library. The byte data is serialized
* in Base64 format.
*/
class ByteStringTypeAdapter extends TypeAdapter<ByteString> {
@Override public void write(JsonWriter out, ByteString value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(value.base64());
}
}
@Override public ByteString read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return ByteString.decodeBase64(in.nextString());
}
}
/*
* Copyright 2013 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.indigofair.wire;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.squareup.wire.Message;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;
import okio.ByteString;
@SuppressWarnings("unchecked")
public class MessageTypeAdapter<M extends Message<M, B>, B extends Message.Builder<M, B>>
extends TypeAdapter<M> {
private final Map<String, Binding> bindings;
private final Class<B> builderClass;
public MessageTypeAdapter(Map<String, Binding> bindings, Class<?> builderClass) {
this.bindings = bindings;
this.builderClass = (Class<B>) builderClass;
}
@Override public void write(JsonWriter out, M message) throws IOException {
out.beginObject();
try {
for (Binding binding : bindings.values()) {
Object value = binding.messageField.get(message);
if (value == null) continue;
out.name(binding.externalName);
((TypeAdapter<Object>) binding.typeAdapter).write(out, value);
}
} catch (IllegalAccessException e) {
throw new AssertionError();
}
out.endObject();
}
@Override public M read(JsonReader in) throws IOException {
try {
B builder = builderClass.newInstance();
in.beginObject();
while (in.peek() != JsonToken.END_OBJECT) {
String externalName = in.nextName();
Binding binding = bindings.get(externalName);
if (binding == null) {
in.skipValue();
} else {
Object value = binding.typeAdapter.read(in);
binding.builderField.set(builder, value);
}
}
in.endObject();
return builder.build();
} catch (IllegalAccessException | InstantiationException e) {
throw new AssertionError();
}
}
static final class Binding {
final String internalName;
final String externalName;
final Field messageField;
final Field builderField;
final TypeAdapter<?> typeAdapter;
public Binding(String internalName, String externalName, Field messageField, Field builderField,
TypeAdapter<?> typeAdapter) {
this.internalName = internalName;
this.externalName = externalName;
this.messageField = messageField;
this.builderField = builderField;
this.typeAdapter = typeAdapter;
}
}
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType().equals(ByteString.class)) {
return (TypeAdapter<T>) new ByteStringTypeAdapter();
}
if (Message.class.isAssignableFrom(type.getRawType())) {
FieldNamingStrategy fieldNamingStrategy = gson.fieldNamingStrategy();
try {
Class<?> builder = Class.forName(type.getRawType().getName() + "$Builder");
Map<String, Binding> bindings = new LinkedHashMap<>();
for (Field field : type.getRawType().getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
Field builderField = builder.getDeclaredField(field.getName());
String internalName = field.getName();
String externalName = fieldNamingStrategy.translateName(field);
bindings.put(externalName, new Binding(internalName, externalName, field, builderField,
gson.getAdapter(TypeToken.get(field.getGenericType()))));
}
return (TypeAdapter<T>) new MessageTypeAdapter<>(bindings, builder).nullSafe();
} catch (ClassNotFoundException | NoSuchFieldException e) {
throw new AssertionError();
}
}
return null;
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment