/* * 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 com.google.gson.FieldNamingStrategy; import com.google.gson.Gson; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import com.google.gson.ReflectionAccessFilter; import com.google.gson.ReflectionAccessFilter.FilterResult; 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.internal.ReflectionAccessFilterHelper; import com.google.gson.internal.reflect.ReflectionHelper; 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 java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 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; private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory; private final List reflectionFilters; public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, FieldNamingStrategy fieldNamingPolicy, Excluder excluder, JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory, List reflectionFilters) { this.constructorConstructor = constructorConstructor; this.fieldNamingPolicy = fieldNamingPolicy; this.excluder = excluder; this.jsonAdapterFactory = jsonAdapterFactory; this.reflectionFilters = reflectionFilters; } 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 getFieldNames(Field f) { SerializedName annotation = f.getAnnotation(SerializedName.class); if (annotation == null) { String name = fieldNamingPolicy.translateName(f); return Collections.singletonList(name); } String serializedName = annotation.value(); String[] alternates = annotation.alternate(); if (alternates.length == 0) { return Collections.singletonList(serializedName); } List fieldNames = new ArrayList<>(alternates.length + 1); fieldNames.add(serializedName); for (String alternate : alternates) { fieldNames.add(alternate); } return fieldNames; } @Override public TypeAdapter create(Gson gson, final TypeToken type) { Class raw = type.getRawType(); if (!Object.class.isAssignableFrom(raw)) { return null; // it's a primitive! } FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw); if (filterResult == FilterResult.BLOCK_ALL) { throw new JsonIOException("ReflectionAccessFilter does not permit using reflection for " + raw + ". Register a TypeAdapter for this type or adjust the access filter."); } boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE; ObjectConstructor constructor = constructorConstructor.get(type); return new Adapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible)); } private static void checkAccessible(Object object, Field field) { if (!ReflectionAccessFilterHelper.canAccess(field, Modifier.isStatic(field.getModifiers()) ? null : object)) { throw new JsonIOException("Field '" + field.getDeclaringClass().getName() + "#" + field.getName() + "' is not accessible and ReflectionAccessFilter does not " + "permit making it accessible. Register a TypeAdapter for the declaring type " + "or adjust the access filter."); } } private ReflectiveTypeAdapterFactory.BoundField createBoundField( final Gson context, final Field field, final String name, final TypeToken fieldType, boolean serialize, boolean deserialize, final boolean blockInaccessible) { final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType()); // special casing primitives here saves ~5% on Android... JsonAdapter annotation = field.getAnnotation(JsonAdapter.class); TypeAdapter mapped = null; if (annotation != null) { mapped = jsonAdapterFactory.getTypeAdapter( constructorConstructor, context, fieldType, annotation); } final boolean jsonAdapterPresent = mapped != null; if (mapped == null) mapped = context.getAdapter(fieldType); final TypeAdapter typeAdapter = mapped; return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) { @SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree @Override void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException { if (!serialized) return; if (blockInaccessible) { checkAccessible(value, field); } Object fieldValue = field.get(value); if (fieldValue == value) { // avoid direct recursion return; } writer.name(name); TypeAdapter t = jsonAdapterPresent ? typeAdapter : new TypeAdapterRuntimeTypeWrapper(context, 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) { if (blockInaccessible) { checkAccessible(value, field); } field.set(value, fieldValue); } } }; } private Map getBoundFields(Gson context, TypeToken type, Class raw, boolean blockInaccessible) { Map result = new LinkedHashMap<>(); if (raw.isInterface()) { return result; } Type declaredType = type.getType(); Class originalRaw = raw; while (raw != Object.class) { Field[] fields = raw.getDeclaredFields(); // For inherited fields, check if access to their declaring class is allowed if (raw != originalRaw && fields.length > 0) { FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw); if (filterResult == FilterResult.BLOCK_ALL) { throw new JsonIOException("ReflectionAccessFilter does not permit using reflection for " + raw + " (supertype of " + originalRaw + "). Register a TypeAdapter for this type " + "or adjust the access filter."); } blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE; } for (Field field : fields) { boolean serialize = excludeField(field, true); boolean deserialize = excludeField(field, false); if (!serialize && !deserialize) { continue; } // If blockInaccessible, skip and perform access check later if (!blockInaccessible) { ReflectionHelper.makeAccessible(field); } Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType()); List fieldNames = getFieldNames(field); BoundField previous = null; for (int i = 0, size = fieldNames.size(); i < 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, blockInaccessible); 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 void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException; abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException; } public static final class Adapter extends TypeAdapter { private final ObjectConstructor constructor; private final Map boundFields; Adapter(ObjectConstructor constructor, Map 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 ReflectionHelper.createExceptionForUnexpectedIllegalAccess(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()) { boundField.write(out, value); } } catch (IllegalAccessException e) { throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e); } out.endObject(); } } }