• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Gson;
20 import com.google.gson.JsonArray;
21 import com.google.gson.JsonElement;
22 import com.google.gson.JsonIOException;
23 import com.google.gson.JsonNull;
24 import com.google.gson.JsonObject;
25 import com.google.gson.JsonPrimitive;
26 import com.google.gson.JsonSyntaxException;
27 import com.google.gson.TypeAdapter;
28 import com.google.gson.TypeAdapterFactory;
29 import com.google.gson.annotations.SerializedName;
30 import com.google.gson.internal.LazilyParsedNumber;
31 import com.google.gson.reflect.TypeToken;
32 import com.google.gson.stream.JsonReader;
33 import com.google.gson.stream.JsonToken;
34 import com.google.gson.stream.JsonWriter;
35 import java.io.IOException;
36 import java.lang.reflect.AccessibleObject;
37 import java.lang.reflect.Field;
38 import java.math.BigDecimal;
39 import java.math.BigInteger;
40 import java.net.InetAddress;
41 import java.net.URI;
42 import java.net.URISyntaxException;
43 import java.net.URL;
44 import java.security.AccessController;
45 import java.security.PrivilegedAction;
46 import java.util.ArrayDeque;
47 import java.util.ArrayList;
48 import java.util.BitSet;
49 import java.util.Calendar;
50 import java.util.Currency;
51 import java.util.Deque;
52 import java.util.GregorianCalendar;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Locale;
56 import java.util.Map;
57 import java.util.StringTokenizer;
58 import java.util.UUID;
59 import java.util.concurrent.atomic.AtomicBoolean;
60 import java.util.concurrent.atomic.AtomicInteger;
61 import java.util.concurrent.atomic.AtomicIntegerArray;
62 
63 /**
64  * Type adapters for basic types.
65  */
66 public final class TypeAdapters {
TypeAdapters()67   private TypeAdapters() {
68     throw new UnsupportedOperationException();
69   }
70 
71   @SuppressWarnings("rawtypes")
72   public static final TypeAdapter<Class> CLASS = new TypeAdapter<Class>() {
73     @Override
74     public void write(JsonWriter out, Class value) throws IOException {
75       throw new UnsupportedOperationException("Attempted to serialize java.lang.Class: "
76               + value.getName() + ". Forgot to register a type adapter?");
77     }
78     @Override
79     public Class read(JsonReader in) throws IOException {
80       throw new UnsupportedOperationException(
81               "Attempted to deserialize a java.lang.Class. Forgot to register a type adapter?");
82     }
83   }.nullSafe();
84 
85   public static final TypeAdapterFactory CLASS_FACTORY = newFactory(Class.class, CLASS);
86 
87   public static final TypeAdapter<BitSet> BIT_SET = new TypeAdapter<BitSet>() {
88     @Override public BitSet read(JsonReader in) throws IOException {
89       BitSet bitset = new BitSet();
90       in.beginArray();
91       int i = 0;
92       JsonToken tokenType = in.peek();
93       while (tokenType != JsonToken.END_ARRAY) {
94         boolean set;
95         switch (tokenType) {
96         case NUMBER:
97         case STRING:
98           int intValue = in.nextInt();
99           if (intValue == 0) {
100             set = false;
101           } else if (intValue == 1) {
102             set = true;
103           } else {
104             throw new JsonSyntaxException("Invalid bitset value " + intValue + ", expected 0 or 1; at path " + in.getPreviousPath());
105           }
106           break;
107         case BOOLEAN:
108           set = in.nextBoolean();
109           break;
110         default:
111           throw new JsonSyntaxException("Invalid bitset value type: " + tokenType + "; at path " + in.getPath());
112         }
113         if (set) {
114           bitset.set(i);
115         }
116         ++i;
117         tokenType = in.peek();
118       }
119       in.endArray();
120       return bitset;
121     }
122 
123     @Override public void write(JsonWriter out, BitSet src) throws IOException {
124       out.beginArray();
125       for (int i = 0, length = src.length(); i < length; i++) {
126         int value = (src.get(i)) ? 1 : 0;
127         out.value(value);
128       }
129       out.endArray();
130     }
131   }.nullSafe();
132 
133   public static final TypeAdapterFactory BIT_SET_FACTORY = newFactory(BitSet.class, BIT_SET);
134 
135   public static final TypeAdapter<Boolean> BOOLEAN = new TypeAdapter<Boolean>() {
136     @Override
137     public Boolean read(JsonReader in) throws IOException {
138       JsonToken peek = in.peek();
139       if (peek == JsonToken.NULL) {
140         in.nextNull();
141         return null;
142       } else if (peek == JsonToken.STRING) {
143         // support strings for compatibility with GSON 1.7
144         return Boolean.parseBoolean(in.nextString());
145       }
146       return in.nextBoolean();
147     }
148     @Override
149     public void write(JsonWriter out, Boolean value) throws IOException {
150       out.value(value);
151     }
152   };
153 
154   /**
155    * Writes a boolean as a string. Useful for map keys, where booleans aren't
156    * otherwise permitted.
157    */
158   public static final TypeAdapter<Boolean> BOOLEAN_AS_STRING = new TypeAdapter<Boolean>() {
159     @Override public Boolean read(JsonReader in) throws IOException {
160       if (in.peek() == JsonToken.NULL) {
161         in.nextNull();
162         return null;
163       }
164       return Boolean.valueOf(in.nextString());
165     }
166 
167     @Override public void write(JsonWriter out, Boolean value) throws IOException {
168       out.value(value == null ? "null" : value.toString());
169     }
170   };
171 
172   public static final TypeAdapterFactory BOOLEAN_FACTORY
173       = newFactory(boolean.class, Boolean.class, BOOLEAN);
174 
175   public static final TypeAdapter<Number> BYTE = new TypeAdapter<Number>() {
176     @Override
177     public Number read(JsonReader in) throws IOException {
178       if (in.peek() == JsonToken.NULL) {
179         in.nextNull();
180         return null;
181       }
182 
183       int intValue;
184       try {
185         intValue = in.nextInt();
186       } catch (NumberFormatException e) {
187         throw new JsonSyntaxException(e);
188       }
189       // Allow up to 255 to support unsigned values
190       if (intValue > 255 || intValue < Byte.MIN_VALUE) {
191         throw new JsonSyntaxException("Lossy conversion from " + intValue + " to byte; at path " + in.getPreviousPath());
192       }
193       return (byte) intValue;
194     }
195     @Override
196     public void write(JsonWriter out, Number value) throws IOException {
197       if (value == null) {
198         out.nullValue();
199       } else {
200         out.value(value.byteValue());
201       }
202     }
203   };
204 
205   public static final TypeAdapterFactory BYTE_FACTORY
206       = newFactory(byte.class, Byte.class, BYTE);
207 
208   public static final TypeAdapter<Number> SHORT = new TypeAdapter<Number>() {
209     @Override
210     public Number read(JsonReader in) throws IOException {
211       if (in.peek() == JsonToken.NULL) {
212         in.nextNull();
213         return null;
214       }
215 
216       int intValue;
217       try {
218         intValue = in.nextInt();
219       } catch (NumberFormatException e) {
220         throw new JsonSyntaxException(e);
221       }
222       // Allow up to 65535 to support unsigned values
223       if (intValue > 65535 || intValue < Short.MIN_VALUE) {
224         throw new JsonSyntaxException("Lossy conversion from " + intValue + " to short; at path " + in.getPreviousPath());
225       }
226       return (short) intValue;
227     }
228     @Override
229     public void write(JsonWriter out, Number value) throws IOException {
230       if (value == null) {
231         out.nullValue();
232       } else {
233         out.value(value.shortValue());
234       }
235     }
236   };
237 
238   public static final TypeAdapterFactory SHORT_FACTORY
239       = newFactory(short.class, Short.class, SHORT);
240 
241   public static final TypeAdapter<Number> INTEGER = new TypeAdapter<Number>() {
242     @Override
243     public Number read(JsonReader in) throws IOException {
244       if (in.peek() == JsonToken.NULL) {
245         in.nextNull();
246         return null;
247       }
248       try {
249         return in.nextInt();
250       } catch (NumberFormatException e) {
251         throw new JsonSyntaxException(e);
252       }
253     }
254     @Override
255     public void write(JsonWriter out, Number value) throws IOException {
256       if (value == null) {
257         out.nullValue();
258       } else {
259         out.value(value.intValue());
260       }
261     }
262   };
263   public static final TypeAdapterFactory INTEGER_FACTORY
264       = newFactory(int.class, Integer.class, INTEGER);
265 
266   public static final TypeAdapter<AtomicInteger> ATOMIC_INTEGER = new TypeAdapter<AtomicInteger>() {
267     @Override public AtomicInteger read(JsonReader in) throws IOException {
268       try {
269         return new AtomicInteger(in.nextInt());
270       } catch (NumberFormatException e) {
271         throw new JsonSyntaxException(e);
272       }
273     }
274     @Override public void write(JsonWriter out, AtomicInteger value) throws IOException {
275       out.value(value.get());
276     }
277   }.nullSafe();
278   public static final TypeAdapterFactory ATOMIC_INTEGER_FACTORY =
279       newFactory(AtomicInteger.class, TypeAdapters.ATOMIC_INTEGER);
280 
281   public static final TypeAdapter<AtomicBoolean> ATOMIC_BOOLEAN = new TypeAdapter<AtomicBoolean>() {
282     @Override public AtomicBoolean read(JsonReader in) throws IOException {
283       return new AtomicBoolean(in.nextBoolean());
284     }
285     @Override public void write(JsonWriter out, AtomicBoolean value) throws IOException {
286       out.value(value.get());
287     }
288   }.nullSafe();
289   public static final TypeAdapterFactory ATOMIC_BOOLEAN_FACTORY =
290       newFactory(AtomicBoolean.class, TypeAdapters.ATOMIC_BOOLEAN);
291 
292   public static final TypeAdapter<AtomicIntegerArray> ATOMIC_INTEGER_ARRAY = new TypeAdapter<AtomicIntegerArray>() {
293     @Override public AtomicIntegerArray read(JsonReader in) throws IOException {
294         List<Integer> list = new ArrayList<>();
295         in.beginArray();
296         while (in.hasNext()) {
297           try {
298             int integer = in.nextInt();
299             list.add(integer);
300           } catch (NumberFormatException e) {
301             throw new JsonSyntaxException(e);
302           }
303         }
304         in.endArray();
305         int length = list.size();
306         AtomicIntegerArray array = new AtomicIntegerArray(length);
307         for (int i = 0; i < length; ++i) {
308           array.set(i, list.get(i));
309         }
310         return array;
311     }
312     @Override public void write(JsonWriter out, AtomicIntegerArray value) throws IOException {
313       out.beginArray();
314       for (int i = 0, length = value.length(); i < length; i++) {
315         out.value(value.get(i));
316       }
317       out.endArray();
318     }
319   }.nullSafe();
320   public static final TypeAdapterFactory ATOMIC_INTEGER_ARRAY_FACTORY =
321       newFactory(AtomicIntegerArray.class, TypeAdapters.ATOMIC_INTEGER_ARRAY);
322 
323   public static final TypeAdapter<Number> LONG = new TypeAdapter<Number>() {
324     @Override
325     public Number read(JsonReader in) throws IOException {
326       if (in.peek() == JsonToken.NULL) {
327         in.nextNull();
328         return null;
329       }
330       try {
331         return in.nextLong();
332       } catch (NumberFormatException e) {
333         throw new JsonSyntaxException(e);
334       }
335     }
336     @Override
337     public void write(JsonWriter out, Number value) throws IOException {
338       if (value == null) {
339         out.nullValue();
340       } else {
341         out.value(value.longValue());
342       }
343     }
344   };
345 
346   public static final TypeAdapter<Number> FLOAT = new TypeAdapter<Number>() {
347     @Override
348     public Number read(JsonReader in) throws IOException {
349       if (in.peek() == JsonToken.NULL) {
350         in.nextNull();
351         return null;
352       }
353       return (float) in.nextDouble();
354     }
355     @Override
356     public void write(JsonWriter out, Number value) throws IOException {
357       if (value == null) {
358         out.nullValue();
359       } else {
360         // For backward compatibility don't call `JsonWriter.value(float)` because that method has
361         // been newly added and not all custom JsonWriter implementations might override it yet
362         Number floatNumber = value instanceof Float ? value : value.floatValue();
363         out.value(floatNumber);
364       }
365     }
366   };
367 
368   public static final TypeAdapter<Number> DOUBLE = new TypeAdapter<Number>() {
369     @Override
370     public Number read(JsonReader in) throws IOException {
371       if (in.peek() == JsonToken.NULL) {
372         in.nextNull();
373         return null;
374       }
375       return in.nextDouble();
376     }
377     @Override
378     public void write(JsonWriter out, Number value) throws IOException {
379       if (value == null) {
380         out.nullValue();
381       } else {
382         out.value(value.doubleValue());
383       }
384     }
385   };
386 
387   public static final TypeAdapter<Character> CHARACTER = new TypeAdapter<Character>() {
388     @Override
389     public Character read(JsonReader in) throws IOException {
390       if (in.peek() == JsonToken.NULL) {
391         in.nextNull();
392         return null;
393       }
394       String str = in.nextString();
395       if (str.length() != 1) {
396         throw new JsonSyntaxException("Expecting character, got: " + str + "; at " + in.getPreviousPath());
397       }
398       return str.charAt(0);
399     }
400     @Override
401     public void write(JsonWriter out, Character value) throws IOException {
402       out.value(value == null ? null : String.valueOf(value));
403     }
404   };
405 
406   public static final TypeAdapterFactory CHARACTER_FACTORY
407       = newFactory(char.class, Character.class, CHARACTER);
408 
409   public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
410     @Override
411     public String read(JsonReader in) throws IOException {
412       JsonToken peek = in.peek();
413       if (peek == JsonToken.NULL) {
414         in.nextNull();
415         return null;
416       }
417       /* coerce booleans to strings for backwards compatibility */
418       if (peek == JsonToken.BOOLEAN) {
419         return Boolean.toString(in.nextBoolean());
420       }
421       return in.nextString();
422     }
423     @Override
424     public void write(JsonWriter out, String value) throws IOException {
425       out.value(value);
426     }
427   };
428 
429   public static final TypeAdapter<BigDecimal> BIG_DECIMAL = new TypeAdapter<BigDecimal>() {
430     @Override public BigDecimal read(JsonReader in) throws IOException {
431       if (in.peek() == JsonToken.NULL) {
432         in.nextNull();
433         return null;
434       }
435       String s = in.nextString();
436       try {
437         return new BigDecimal(s);
438       } catch (NumberFormatException e) {
439         throw new JsonSyntaxException("Failed parsing '" + s + "' as BigDecimal; at path " + in.getPreviousPath(), e);
440       }
441     }
442 
443     @Override public void write(JsonWriter out, BigDecimal value) throws IOException {
444       out.value(value);
445     }
446   };
447 
448   public static final TypeAdapter<BigInteger> BIG_INTEGER = new TypeAdapter<BigInteger>() {
449     @Override public BigInteger read(JsonReader in) throws IOException {
450       if (in.peek() == JsonToken.NULL) {
451         in.nextNull();
452         return null;
453       }
454       String s = in.nextString();
455       try {
456         return new BigInteger(s);
457       } catch (NumberFormatException e) {
458         throw new JsonSyntaxException("Failed parsing '" + s + "' as BigInteger; at path " + in.getPreviousPath(), e);
459       }
460     }
461 
462     @Override public void write(JsonWriter out, BigInteger value) throws IOException {
463       out.value(value);
464     }
465   };
466 
467   public static final TypeAdapter<LazilyParsedNumber> LAZILY_PARSED_NUMBER = new TypeAdapter<LazilyParsedNumber>() {
468     // Normally users should not be able to access and deserialize LazilyParsedNumber because
469     // it is an internal type, but implement this nonetheless in case there are legit corner
470     // cases where this is possible
471     @Override public LazilyParsedNumber read(JsonReader in) throws IOException {
472       if (in.peek() == JsonToken.NULL) {
473         in.nextNull();
474         return null;
475       }
476       return new LazilyParsedNumber(in.nextString());
477     }
478 
479     @Override public void write(JsonWriter out, LazilyParsedNumber value) throws IOException {
480       out.value(value);
481     }
482   };
483 
484   public static final TypeAdapterFactory STRING_FACTORY = newFactory(String.class, STRING);
485 
486   public static final TypeAdapter<StringBuilder> STRING_BUILDER = new TypeAdapter<StringBuilder>() {
487     @Override
488     public StringBuilder read(JsonReader in) throws IOException {
489       if (in.peek() == JsonToken.NULL) {
490         in.nextNull();
491         return null;
492       }
493       return new StringBuilder(in.nextString());
494     }
495     @Override
496     public void write(JsonWriter out, StringBuilder value) throws IOException {
497       out.value(value == null ? null : value.toString());
498     }
499   };
500 
501   public static final TypeAdapterFactory STRING_BUILDER_FACTORY =
502     newFactory(StringBuilder.class, STRING_BUILDER);
503 
504   public static final TypeAdapter<StringBuffer> STRING_BUFFER = new TypeAdapter<StringBuffer>() {
505     @Override
506     public StringBuffer read(JsonReader in) throws IOException {
507       if (in.peek() == JsonToken.NULL) {
508         in.nextNull();
509         return null;
510       }
511       return new StringBuffer(in.nextString());
512     }
513     @Override
514     public void write(JsonWriter out, StringBuffer value) throws IOException {
515       out.value(value == null ? null : value.toString());
516     }
517   };
518 
519   public static final TypeAdapterFactory STRING_BUFFER_FACTORY =
520     newFactory(StringBuffer.class, STRING_BUFFER);
521 
522   public static final TypeAdapter<URL> URL = new TypeAdapter<URL>() {
523     @Override
524     public URL read(JsonReader in) throws IOException {
525       if (in.peek() == JsonToken.NULL) {
526         in.nextNull();
527         return null;
528       }
529       String nextString = in.nextString();
530       return "null".equals(nextString) ? null : new URL(nextString);
531     }
532     @Override
533     public void write(JsonWriter out, URL value) throws IOException {
534       out.value(value == null ? null : value.toExternalForm());
535     }
536   };
537 
538   public static final TypeAdapterFactory URL_FACTORY = newFactory(URL.class, URL);
539 
540   public static final TypeAdapter<URI> URI = new TypeAdapter<URI>() {
541     @Override
542     public URI read(JsonReader in) throws IOException {
543       if (in.peek() == JsonToken.NULL) {
544         in.nextNull();
545         return null;
546       }
547       try {
548         String nextString = in.nextString();
549         return "null".equals(nextString) ? null : new URI(nextString);
550       } catch (URISyntaxException e) {
551         throw new JsonIOException(e);
552       }
553     }
554     @Override
555     public void write(JsonWriter out, URI value) throws IOException {
556       out.value(value == null ? null : value.toASCIIString());
557     }
558   };
559 
560   public static final TypeAdapterFactory URI_FACTORY = newFactory(URI.class, URI);
561 
562   public static final TypeAdapter<InetAddress> INET_ADDRESS = new TypeAdapter<InetAddress>() {
563     @Override
564     public InetAddress read(JsonReader in) throws IOException {
565       if (in.peek() == JsonToken.NULL) {
566         in.nextNull();
567         return null;
568       }
569       // regrettably, this should have included both the host name and the host address
570       return InetAddress.getByName(in.nextString());
571     }
572     @Override
573     public void write(JsonWriter out, InetAddress value) throws IOException {
574       out.value(value == null ? null : value.getHostAddress());
575     }
576   };
577 
578   public static final TypeAdapterFactory INET_ADDRESS_FACTORY =
579     newTypeHierarchyFactory(InetAddress.class, INET_ADDRESS);
580 
581   public static final TypeAdapter<UUID> UUID = new TypeAdapter<UUID>() {
582     @Override
583     public UUID read(JsonReader in) throws IOException {
584       if (in.peek() == JsonToken.NULL) {
585         in.nextNull();
586         return null;
587       }
588       String s = in.nextString();
589       try {
590         return java.util.UUID.fromString(s);
591       } catch (IllegalArgumentException e) {
592         throw new JsonSyntaxException("Failed parsing '" + s + "' as UUID; at path " + in.getPreviousPath(), e);
593       }
594     }
595     @Override
596     public void write(JsonWriter out, UUID value) throws IOException {
597       out.value(value == null ? null : value.toString());
598     }
599   };
600 
601   public static final TypeAdapterFactory UUID_FACTORY = newFactory(UUID.class, UUID);
602 
603   public static final TypeAdapter<Currency> CURRENCY = new TypeAdapter<Currency>() {
604     @Override
605     public Currency read(JsonReader in) throws IOException {
606       String s = in.nextString();
607       try {
608         return Currency.getInstance(s);
609       } catch (IllegalArgumentException e) {
610         throw new JsonSyntaxException("Failed parsing '" + s + "' as Currency; at path " + in.getPreviousPath(), e);
611       }
612     }
613     @Override
614     public void write(JsonWriter out, Currency value) throws IOException {
615       out.value(value.getCurrencyCode());
616     }
617   }.nullSafe();
618   public static final TypeAdapterFactory CURRENCY_FACTORY = newFactory(Currency.class, CURRENCY);
619 
620   public static final TypeAdapter<Calendar> CALENDAR = new TypeAdapter<Calendar>() {
621     private static final String YEAR = "year";
622     private static final String MONTH = "month";
623     private static final String DAY_OF_MONTH = "dayOfMonth";
624     private static final String HOUR_OF_DAY = "hourOfDay";
625     private static final String MINUTE = "minute";
626     private static final String SECOND = "second";
627 
628     @Override
629     public Calendar read(JsonReader in) throws IOException {
630       if (in.peek() == JsonToken.NULL) {
631         in.nextNull();
632         return  null;
633       }
634       in.beginObject();
635       int year = 0;
636       int month = 0;
637       int dayOfMonth = 0;
638       int hourOfDay = 0;
639       int minute = 0;
640       int second = 0;
641       while (in.peek() != JsonToken.END_OBJECT) {
642         String name = in.nextName();
643         int value = in.nextInt();
644         if (YEAR.equals(name)) {
645           year = value;
646         } else if (MONTH.equals(name)) {
647           month = value;
648         } else if (DAY_OF_MONTH.equals(name)) {
649           dayOfMonth = value;
650         } else if (HOUR_OF_DAY.equals(name)) {
651           hourOfDay = value;
652         } else if (MINUTE.equals(name)) {
653           minute = value;
654         } else if (SECOND.equals(name)) {
655           second = value;
656         }
657       }
658       in.endObject();
659       return new GregorianCalendar(year, month, dayOfMonth, hourOfDay, minute, second);
660     }
661 
662     @Override
663     public void write(JsonWriter out, Calendar value) throws IOException {
664       if (value == null) {
665         out.nullValue();
666         return;
667       }
668       out.beginObject();
669       out.name(YEAR);
670       out.value(value.get(Calendar.YEAR));
671       out.name(MONTH);
672       out.value(value.get(Calendar.MONTH));
673       out.name(DAY_OF_MONTH);
674       out.value(value.get(Calendar.DAY_OF_MONTH));
675       out.name(HOUR_OF_DAY);
676       out.value(value.get(Calendar.HOUR_OF_DAY));
677       out.name(MINUTE);
678       out.value(value.get(Calendar.MINUTE));
679       out.name(SECOND);
680       out.value(value.get(Calendar.SECOND));
681       out.endObject();
682     }
683   };
684 
685   public static final TypeAdapterFactory CALENDAR_FACTORY =
686     newFactoryForMultipleTypes(Calendar.class, GregorianCalendar.class, CALENDAR);
687 
688   public static final TypeAdapter<Locale> LOCALE = new TypeAdapter<Locale>() {
689     @Override
690     public Locale read(JsonReader in) throws IOException {
691       if (in.peek() == JsonToken.NULL) {
692         in.nextNull();
693         return null;
694       }
695       String locale = in.nextString();
696       StringTokenizer tokenizer = new StringTokenizer(locale, "_");
697       String language = null;
698       String country = null;
699       String variant = null;
700       if (tokenizer.hasMoreElements()) {
701         language = tokenizer.nextToken();
702       }
703       if (tokenizer.hasMoreElements()) {
704         country = tokenizer.nextToken();
705       }
706       if (tokenizer.hasMoreElements()) {
707         variant = tokenizer.nextToken();
708       }
709       if (country == null && variant == null) {
710         return new Locale(language);
711       } else if (variant == null) {
712         return new Locale(language, country);
713       } else {
714         return new Locale(language, country, variant);
715       }
716     }
717     @Override
718     public void write(JsonWriter out, Locale value) throws IOException {
719       out.value(value == null ? null : value.toString());
720     }
721   };
722 
723   public static final TypeAdapterFactory LOCALE_FACTORY = newFactory(Locale.class, LOCALE);
724 
725   public static final TypeAdapter<JsonElement> JSON_ELEMENT = new TypeAdapter<JsonElement>() {
726     /**
727      * Tries to begin reading a JSON array or JSON object, returning {@code null} if
728      * the next element is neither of those.
729      */
730     private JsonElement tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException {
731       switch (peeked) {
732         case BEGIN_ARRAY:
733           in.beginArray();
734           return new JsonArray();
735         case BEGIN_OBJECT:
736           in.beginObject();
737           return new JsonObject();
738         default:
739           return null;
740       }
741     }
742 
743     /** Reads a {@link JsonElement} which cannot have any nested elements */
744     private JsonElement readTerminal(JsonReader in, JsonToken peeked) throws IOException {
745       switch (peeked) {
746         case STRING:
747           return new JsonPrimitive(in.nextString());
748         case NUMBER:
749           String number = in.nextString();
750           return new JsonPrimitive(new LazilyParsedNumber(number));
751         case BOOLEAN:
752           return new JsonPrimitive(in.nextBoolean());
753         case NULL:
754           in.nextNull();
755           return JsonNull.INSTANCE;
756         default:
757           // When read(JsonReader) is called with JsonReader in invalid state
758           throw new IllegalStateException("Unexpected token: " + peeked);
759       }
760     }
761 
762     @Override public JsonElement read(JsonReader in) throws IOException {
763       if (in instanceof JsonTreeReader) {
764         return ((JsonTreeReader) in).nextJsonElement();
765       }
766 
767       // Either JsonArray or JsonObject
768       JsonElement current;
769       JsonToken peeked = in.peek();
770 
771       current = tryBeginNesting(in, peeked);
772       if (current == null) {
773         return readTerminal(in, peeked);
774       }
775 
776       Deque<JsonElement> stack = new ArrayDeque<>();
777 
778       while (true) {
779         while (in.hasNext()) {
780           String name = null;
781           // Name is only used for JSON object members
782           if (current instanceof JsonObject) {
783             name = in.nextName();
784           }
785 
786           peeked = in.peek();
787           JsonElement value = tryBeginNesting(in, peeked);
788           boolean isNesting = value != null;
789 
790           if (value == null) {
791             value = readTerminal(in, peeked);
792           }
793 
794           if (current instanceof JsonArray) {
795             ((JsonArray) current).add(value);
796           } else {
797             ((JsonObject) current).add(name, value);
798           }
799 
800           if (isNesting) {
801             stack.addLast(current);
802             current = value;
803           }
804         }
805 
806         // End current element
807         if (current instanceof JsonArray) {
808           in.endArray();
809         } else {
810           in.endObject();
811         }
812 
813         if (stack.isEmpty()) {
814           return current;
815         } else {
816           // Continue with enclosing element
817           current = stack.removeLast();
818         }
819       }
820     }
821 
822     @Override public void write(JsonWriter out, JsonElement value) throws IOException {
823       if (value == null || value.isJsonNull()) {
824         out.nullValue();
825       } else if (value.isJsonPrimitive()) {
826         JsonPrimitive primitive = value.getAsJsonPrimitive();
827         if (primitive.isNumber()) {
828           out.value(primitive.getAsNumber());
829         } else if (primitive.isBoolean()) {
830           out.value(primitive.getAsBoolean());
831         } else {
832           out.value(primitive.getAsString());
833         }
834 
835       } else if (value.isJsonArray()) {
836         out.beginArray();
837         for (JsonElement e : value.getAsJsonArray()) {
838           write(out, e);
839         }
840         out.endArray();
841 
842       } else if (value.isJsonObject()) {
843         out.beginObject();
844         for (Map.Entry<String, JsonElement> e : value.getAsJsonObject().entrySet()) {
845           out.name(e.getKey());
846           write(out, e.getValue());
847         }
848         out.endObject();
849 
850       } else {
851         throw new IllegalArgumentException("Couldn't write " + value.getClass());
852       }
853     }
854   };
855 
856   public static final TypeAdapterFactory JSON_ELEMENT_FACTORY
857       = newTypeHierarchyFactory(JsonElement.class, JSON_ELEMENT);
858 
859   private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
860     private final Map<String, T> nameToConstant = new HashMap<>();
861     private final Map<String, T> stringToConstant = new HashMap<>();
862     private final Map<T, String> constantToName = new HashMap<>();
863 
EnumTypeAdapter(final Class<T> classOfT)864     public EnumTypeAdapter(final Class<T> classOfT) {
865       try {
866         // Uses reflection to find enum constants to work around name mismatches for obfuscated classes
867         // Reflection access might throw SecurityException, therefore run this in privileged context;
868         // should be acceptable because this only retrieves enum constants, but does not expose anything else
869         Field[] constantFields = AccessController.doPrivileged(new PrivilegedAction<Field[]>() {
870           @Override public Field[] run() {
871             Field[] fields = classOfT.getDeclaredFields();
872             ArrayList<Field> constantFieldsList = new ArrayList<>(fields.length);
873             for (Field f : fields) {
874               if (f.isEnumConstant()) {
875                 constantFieldsList.add(f);
876               }
877             }
878 
879             Field[] constantFields = constantFieldsList.toArray(new Field[0]);
880             AccessibleObject.setAccessible(constantFields, true);
881             return constantFields;
882           }
883         });
884         for (Field constantField : constantFields) {
885           @SuppressWarnings("unchecked")
886           T constant = (T)(constantField.get(null));
887           String name = constant.name();
888           String toStringVal = constant.toString();
889 
890           SerializedName annotation = constantField.getAnnotation(SerializedName.class);
891           if (annotation != null) {
892             name = annotation.value();
893             for (String alternate : annotation.alternate()) {
894               nameToConstant.put(alternate, constant);
895             }
896           }
897           nameToConstant.put(name, constant);
898           stringToConstant.put(toStringVal, constant);
899           constantToName.put(constant, name);
900         }
901       } catch (IllegalAccessException e) {
902         throw new AssertionError(e);
903       }
904     }
read(JsonReader in)905     @Override public T read(JsonReader in) throws IOException {
906       if (in.peek() == JsonToken.NULL) {
907         in.nextNull();
908         return null;
909       }
910       String key = in.nextString();
911       T constant = nameToConstant.get(key);
912       return (constant == null) ? stringToConstant.get(key) : constant;
913     }
914 
write(JsonWriter out, T value)915     @Override public void write(JsonWriter out, T value) throws IOException {
916       out.value(value == null ? null : constantToName.get(value));
917     }
918   }
919 
920   public static final TypeAdapterFactory ENUM_FACTORY = new TypeAdapterFactory() {
921     @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
922       Class<? super T> rawType = typeToken.getRawType();
923       if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) {
924         return null;
925       }
926       if (!rawType.isEnum()) {
927         rawType = rawType.getSuperclass(); // handle anonymous subclasses
928       }
929       @SuppressWarnings({"rawtypes", "unchecked"})
930       TypeAdapter<T> adapter = (TypeAdapter<T>) new EnumTypeAdapter(rawType);
931       return adapter;
932     }
933   };
934 
newFactory( final TypeToken<TT> type, final TypeAdapter<TT> typeAdapter)935   public static <TT> TypeAdapterFactory newFactory(
936       final TypeToken<TT> type, final TypeAdapter<TT> typeAdapter) {
937     return new TypeAdapterFactory() {
938       @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
939       @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
940         return typeToken.equals(type) ? (TypeAdapter<T>) typeAdapter : null;
941       }
942     };
943   }
944 
945   public static <TT> TypeAdapterFactory newFactory(
946       final Class<TT> type, final TypeAdapter<TT> typeAdapter) {
947     return new TypeAdapterFactory() {
948       @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
949       @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
950         return typeToken.getRawType() == type ? (TypeAdapter<T>) typeAdapter : null;
951       }
952       @Override public String toString() {
953         return "Factory[type=" + type.getName() + ",adapter=" + typeAdapter + "]";
954       }
955     };
956   }
957 
958   public static <TT> TypeAdapterFactory newFactory(
959       final Class<TT> unboxed, final Class<TT> boxed, final TypeAdapter<? super TT> typeAdapter) {
960     return new TypeAdapterFactory() {
961       @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
962       @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
963         Class<? super T> rawType = typeToken.getRawType();
964         return (rawType == unboxed || rawType == boxed) ? (TypeAdapter<T>) typeAdapter : null;
965       }
966       @Override public String toString() {
967         return "Factory[type=" + boxed.getName()
968             + "+" + unboxed.getName() + ",adapter=" + typeAdapter + "]";
969       }
970     };
971   }
972 
973   public static <TT> TypeAdapterFactory newFactoryForMultipleTypes(final Class<TT> base,
974       final Class<? extends TT> sub, final TypeAdapter<? super TT> typeAdapter) {
975     return new TypeAdapterFactory() {
976       @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
977       @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
978         Class<? super T> rawType = typeToken.getRawType();
979         return (rawType == base || rawType == sub) ? (TypeAdapter<T>) typeAdapter : null;
980       }
981       @Override public String toString() {
982         return "Factory[type=" + base.getName()
983             + "+" + sub.getName() + ",adapter=" + typeAdapter + "]";
984       }
985     };
986   }
987 
988   /**
989    * Returns a factory for all subtypes of {@code typeAdapter}. We do a runtime check to confirm
990    * that the deserialized type matches the type requested.
991    */
992   public static <T1> TypeAdapterFactory newTypeHierarchyFactory(
993       final Class<T1> clazz, final TypeAdapter<T1> typeAdapter) {
994     return new TypeAdapterFactory() {
995       @SuppressWarnings("unchecked")
996       @Override public <T2> TypeAdapter<T2> create(Gson gson, TypeToken<T2> typeToken) {
997         final Class<? super T2> requestedType = typeToken.getRawType();
998         if (!clazz.isAssignableFrom(requestedType)) {
999           return null;
1000         }
1001         return (TypeAdapter<T2>) new TypeAdapter<T1>() {
1002           @Override public void write(JsonWriter out, T1 value) throws IOException {
1003             typeAdapter.write(out, value);
1004           }
1005 
1006           @Override public T1 read(JsonReader in) throws IOException {
1007             T1 result = typeAdapter.read(in);
1008             if (result != null && !requestedType.isInstance(result)) {
1009               throw new JsonSyntaxException("Expected a " + requestedType.getName()
1010                   + " but was " + result.getClass().getName() + "; at path " + in.getPreviousPath());
1011             }
1012             return result;
1013           }
1014         };
1015       }
1016       @Override public String toString() {
1017         return "Factory[typeHierarchy=" + clazz.getName() + ",adapter=" + typeAdapter + "]";
1018       }
1019     };
1020   }
1021 }
1022