diff options
Diffstat (limited to 'gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java')
-rw-r--r-- | gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java new file mode 100644 index 00000000..3957c369 --- /dev/null +++ b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2011 Google 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.google.gson.internal.bind; + +import static com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory.getTypeAdapter; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.google.gson.FieldNamingStrategy; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.internal.$Gson$Types; +import com.google.gson.internal.ConstructorConstructor; +import com.google.gson.internal.Excluder; +import com.google.gson.internal.ObjectConstructor; +import com.google.gson.internal.Primitives; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * Type adapter that reflects over the fields and methods of a class. + */ +public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { + private final ConstructorConstructor constructorConstructor; + private final FieldNamingStrategy fieldNamingPolicy; + private final Excluder excluder; + + public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, + FieldNamingStrategy fieldNamingPolicy, Excluder excluder) { + this.constructorConstructor = constructorConstructor; + this.fieldNamingPolicy = fieldNamingPolicy; + this.excluder = excluder; + } + + public boolean excludeField(Field f, boolean serialize) { + return excludeField(f, serialize, excluder); + } + + static boolean excludeField(Field f, boolean serialize, Excluder excluder) { + return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize); + } + + /** first element holds the default name */ + private List<String> getFieldNames(Field f) { + return getFieldName(fieldNamingPolicy, f); + } + + /** first element holds the default name */ + static List<String> getFieldName(FieldNamingStrategy fieldNamingPolicy, Field f) { + SerializedName serializedName = f.getAnnotation(SerializedName.class); + List<String> fieldNames = new LinkedList<String>(); + if (serializedName == null) { + fieldNames.add(fieldNamingPolicy.translateName(f)); + } else { + fieldNames.add(serializedName.value()); + for (String alternate : serializedName.alternate()) { + fieldNames.add(alternate); + } + } + return fieldNames; + } + + public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) { + Class<? super T> raw = type.getRawType(); + + if (!Object.class.isAssignableFrom(raw)) { + return null; // it's a primitive! + } + + ObjectConstructor<T> constructor = constructorConstructor.get(type); + return new Adapter<T>(constructor, getBoundFields(gson, type, raw)); + } + + private ReflectiveTypeAdapterFactory.BoundField createBoundField( + final Gson context, final Field field, final String name, + final TypeToken<?> fieldType, boolean serialize, boolean deserialize) { + final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType()); + // special casing primitives here saves ~5% on Android... + return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) { + final TypeAdapter<?> typeAdapter = getFieldAdapter(context, field, fieldType); + @SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree + @Override void write(JsonWriter writer, Object value) + throws IOException, IllegalAccessException { + Object fieldValue = field.get(value); + TypeAdapter t = + new TypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType()); + t.write(writer, fieldValue); + } + @Override void read(JsonReader reader, Object value) + throws IOException, IllegalAccessException { + Object fieldValue = typeAdapter.read(reader); + if (fieldValue != null || !isPrimitive) { + field.set(value, fieldValue); + } + } + public boolean writeField(Object value) throws IOException, IllegalAccessException { + if (!serialized) return false; + Object fieldValue = field.get(value); + return fieldValue != value; // avoid recursion for example for Throwable.cause + } + }; + } + + private TypeAdapter<?> getFieldAdapter(Gson gson, Field field, TypeToken<?> fieldType) { + JsonAdapter annotation = field.getAnnotation(JsonAdapter.class); + if (annotation != null) { + TypeAdapter<?> adapter = getTypeAdapter(constructorConstructor, gson, fieldType, annotation); + if (adapter != null) return adapter; + } + return gson.getAdapter(fieldType); + } + + private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) { + Map<String, BoundField> result = new LinkedHashMap<String, BoundField>(); + if (raw.isInterface()) { + return result; + } + + Type declaredType = type.getType(); + while (raw != Object.class) { + Field[] fields = raw.getDeclaredFields(); + for (Field field : fields) { + boolean serialize = excludeField(field, true); + boolean deserialize = excludeField(field, false); + if (!serialize && !deserialize) { + continue; + } + field.setAccessible(true); + Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType()); + List<String> fieldNames = getFieldNames(field); + BoundField previous = null; + for (int i = 0; i < fieldNames.size(); ++i) { + String name = fieldNames.get(i); + if (i != 0) serialize = false; // only serialize the default name + BoundField boundField = createBoundField(context, field, name, + TypeToken.get(fieldType), serialize, deserialize); + BoundField replaced = result.put(name, boundField); + if (previous == null) previous = replaced; + } + if (previous != null) { + throw new IllegalArgumentException(declaredType + + " declares multiple JSON fields named " + previous.name); + } + } + type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass())); + raw = type.getRawType(); + } + return result; + } + + static abstract class BoundField { + final String name; + final boolean serialized; + final boolean deserialized; + + protected BoundField(String name, boolean serialized, boolean deserialized) { + this.name = name; + this.serialized = serialized; + this.deserialized = deserialized; + } + abstract boolean writeField(Object value) throws IOException, IllegalAccessException; + abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException; + abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException; + } + + public static final class Adapter<T> extends TypeAdapter<T> { + private final ObjectConstructor<T> constructor; + private final Map<String, BoundField> boundFields; + + private Adapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) { + this.constructor = constructor; + this.boundFields = boundFields; + } + + @Override public T read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + + T instance = constructor.construct(); + + try { + in.beginObject(); + while (in.hasNext()) { + String name = in.nextName(); + BoundField field = boundFields.get(name); + if (field == null || !field.deserialized) { + in.skipValue(); + } else { + field.read(in, instance); + } + } + } catch (IllegalStateException e) { + throw new JsonSyntaxException(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + in.endObject(); + return instance; + } + + @Override public void write(JsonWriter out, T value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + + out.beginObject(); + try { + for (BoundField boundField : boundFields.values()) { + if (boundField.writeField(value)) { + out.name(boundField.name); + boundField.write(out, value); + } + } + } catch (IllegalAccessException e) { + throw new AssertionError(); + } + out.endObject(); + } + } +} |