| /* | 
|  * Copyright (C) 2011 Google Inc. | 
|  * | 
|  * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  * you may not use this file except in compliance with the License. | 
|  * You may obtain a copy of the License at | 
|  * | 
|  *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  * | 
|  * Unless required by applicable law or agreed to in writing, software | 
|  * distributed under the License is distributed on an "AS IS" BASIS, | 
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  * See the License for the specific language governing permissions and | 
|  * limitations under the License. | 
|  */ | 
|   | 
| package cn.emay.sdk.util.json.gson.internal.bind; | 
|   | 
| import java.io.IOException; | 
| import java.lang.reflect.Type; | 
| import java.util.ArrayList; | 
| import java.util.List; | 
| import java.util.Map; | 
|   | 
| import cn.emay.sdk.util.json.gson.Gson; | 
| import cn.emay.sdk.util.json.gson.JsonElement; | 
| import cn.emay.sdk.util.json.gson.JsonPrimitive; | 
| import cn.emay.sdk.util.json.gson.JsonSyntaxException; | 
| import cn.emay.sdk.util.json.gson.TypeAdapter; | 
| import cn.emay.sdk.util.json.gson.TypeAdapterFactory; | 
| import cn.emay.sdk.util.json.gson.internal.$Gson$Types; | 
| import cn.emay.sdk.util.json.gson.internal.ConstructorConstructor; | 
| import cn.emay.sdk.util.json.gson.internal.JsonReaderInternalAccess; | 
| import cn.emay.sdk.util.json.gson.internal.ObjectConstructor; | 
| import cn.emay.sdk.util.json.gson.internal.Streams; | 
| import cn.emay.sdk.util.json.gson.reflect.TypeToken; | 
| import cn.emay.sdk.util.json.gson.stream.JsonReader; | 
| import cn.emay.sdk.util.json.gson.stream.JsonToken; | 
| import cn.emay.sdk.util.json.gson.stream.JsonWriter; | 
|   | 
| /** | 
|  * Adapts maps to either JSON objects or JSON arrays. | 
|  * | 
|  * <h3>Maps as JSON objects</h3> For primitive keys or when complex map key | 
|  * serialization is not enabled, this converts Java {@link Map Maps} to JSON | 
|  * Objects. This requires that map keys can be serialized as strings; this is | 
|  * insufficient for some key types. For example, consider a map whose keys are | 
|  * points on a grid. The default JSON form encodes reasonably: | 
|  *  | 
|  * <pre> | 
|  * { | 
|  *     @code | 
|  *     Map<Point, String> original = new LinkedHashMap<Point, String>(); | 
|  *     original.put(new Point(5, 6), "a"); | 
|  *     original.put(new Point(8, 8), "b"); | 
|  *     System.out.println(gson.toJson(original, type)); | 
|  * } | 
|  * </pre> | 
|  *  | 
|  * The above code prints this JSON object: | 
|  *  | 
|  * <pre> | 
|  *    {@code | 
|  *   { | 
|  *     "(5,6)": "a", | 
|  *     "(8,8)": "b" | 
|  *   } | 
|  * } | 
|  * </pre> | 
|  *  | 
|  * But GSON is unable to deserialize this value because the JSON string name is | 
|  * just the {@link Object#toString() toString()} of the map key. Attempting to | 
|  * convert the above JSON to an object fails with a parse exception: | 
|  *  | 
|  * <pre> | 
|  * com.google.gson.JsonParseException: Expecting object found: "(5,6)" | 
|  *   at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler | 
|  *   at com.google.gson.ObjectNavigator.navigateClassFields | 
|  *   ... | 
|  * </pre> | 
|  * | 
|  * <h3>Maps as JSON arrays</h3> An alternative approach taken by this type | 
|  * adapter when it is required and complex map key serialization is enabled is | 
|  * to encode maps as arrays of map entries. Each map entry is a two element | 
|  * array containing a key and a value. This approach is more flexible because | 
|  * any type can be used as the map's key; not just strings. But it's also less | 
|  * portable because the receiver of such JSON must be aware of the map entry | 
|  * convention. | 
|  * | 
|  * <p> | 
|  * Register this adapter when you are creating your GSON instance. | 
|  *  | 
|  * <pre> | 
|  * { | 
|  *     @code | 
|  *     Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapAsArrayTypeAdapter()).create(); | 
|  * } | 
|  * </pre> | 
|  *  | 
|  * This will change the structure of the JSON emitted by the code above. Now we | 
|  * get an array. In this case the arrays elements are map entries: | 
|  *  | 
|  * <pre> | 
|  *    {@code | 
|  *   [ | 
|  *     [ | 
|  *       { | 
|  *         "x": 5, | 
|  *         "y": 6 | 
|  *       }, | 
|  *       "a", | 
|  *     ], | 
|  *     [ | 
|  *       { | 
|  *         "x": 8, | 
|  *         "y": 8 | 
|  *       }, | 
|  *       "b" | 
|  *     ] | 
|  *   ] | 
|  * } | 
|  * </pre> | 
|  *  | 
|  * This format will serialize and deserialize just fine as long as this adapter | 
|  * is registered. | 
|  */ | 
| public final class MapTypeAdapterFactory implements TypeAdapterFactory { | 
|     private final ConstructorConstructor constructorConstructor; | 
|     final boolean complexMapKeySerialization; | 
|   | 
|     public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor, boolean complexMapKeySerialization) { | 
|         this.constructorConstructor = constructorConstructor; | 
|         this.complexMapKeySerialization = complexMapKeySerialization; | 
|     } | 
|   | 
|     @Override | 
|     public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { | 
|         Type type = typeToken.getType(); | 
|   | 
|         Class<? super T> rawType = typeToken.getRawType(); | 
|         if (!Map.class.isAssignableFrom(rawType)) { | 
|             return null; | 
|         } | 
|   | 
|         Class<?> rawTypeOfSrc = $Gson$Types.getRawType(type); | 
|         Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc); | 
|         TypeAdapter<?> keyAdapter = getKeyAdapter(gson, keyAndValueTypes[0]); | 
|         TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(keyAndValueTypes[1])); | 
|         ObjectConstructor<T> constructor = constructorConstructor.get(typeToken); | 
|   | 
|         @SuppressWarnings({ "unchecked", "rawtypes" }) | 
|         // we don't define a type parameter for the key or value types | 
|         TypeAdapter<T> result = new Adapter(gson, keyAndValueTypes[0], keyAdapter, keyAndValueTypes[1], valueAdapter, constructor); | 
|         return result; | 
|     } | 
|   | 
|     /** | 
|      * Returns a type adapter that writes the value as a string. | 
|      */ | 
|     private TypeAdapter<?> getKeyAdapter(Gson context, Type keyType) { | 
|         return (keyType == boolean.class || keyType == Boolean.class) ? TypeAdapters.BOOLEAN_AS_STRING : context.getAdapter(TypeToken.get(keyType)); | 
|     } | 
|   | 
|     private final class Adapter<K, V> extends TypeAdapter<Map<K, V>> { | 
|         private final TypeAdapter<K> keyTypeAdapter; | 
|         private final TypeAdapter<V> valueTypeAdapter; | 
|         private final ObjectConstructor<? extends Map<K, V>> constructor; | 
|   | 
|         public Adapter(Gson context, Type keyType, TypeAdapter<K> keyTypeAdapter, Type valueType, TypeAdapter<V> valueTypeAdapter, ObjectConstructor<? extends Map<K, V>> constructor) { | 
|             this.keyTypeAdapter = new TypeAdapterRuntimeTypeWrapper<K>(context, keyTypeAdapter, keyType); | 
|             this.valueTypeAdapter = new TypeAdapterRuntimeTypeWrapper<V>(context, valueTypeAdapter, valueType); | 
|             this.constructor = constructor; | 
|         } | 
|   | 
|         @Override | 
|         public Map<K, V> read(JsonReader in) throws IOException { | 
|             JsonToken peek = in.peek(); | 
|             if (peek == JsonToken.NULL) { | 
|                 in.nextNull(); | 
|                 return null; | 
|             } | 
|   | 
|             Map<K, V> map = constructor.construct(); | 
|   | 
|             if (peek == JsonToken.BEGIN_ARRAY) { | 
|                 in.beginArray(); | 
|                 while (in.hasNext()) { | 
|                     in.beginArray(); // entry array | 
|                     K key = keyTypeAdapter.read(in); | 
|                     V value = valueTypeAdapter.read(in); | 
|                     V replaced = map.put(key, value); | 
|                     if (replaced != null) { | 
|                         throw new JsonSyntaxException("duplicate key: " + key); | 
|                     } | 
|                     in.endArray(); | 
|                 } | 
|                 in.endArray(); | 
|             } else { | 
|                 in.beginObject(); | 
|                 while (in.hasNext()) { | 
|                     JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in); | 
|                     K key = keyTypeAdapter.read(in); | 
|                     V value = valueTypeAdapter.read(in); | 
|                     V replaced = map.put(key, value); | 
|                     if (replaced != null) { | 
|                         throw new JsonSyntaxException("duplicate key: " + key); | 
|                     } | 
|                 } | 
|                 in.endObject(); | 
|             } | 
|             return map; | 
|         } | 
|   | 
|         @Override | 
|         public void write(JsonWriter out, Map<K, V> map) throws IOException { | 
|             if (map == null) { | 
|                 out.nullValue(); | 
|                 return; | 
|             } | 
|   | 
|             if (!complexMapKeySerialization) { | 
|                 out.beginObject(); | 
|                 for (Map.Entry<K, V> entry : map.entrySet()) { | 
|                     out.name(String.valueOf(entry.getKey())); | 
|                     valueTypeAdapter.write(out, entry.getValue()); | 
|                 } | 
|                 out.endObject(); | 
|                 return; | 
|             } | 
|   | 
|             boolean hasComplexKeys = false; | 
|             List<JsonElement> keys = new ArrayList<JsonElement>(map.size()); | 
|   | 
|             List<V> values = new ArrayList<V>(map.size()); | 
|             for (Map.Entry<K, V> entry : map.entrySet()) { | 
|                 JsonElement keyElement = keyTypeAdapter.toJsonTree(entry.getKey()); | 
|                 keys.add(keyElement); | 
|                 values.add(entry.getValue()); | 
|                 hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject(); | 
|             } | 
|   | 
|             if (hasComplexKeys) { | 
|                 out.beginArray(); | 
|                 for (int i = 0; i < keys.size(); i++) { | 
|                     out.beginArray(); // entry array | 
|                     Streams.write(keys.get(i), out); | 
|                     valueTypeAdapter.write(out, values.get(i)); | 
|                     out.endArray(); | 
|                 } | 
|                 out.endArray(); | 
|             } else { | 
|                 out.beginObject(); | 
|                 for (int i = 0; i < keys.size(); i++) { | 
|                     JsonElement keyElement = keys.get(i); | 
|                     out.name(keyToString(keyElement)); | 
|                     valueTypeAdapter.write(out, values.get(i)); | 
|                 } | 
|                 out.endObject(); | 
|             } | 
|         } | 
|   | 
|         private String keyToString(JsonElement keyElement) { | 
|             if (keyElement.isJsonPrimitive()) { | 
|                 JsonPrimitive primitive = keyElement.getAsJsonPrimitive(); | 
|                 if (primitive.isNumber()) { | 
|                     return String.valueOf(primitive.getAsNumber()); | 
|                 } else if (primitive.isBoolean()) { | 
|                     return Boolean.toString(primitive.getAsBoolean()); | 
|                 } else if (primitive.isString()) { | 
|                     return primitive.getAsString(); | 
|                 } else { | 
|                     throw new AssertionError(); | 
|                 } | 
|             } else if (keyElement.isJsonNull()) { | 
|                 return "null"; | 
|             } else { | 
|                 throw new AssertionError(); | 
|             } | 
|         } | 
|     } | 
| } |