/* * 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.smartandroid.sa.json.internal.bind; import java.io.IOException; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.LinkedHashMap; import java.util.Map; import com.smartandroid.sa.json.internal.$Gson$Types; import com.smartandroid.sa.json.internal.UnsafeAllocator; import com.smartandroid.sa.json.reflect.TypeToken; import com.smartandroid.sa.json.stream.JsonReader; import com.smartandroid.sa.json.stream.JsonToken; import com.smartandroid.sa.json.stream.JsonWriter; /** * Adapts the fields of an object to the properties of a JSON object. */ public final class ReflectiveTypeAdapter<T> extends TypeAdapter<T> { public static final Factory FACTORY = new FactoryImpl(); private static final UnsafeAllocator unsafeAllocator = UnsafeAllocator .create(); private final Class<? super T> rawType; private final Constructor<? super T> constructor; private final Map<String, BoundField> map; private final BoundField[] boundFields; ReflectiveTypeAdapter(Class<? super T> rawType, Constructor<? super T> constructor, Map<String, BoundField> map) { this.rawType = rawType; this.constructor = constructor; this.map = map; this.boundFields = map.values().toArray(new BoundField[map.size()]); } @SuppressWarnings("unchecked") // the '? super T' is a raw T (the only kind we can construct) public T read(JsonReader reader) throws IOException { if (reader.peek() == JsonToken.NULL) { reader.nextNull(); // TODO: does this belong here? return null; } T instance; if (constructor != null) { instance = (T) Reflection.newInstance(constructor); } else { try { instance = (T) unsafeAllocator.newInstance(rawType); } catch (Exception e) { throw new RuntimeException( ("Unable to invoke no-args constructor for " + rawType.getName() + ". Register an InstanceCreator with Gson for this type may fix this problem."), e); } } // TODO: null out the other fields? reader.beginObject(); try { while (reader.hasNext()) { String name = reader.nextName(); BoundField field = map.get(name); if (field == null || !field.deserialized) { // TODO: define a better policy reader.skipValue(); } else { field.read(reader, instance); } } } catch (IllegalAccessException e) { throw new AssertionError(); } reader.endObject(); return instance; } public void write(JsonWriter writer, T value) throws IOException { if (value == null) { writer.nullValue(); // TODO: better policy here? return; } writer.beginObject(); try { for (BoundField boundField : boundFields) { if (boundField.serialized) { writer.name(boundField.name); boundField.write(writer, value); } } } catch (IllegalAccessException e) { throw new AssertionError(); } writer.endObject(); } static BoundField createBoundField(final MiniGson context, final Field field, final String name, final TypeToken<?> fieldType, boolean serialize, boolean deserialize) { // special casing primitives here saves ~5% on Android... return new BoundField(name, serialize, deserialize) { final TypeAdapter<?> typeAdapter = context.getAdapter(fieldType); @SuppressWarnings("unchecked") // 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); field.set(value, fieldValue); } }; } public static class FactoryImpl implements Factory { public boolean serializeField(Class<?> declaringClazz, Field f, Type declaredType) { return true; } public boolean deserializeField(Class<?> declaringClazz, Field f, Type declaredType) { return true; } public String getFieldName(Class<?> declaringClazz, Field f, Type declaredType) { return f.getName(); } public <T> TypeAdapter<T> create(MiniGson context, TypeToken<T> type) { Class<? super T> raw = type.getRawType(); if (!Object.class.isAssignableFrom(raw)) { return null; // it's a primitive! } Constructor<? super T> constructor = null; try { constructor = raw.getDeclaredConstructor(); } catch (NoSuchMethodException ignored) { } return new ReflectiveTypeAdapter<T>(raw, constructor, getBoundFields(context, type, raw)); } private Map<String, BoundField> getBoundFields(MiniGson 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(); AccessibleObject.setAccessible(fields, true); for (Field field : fields) { boolean serialize = serializeField(raw, field, declaredType); boolean deserialize = deserializeField(raw, field, declaredType); if (serialize || deserialize) { Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType()); BoundField boundField = createBoundField(context, field, getFieldName(raw, field, declaredType), TypeToken.get(fieldType), serialize, deserialize); BoundField previous = result.put(boundField.name, boundField); 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; } }