1
16 package com.squareup.moshi;
17
18 import com.squareup.moshi.internal.Util;
19 import java.io.IOException;
20 import java.lang.annotation.Annotation;
21 import java.lang.reflect.Field;
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Modifier;
24 import java.lang.reflect.ParameterizedType;
25 import java.lang.reflect.Type;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.TreeMap;
29 import javax.annotation.Nullable;
30
31 import static com.squareup.moshi.internal.Util.resolve;
32 import static com.squareup.moshi.internal.Util.typeAnnotatedWithAnnotations;
33
34
49 final class ClassJsonAdapter<T> extends JsonAdapter<T> {
50 public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() {
51 @Override public @Nullable JsonAdapter<?> create(
52 Type type, Set<? extends Annotation> annotations, Moshi moshi) {
53 if (!(type instanceof Class) && !(type instanceof ParameterizedType)) {
54 return null;
55 }
56 Class<?> rawType = Types.getRawType(type);
57 if (rawType.isInterface() || rawType.isEnum()) return null;
58 if (Util.isPlatformType(rawType) && !Types.isAllowedPlatformType(rawType)) {
59 throw new IllegalArgumentException("Platform "
60 + typeAnnotatedWithAnnotations(type, annotations)
61 + " requires explicit JsonAdapter to be registered");
62 }
63 if (!annotations.isEmpty()) return null;
64
65 if (rawType.isAnonymousClass()) {
66 throw new IllegalArgumentException("Cannot serialize anonymous class " + rawType.getName());
67 }
68 if (rawType.isLocalClass()) {
69 throw new IllegalArgumentException("Cannot serialize local class " + rawType.getName());
70 }
71 if (rawType.getEnclosingClass() != null && !Modifier.isStatic(rawType.getModifiers())) {
72 throw new IllegalArgumentException(
73 "Cannot serialize non-static nested class " + rawType.getName());
74 }
75 if (Modifier.isAbstract(rawType.getModifiers())) {
76 throw new IllegalArgumentException("Cannot serialize abstract class " + rawType.getName());
77 }
78
79 ClassFactory<Object> classFactory = ClassFactory.get(rawType);
80 Map<String, FieldBinding<?>> fields = new TreeMap<>();
81 for (Type t = type; t != Object.class; t = Types.getGenericSuperclass(t)) {
82 createFieldBindings(moshi, t, fields);
83 }
84 return new ClassJsonAdapter<>(classFactory, fields).nullSafe();
85 }
86
87
88 private void createFieldBindings(
89 Moshi moshi, Type type, Map<String, FieldBinding<?>> fieldBindings) {
90 Class<?> rawType = Types.getRawType(type);
91 boolean platformType = Util.isPlatformType(rawType);
92 for (Field field : rawType.getDeclaredFields()) {
93 if (!includeField(platformType, field.getModifiers())) continue;
94
95
96 Type fieldType = resolve(type, rawType, field.getGenericType());
97 Set<? extends Annotation> annotations = Util.jsonAnnotations(field);
98 String fieldName = field.getName();
99 JsonAdapter<Object> adapter = moshi.adapter(fieldType, annotations, fieldName);
100
101
102 field.setAccessible(true);
103
104
105 Json jsonAnnotation = field.getAnnotation(Json.class);
106 String name = jsonAnnotation != null ? jsonAnnotation.name() : fieldName;
107 FieldBinding<Object> fieldBinding = new FieldBinding<>(name, field, adapter);
108 FieldBinding<?> replaced = fieldBindings.put(name, fieldBinding);
109 if (replaced != null) {
110 throw new IllegalArgumentException("Conflicting fields:\n"
111 + " " + replaced.field + "\n"
112 + " " + fieldBinding.field);
113 }
114 }
115 }
116
117
118 private boolean includeField(boolean platformType, int modifiers) {
119 if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) return false;
120 return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers) || !platformType;
121 }
122 };
123
124 private final ClassFactory<T> classFactory;
125 private final FieldBinding<?>[] fieldsArray;
126 private final JsonReader.Options options;
127
128 ClassJsonAdapter(ClassFactory<T> classFactory, Map<String, FieldBinding<?>> fieldsMap) {
129 this.classFactory = classFactory;
130 this.fieldsArray = fieldsMap.values().toArray(new FieldBinding[fieldsMap.size()]);
131 this.options = JsonReader.Options.of(
132 fieldsMap.keySet().toArray(new String[fieldsMap.size()]));
133 }
134
135 @Override public T fromJson(JsonReader reader) throws IOException {
136 T result;
137 try {
138 result = classFactory.newInstance();
139 } catch (InstantiationException e) {
140 throw new RuntimeException(e);
141 } catch (InvocationTargetException e) {
142 throw Util.rethrowCause(e);
143 } catch (IllegalAccessException e) {
144 throw new AssertionError();
145 }
146
147 try {
148 reader.beginObject();
149 while (reader.hasNext()) {
150 int index = reader.selectName(options);
151 if (index == -1) {
152 reader.skipName();
153 reader.skipValue();
154 continue;
155 }
156 fieldsArray[index].read(reader, result);
157 }
158 reader.endObject();
159 return result;
160 } catch (IllegalAccessException e) {
161 throw new AssertionError();
162 }
163 }
164
165 @Override public void toJson(JsonWriter writer, T value) throws IOException {
166 try {
167 writer.beginObject();
168 for (FieldBinding<?> fieldBinding : fieldsArray) {
169 writer.name(fieldBinding.name);
170 fieldBinding.write(writer, value);
171 }
172 writer.endObject();
173 } catch (IllegalAccessException e) {
174 throw new AssertionError();
175 }
176 }
177
178 @Override public String toString() {
179 return "JsonAdapter(" + classFactory + ")";
180 }
181
182 static class FieldBinding<T> {
183 final String name;
184 final Field field;
185 final JsonAdapter<T> adapter;
186
187 FieldBinding(String name, Field field, JsonAdapter<T> adapter) {
188 this.name = name;
189 this.field = field;
190 this.adapter = adapter;
191 }
192
193 void read(JsonReader reader, Object value) throws IOException, IllegalAccessException {
194 T fieldValue = adapter.fromJson(reader);
195 field.set(value, fieldValue);
196 }
197
198 @SuppressWarnings("unchecked")
199 void write(JsonWriter writer, Object value) throws IllegalAccessException, IOException {
200 T fieldValue = (T) field.get(value);
201 adapter.toJson(writer, fieldValue);
202 }
203 }
204 }
205