1 /*
2  * Copyright (C) 2011 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package com.google.gson.internal.bind;
18
19 import com.google.gson.FieldNamingStrategy;
20 import com.google.gson.Gson;
21 import com.google.gson.JsonSyntaxException;
22 import com.google.gson.TypeAdapter;
23 import com.google.gson.TypeAdapterFactory;
24 import com.google.gson.annotations.JsonAdapter;
25 import com.google.gson.annotations.SerializedName;
26 import com.google.gson.internal.$Gson$Types;
27 import com.google.gson.internal.ConstructorConstructor;
28 import com.google.gson.internal.Excluder;
29 import com.google.gson.internal.ObjectConstructor;
30 import com.google.gson.internal.Primitives;
31 import com.google.gson.internal.reflect.ReflectionAccessor;
32 import com.google.gson.reflect.TypeToken;
33 import com.google.gson.stream.JsonReader;
34 import com.google.gson.stream.JsonToken;
35 import com.google.gson.stream.JsonWriter;
36 import java.io.IOException;
37 import java.lang.reflect.Field;
38 import java.lang.reflect.Type;
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.LinkedHashMap;
42 import java.util.List;
43 import java.util.Map;
44
45 /**
46  * Type adapter that reflects over the fields and methods of a class.
47  */

48 public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
49   private final ConstructorConstructor constructorConstructor;
50   private final FieldNamingStrategy fieldNamingPolicy;
51   private final Excluder excluder;
52   private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
53   private final ReflectionAccessor accessor = ReflectionAccessor.getInstance();
54
55   public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
56       FieldNamingStrategy fieldNamingPolicy, Excluder excluder,
57       JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory) {
58     this.constructorConstructor = constructorConstructor;
59     this.fieldNamingPolicy = fieldNamingPolicy;
60     this.excluder = excluder;
61     this.jsonAdapterFactory = jsonAdapterFactory;
62   }
63
64   public boolean excludeField(Field f, boolean serialize) {
65     return excludeField(f, serialize, excluder);
66   }
67
68   static boolean excludeField(Field f, boolean serialize, Excluder excluder) {
69     return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
70   }
71
72   /** first element holds the default name */
73   private List<String> getFieldNames(Field f) {
74     SerializedName annotation = f.getAnnotation(SerializedName.class);
75     if (annotation == null) {
76       String name = fieldNamingPolicy.translateName(f);
77       return Collections.singletonList(name);
78     }
79
80     String serializedName = annotation.value();
81     String[] alternates = annotation.alternate();
82     if (alternates.length == 0) {
83       return Collections.singletonList(serializedName);
84     }
85
86     List<String> fieldNames = new ArrayList<String>(alternates.length + 1);
87     fieldNames.add(serializedName);
88     for (String alternate : alternates) {
89       fieldNames.add(alternate);
90     }
91     return fieldNames;
92   }
93
94   @Override public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
95     Class<? super T> raw = type.getRawType();
96
97     if (!Object.class.isAssignableFrom(raw)) {
98       return null// it's a primitive!
99     }
100
101     ObjectConstructor<T> constructor = constructorConstructor.get(type);
102     return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
103   }
104
105   private ReflectiveTypeAdapterFactory.BoundField createBoundField(
106       final Gson context, final Field field, final String name,
107       final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
108     final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
109     // special casing primitives here saves ~5% on Android...
110     JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
111     TypeAdapter<?> mapped = null;
112     if (annotation != null) {
113       mapped = jsonAdapterFactory.getTypeAdapter(
114           constructorConstructor, context, fieldType, annotation);
115     }
116     final boolean jsonAdapterPresent = mapped != null;
117     if (mapped == null) mapped = context.getAdapter(fieldType);
118
119     final TypeAdapter<?> typeAdapter = mapped;
120     return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
121       @SuppressWarnings({"unchecked""rawtypes"}) // the type adapter and field type always agree
122       @Override void write(JsonWriter writer, Object value)
123           throws IOException, IllegalAccessException {
124         Object fieldValue = field.get(value);
125         TypeAdapter t = jsonAdapterPresent ? typeAdapter
126             : new TypeAdapterRuntimeTypeWrapper(context, typeAdapter, fieldType.getType());
127         t.write(writer, fieldValue);
128       }
129       @Override void read(JsonReader reader, Object value)
130           throws IOException, IllegalAccessException {
131         Object fieldValue = typeAdapter.read(reader);
132         if (fieldValue != null || !isPrimitive) {
133           field.set(value, fieldValue);
134         }
135       }
136       @Override public boolean writeField(Object value) throws IOException, IllegalAccessException {
137         if (!serialized) return false;
138         Object fieldValue = field.get(value);
139         return fieldValue != value; // avoid recursion for example for Throwable.cause
140       }
141     };
142   }
143
144   private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
145     Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
146     if (raw.isInterface()) {
147       return result;
148     }
149
150     Type declaredType = type.getType();
151     while (raw != Object.class) {
152       Field[] fields = raw.getDeclaredFields();
153       for (Field field : fields) {
154         boolean serialize = excludeField(field, true);
155         boolean deserialize = excludeField(field, false);
156         if (!serialize && !deserialize) {
157           continue;
158         }
159         accessor.makeAccessible(field);
160         Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
161         List<String> fieldNames = getFieldNames(field);
162         BoundField previous = null;
163         for (int i = 0, size = fieldNames.size(); i < size; ++i) {
164           String name = fieldNames.get(i);
165           if (i != 0) serialize = false// only serialize the default name
166           BoundField boundField = createBoundField(context, field, name,
167               TypeToken.get(fieldType), serialize, deserialize);
168           BoundField replaced = result.put(name, boundField);
169           if (previous == null) previous = replaced;
170         }
171         if (previous != null) {
172           throw new IllegalArgumentException(declaredType
173               + " declares multiple JSON fields named " + previous.name);
174         }
175       }
176       type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
177       raw = type.getRawType();
178     }
179     return result;
180   }
181
182   static abstract class BoundField {
183     final String name;
184     final boolean serialized;
185     final boolean deserialized;
186
187     protected BoundField(String name, boolean serialized, boolean deserialized) {
188       this.name = name;
189       this.serialized = serialized;
190       this.deserialized = deserialized;
191     }
192     abstract boolean writeField(Object value) throws IOException, IllegalAccessException;
193     abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException;
194     abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException;
195   }
196
197   public static final class Adapter<T> extends TypeAdapter<T> {
198     private final ObjectConstructor<T> constructor;
199     private final Map<String, BoundField> boundFields;
200
201     Adapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) {
202       this.constructor = constructor;
203       this.boundFields = boundFields;
204     }
205
206     @Override public T read(JsonReader in) throws IOException {
207       if (in.peek() == JsonToken.NULL) {
208         in.nextNull();
209         return null;
210       }
211
212       T instance = constructor.construct();
213
214       try {
215         in.beginObject();
216         while (in.hasNext()) {
217           String name = in.nextName();
218           BoundField field = boundFields.get(name);
219           if (field == null || !field.deserialized) {
220             in.skipValue();
221           } else {
222             field.read(in, instance);
223           }
224         }
225       } catch (IllegalStateException e) {
226         throw new JsonSyntaxException(e);
227       } catch (IllegalAccessException e) {
228         throw new AssertionError(e);
229       }
230       in.endObject();
231       return instance;
232     }
233
234     @Override public void write(JsonWriter out, T value) throws IOException {
235       if (value == null) {
236         out.nullValue();
237         return;
238       }
239
240       out.beginObject();
241       try {
242         for (BoundField boundField : boundFields.values()) {
243           if (boundField.writeField(value)) {
244             out.name(boundField.name);
245             boundField.write(out, value);
246           }
247         }
248       } catch (IllegalAccessException e) {
249         throw new AssertionError(e);
250       }
251       out.endObject();
252     }
253   }
254 }
255