1 /*
2  * Copyright (C) 2014 Square, 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 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 /**
40  * Coordinates binding between JSON values and Java objects.
41  */

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   /** Returns a JSON adapter for {@code type}, creating it if necessary. */
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   /**
101    * @param fieldName An optional field name associated with this type. The field name is used as a
102    * hint for better adapter lookup error messages for nested structures.
103    */

104   @CheckReturnValue
105   @SuppressWarnings("unchecked"// Factories are required to return only matching JsonAdapters.
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     // If there's an equivalent adapter in the cache, we're done!
118     Object cacheKey = cacheKey(type, annotations);
119     synchronized (adapterCache) {
120       JsonAdapter<?> result = adapterCache.get(cacheKey);
121       if (result != nullreturn (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 != nullreturn adapterFromCall;
134
135       // Ask each factory to create the JSON adapter.
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 == nullcontinue;
139
140         // Success! Notify the LookupChain so it is cached and can be used by re-entrant calls.
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"// Factories are required to return only matching JsonAdapters.
157   public <T> JsonAdapter<T> nextAdapter(JsonAdapter.Factory skipPast, Type type,
158       Set<? extends Annotation> annotations) {
159     if (annotations == nullthrow 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 != nullreturn result;
170     }
171     throw new IllegalArgumentException("No next JsonAdapter for "
172         + typeAnnotatedWithAnnotations(type, annotations));
173   }
174
175   /** Returns a new builder containing all custom factories used by the current instance. */
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   /** Returns an opaque object that's equal if the type and annotations are equal. */
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 == nullthrow new IllegalArgumentException("type == null");
194       if (jsonAdapter == nullthrow 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 == nullthrow new IllegalArgumentException("type == null");
207       if (annotation == nullthrow new IllegalArgumentException("annotation == null");
208       if (jsonAdapter == nullthrow 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 == nullthrow new IllegalArgumentException("factory == null");
231       factories.add(factory);
232       return this;
233     }
234
235     public Builder add(Object adapter) {
236       if (adapter == nullthrow 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   /**
251    * A possibly-reentrant chain of lookups for JSON adapters.
252    *
253    * <p>We keep track of the current stack of lookups: we may start by looking up the JSON adapter
254    * for Employee, re-enter looking for the JSON adapter of HomeAddress, and re-enter again looking
255    * up the JSON adapter of PostalCode. If any of these lookups fail we can provide a stack trace
256    * with all of the lookups.
257    *
258    * <p>Sometimes a JSON adapter factory depends on its own product; either directly or indirectly.
259    * To make this work, we offer a JSON adapter stub while the final adapter is being computed.
260    * When it is ready, we wire the stub to that finished adapter. This is necessary in
261    * self-referential object models, such as an {@code Employee} class that has a {@code
262    * List<Employee>} field for an organization's management hierarchy.
263    *
264    * <p>This class defers putting any JSON adapters in the cache until the topmost JSON adapter has
265    * successfully been computed. That way we don't pollute the cache with incomplete stubs, or
266    * adapters that may transitively depend on incomplete stubs.
267    */

268   final class LookupChain {
269     final List<Lookup<?>> callLookups = new ArrayList<>();
270     final Deque<Lookup<?>> stack = new ArrayDeque<>();
271     boolean exceptionAnnotated;
272
273     /**
274      * Returns a JSON adapter that was already created for this call, or null if this is the first
275      * time in this call that the cache key has been requested in this call. This may return a
276      * lookup that isn't yet ready if this lookup is reentrant.
277      */

278     <T> JsonAdapter<T> push(Type type, @Nullable String fieldName, Object cacheKey) {
279       // Try to find a lookup with the same key for the same call.
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       // We might need to know about this cache key later in this call. Prepare for that.
290       Lookup<Object> lookup = new Lookup<>(type, fieldName, cacheKey);
291       callLookups.add(lookup);
292       stack.add(lookup);
293       return null;
294     }
295
296     /** Sets the adapter result of the current lookup. */
297     <T> void adapterFound(JsonAdapter<T> result) {
298       Lookup<T> currentLookup = (Lookup<T>) stack.getLast();
299       currentLookup.adapter = result;
300     }
301
302     /**
303      * Completes the current lookup by removing a stack frame.
304      *
305      * @param success true if the adapter cache should be populated if this is the topmost lookup.
306      */

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       // Don't add the lookup stack to more than one exception; the deepest is sufficient.
329       if (exceptionAnnotated) return e;
330       exceptionAnnotated = true;
331
332       int size = stack.size();
333       if (size == 1 && stack.getFirst().fieldName == nullreturn 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   /** This class implements {@code JsonAdapter} so it can be used as a stub for re-entrant calls. */
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 == nullthrow 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 == nullthrow 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