/* 
 | 
 * 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 cn.emay.sdk.util.json.gson.internal.bind; 
 | 
  
 | 
import static cn.emay.sdk.util.json.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 cn.emay.sdk.util.json.gson.FieldNamingStrategy; 
 | 
import cn.emay.sdk.util.json.gson.Gson; 
 | 
import cn.emay.sdk.util.json.gson.JsonSyntaxException; 
 | 
import cn.emay.sdk.util.json.gson.TypeAdapter; 
 | 
import cn.emay.sdk.util.json.gson.TypeAdapterFactory; 
 | 
import cn.emay.sdk.util.json.gson.annotations.JsonAdapter; 
 | 
import cn.emay.sdk.util.json.gson.annotations.SerializedName; 
 | 
import cn.emay.sdk.util.json.gson.internal.$Gson$Types; 
 | 
import cn.emay.sdk.util.json.gson.internal.ConstructorConstructor; 
 | 
import cn.emay.sdk.util.json.gson.internal.Excluder; 
 | 
import cn.emay.sdk.util.json.gson.internal.ObjectConstructor; 
 | 
import cn.emay.sdk.util.json.gson.internal.Primitives; 
 | 
import cn.emay.sdk.util.json.gson.reflect.TypeToken; 
 | 
import cn.emay.sdk.util.json.gson.stream.JsonReader; 
 | 
import cn.emay.sdk.util.json.gson.stream.JsonToken; 
 | 
import cn.emay.sdk.util.json.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; 
 | 
    } 
 | 
  
 | 
    @Override 
 | 
    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); 
 | 
                } 
 | 
            } 
 | 
  
 | 
            @Override 
 | 
            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 
 | 
            } 
 | 
        }; 
 | 
    } 
 | 
  
 | 
    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; 
 | 
  
 | 
        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(e); 
 | 
            } 
 | 
            out.endObject(); 
 | 
        } 
 | 
    } 
 | 
} 
 |