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.Type;
22 import java.util.ArrayDeque;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.Deque;
27 import java.util.Iterator;
28 import java.util.LinkedHashMap;
29 import java.util.LinkedHashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33 import javax.annotation.CheckReturnValue;
34 import javax.annotation.Nullable;
35
36 import static com.squareup.moshi.internal.Util.canonicalize;
37 import static com.squareup.moshi.internal.Util.typeAnnotatedWithAnnotations;
38
39
42 public final class Moshi {
43 static final List<JsonAdapter.Factory> BUILT_IN_FACTORIES = new ArrayList<>(5);
44
45 static {
46 BUILT_IN_FACTORIES.add(StandardJsonAdapters.FACTORY);
47 BUILT_IN_FACTORIES.add(CollectionJsonAdapter.FACTORY);
48 BUILT_IN_FACTORIES.add(MapJsonAdapter.FACTORY);
49 BUILT_IN_FACTORIES.add(ArrayJsonAdapter.FACTORY);
50 BUILT_IN_FACTORIES.add(ClassJsonAdapter.FACTORY);
51 }
52
53 private final List<JsonAdapter.Factory> factories;
54 private final ThreadLocal<LookupChain> lookupChainThreadLocal = new ThreadLocal<>();
55 private final Map<Object, JsonAdapter<?>> adapterCache = new LinkedHashMap<>();
56
57 Moshi(Builder builder) {
58 List<JsonAdapter.Factory> factories = new ArrayList<>(
59 builder.factories.size() + BUILT_IN_FACTORIES.size());
60 factories.addAll(builder.factories);
61 factories.addAll(BUILT_IN_FACTORIES);
62 this.factories = Collections.unmodifiableList(factories);
63 }
64
65
66 @CheckReturnValue public <T> JsonAdapter<T> adapter(Type type) {
67 return adapter(type, Util.NO_ANNOTATIONS);
68 }
69
70 @CheckReturnValue public <T> JsonAdapter<T> adapter(Class<T> type) {
71 return adapter(type, Util.NO_ANNOTATIONS);
72 }
73
74 @CheckReturnValue
75 public <T> JsonAdapter<T> adapter(Type type, Class<? extends Annotation> annotationType) {
76 if (annotationType == null) {
77 throw new NullPointerException("annotationType == null");
78 }
79 return adapter(type,
80 Collections.singleton(Types.createJsonQualifierImplementation(annotationType)));
81 }
82
83 @CheckReturnValue
84 public <T> JsonAdapter<T> adapter(Type type, Class<? extends Annotation>... annotationTypes) {
85 if (annotationTypes.length == 1) {
86 return adapter(type, annotationTypes[0]);
87 }
88 Set<Annotation> annotations = new LinkedHashSet<>(annotationTypes.length);
89 for (Class<? extends Annotation> annotationType : annotationTypes) {
90 annotations.add(Types.createJsonQualifierImplementation(annotationType));
91 }
92 return adapter(type, Collections.unmodifiableSet(annotations));
93 }
94
95 @CheckReturnValue
96 public <T> JsonAdapter<T> adapter(Type type, Set<? extends Annotation> annotations) {
97 return adapter(type, annotations, null);
98 }
99
100
104 @CheckReturnValue
105 @SuppressWarnings("unchecked")
106 public <T> JsonAdapter<T> adapter(Type type, Set<? extends Annotation> annotations,
107 @Nullable String fieldName) {
108 if (type == null) {
109 throw new NullPointerException("type == null");
110 }
111 if (annotations == null) {
112 throw new NullPointerException("annotations == null");
113 }
114
115 type = canonicalize(type);
116
117
118 Object cacheKey = cacheKey(type, annotations);
119 synchronized (adapterCache) {
120 JsonAdapter<?> result = adapterCache.get(cacheKey);
121 if (result != null) return (JsonAdapter<T>) result;
122 }
123
124 LookupChain lookupChain = lookupChainThreadLocal.get();
125 if (lookupChain == null) {
126 lookupChain = new LookupChain();
127 lookupChainThreadLocal.set(lookupChain);
128 }
129
130 boolean success = false;
131 JsonAdapter<T> adapterFromCall = lookupChain.push(type, fieldName, cacheKey);
132 try {
133 if (adapterFromCall != null) return adapterFromCall;
134
135
136 for (int i = 0, size = factories.size(); i < size; i++) {
137 JsonAdapter<T> result = (JsonAdapter<T>) factories.get(i).create(type, annotations, this);
138 if (result == null) continue;
139
140
141 lookupChain.adapterFound(result);
142 success = true;
143 return result;
144 }
145
146 throw new IllegalArgumentException(
147 "No JsonAdapter for " + typeAnnotatedWithAnnotations(type, annotations));
148 } catch (IllegalArgumentException e) {
149 throw lookupChain.exceptionWithLookupStack(e);
150 } finally {
151 lookupChain.pop(success);
152 }
153 }
154
155 @CheckReturnValue
156 @SuppressWarnings("unchecked")
157 public <T> JsonAdapter<T> nextAdapter(JsonAdapter.Factory skipPast, Type type,
158 Set<? extends Annotation> annotations) {
159 if (annotations == null) throw new NullPointerException("annotations == null");
160
161 type = canonicalize(type);
162
163 int skipPastIndex = factories.indexOf(skipPast);
164 if (skipPastIndex == -1) {
165 throw new IllegalArgumentException("Unable to skip past unknown factory " + skipPast);
166 }
167 for (int i = skipPastIndex + 1, size = factories.size(); i < size; i++) {
168 JsonAdapter<T> result = (JsonAdapter<T>) factories.get(i).create(type, annotations, this);
169 if (result != null) return result;
170 }
171 throw new IllegalArgumentException("No next JsonAdapter for "
172 + typeAnnotatedWithAnnotations(type, annotations));
173 }
174
175
176 @CheckReturnValue public Moshi.Builder newBuilder() {
177 int fullSize = factories.size();
178 int tailSize = BUILT_IN_FACTORIES.size();
179 List<JsonAdapter.Factory> customFactories = factories.subList(0, fullSize - tailSize);
180 return new Builder().addAll(customFactories);
181 }
182
183
184 private Object cacheKey(Type type, Set<? extends Annotation> annotations) {
185 if (annotations.isEmpty()) return type;
186 return Arrays.asList(type, annotations);
187 }
188
189 public static final class Builder {
190 final List<JsonAdapter.Factory> factories = new ArrayList<>();
191
192 public <T> Builder add(final Type type, final JsonAdapter<T> jsonAdapter) {
193 if (type == null) throw new IllegalArgumentException("type == null");
194 if (jsonAdapter == null) throw new IllegalArgumentException("jsonAdapter == null");
195
196 return add(new JsonAdapter.Factory() {
197 @Override public @Nullable JsonAdapter<?> create(
198 Type targetType, Set<? extends Annotation> annotations, Moshi moshi) {
199 return annotations.isEmpty() && Util.typesMatch(type, targetType) ? jsonAdapter : null;
200 }
201 });
202 }
203
204 public <T> Builder add(final Type type, final Class<? extends Annotation> annotation,
205 final JsonAdapter<T> jsonAdapter) {
206 if (type == null) throw new IllegalArgumentException("type == null");
207 if (annotation == null) throw new IllegalArgumentException("annotation == null");
208 if (jsonAdapter == null) throw new IllegalArgumentException("jsonAdapter == null");
209 if (!annotation.isAnnotationPresent(JsonQualifier.class)) {
210 throw new IllegalArgumentException(annotation + " does not have @JsonQualifier");
211 }
212 if (annotation.getDeclaredMethods().length > 0) {
213 throw new IllegalArgumentException("Use JsonAdapter.Factory for annotations with elements");
214 }
215
216 return add(new JsonAdapter.Factory() {
217 @Override public @Nullable JsonAdapter<?> create(
218 Type targetType, Set<? extends Annotation> annotations, Moshi moshi) {
219 if (Util.typesMatch(type, targetType)
220 && annotations.size() == 1
221 && Util.isAnnotationPresent(annotations, annotation)) {
222 return jsonAdapter;
223 }
224 return null;
225 }
226 });
227 }
228
229 public Builder add(JsonAdapter.Factory factory) {
230 if (factory == null) throw new IllegalArgumentException("factory == null");
231 factories.add(factory);
232 return this;
233 }
234
235 public Builder add(Object adapter) {
236 if (adapter == null) throw new IllegalArgumentException("adapter == null");
237 return add(AdapterMethodsFactory.get(adapter));
238 }
239
240 Builder addAll(List<JsonAdapter.Factory> factories) {
241 this.factories.addAll(factories);
242 return this;
243 }
244
245 @CheckReturnValue public Moshi build() {
246 return new Moshi(this);
247 }
248 }
249
250
268 final class LookupChain {
269 final List<Lookup<?>> callLookups = new ArrayList<>();
270 final Deque<Lookup<?>> stack = new ArrayDeque<>();
271 boolean exceptionAnnotated;
272
273
278 <T> JsonAdapter<T> push(Type type, @Nullable String fieldName, Object cacheKey) {
279
280 for (int i = 0, size = callLookups.size(); i < size; i++) {
281 Lookup<?> lookup = callLookups.get(i);
282 if (lookup.cacheKey.equals(cacheKey)) {
283 Lookup<T> hit = (Lookup<T>) lookup;
284 stack.add(hit);
285 return hit.adapter != null ? hit.adapter : hit;
286 }
287 }
288
289
290 Lookup<Object> lookup = new Lookup<>(type, fieldName, cacheKey);
291 callLookups.add(lookup);
292 stack.add(lookup);
293 return null;
294 }
295
296
297 <T> void adapterFound(JsonAdapter<T> result) {
298 Lookup<T> currentLookup = (Lookup<T>) stack.getLast();
299 currentLookup.adapter = result;
300 }
301
302
307 void pop(boolean success) {
308 stack.removeLast();
309 if (!stack.isEmpty()) return;
310
311 lookupChainThreadLocal.remove();
312
313 if (success) {
314 synchronized (adapterCache) {
315 for (int i = 0, size = callLookups.size(); i < size; i++) {
316 Lookup<?> lookup = callLookups.get(i);
317 JsonAdapter<?> replaced = adapterCache.put(lookup.cacheKey, lookup.adapter);
318 if (replaced != null) {
319 ((Lookup<Object>) lookup).adapter = (JsonAdapter<Object>) replaced;
320 adapterCache.put(lookup.cacheKey, replaced);
321 }
322 }
323 }
324 }
325 }
326
327 IllegalArgumentException exceptionWithLookupStack(IllegalArgumentException e) {
328
329 if (exceptionAnnotated) return e;
330 exceptionAnnotated = true;
331
332 int size = stack.size();
333 if (size == 1 && stack.getFirst().fieldName == null) return e;
334
335 StringBuilder errorMessageBuilder = new StringBuilder(e.getMessage());
336 for (Iterator<Lookup<?>> i = stack.descendingIterator(); i.hasNext(); ) {
337 Lookup<?> lookup = i.next();
338 errorMessageBuilder
339 .append("\nfor ")
340 .append(lookup.type);
341 if (lookup.fieldName != null) {
342 errorMessageBuilder
343 .append(' ')
344 .append(lookup.fieldName);
345 }
346 }
347
348 return new IllegalArgumentException(errorMessageBuilder.toString(), e);
349 }
350 }
351
352
353 static final class Lookup<T> extends JsonAdapter<T> {
354 final Type type;
355 final @Nullable String fieldName;
356 final Object cacheKey;
357 @Nullable JsonAdapter<T> adapter;
358
359 Lookup(Type type, @Nullable String fieldName, Object cacheKey) {
360 this.type = type;
361 this.fieldName = fieldName;
362 this.cacheKey = cacheKey;
363 }
364
365 @Override public T fromJson(JsonReader reader) throws IOException {
366 if (adapter == null) throw new IllegalStateException("JsonAdapter isn't ready");
367 return adapter.fromJson(reader);
368 }
369
370 @Override public void toJson(JsonWriter writer, T value) throws IOException {
371 if (adapter == null) throw new IllegalStateException("JsonAdapter isn't ready");
372 adapter.toJson(writer, value);
373 }
374
375 @Override public String toString() {
376 return adapter != null ? adapter.toString() : super.toString();
377 }
378 }
379 }
380