/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.storage.mapdb;
import java.io.IOException;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.internal.bind.MapTypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
/**
* Type adapter that makes sure that all Numeric values in Maps of type
* Map<String, Object> are deserialized as BigDecimal instances instead of
* doubles.
*
* @author Ivan Iliev
*
*/
public class PropertiesTypeAdapter extends TypeAdapter<Map<String, Object>> {
public static final TypeToken<Map<String, Object>> TOKEN = new TypeToken<Map<String, Object>>() {
};
private final TypeAdapter<Map<String, Object>> delegate;
private final ConstructorConstructor constructor;
private final TypeAdapter<String> keyAdapter;
private final TypeAdapter<Object> valueAdapter;
public PropertiesTypeAdapter(Gson gson) {
// obtain the default type adapters for String and Object classes
keyAdapter = gson.getAdapter(String.class);
valueAdapter = gson.getAdapter(Object.class);
// obtain default gson objects
constructor = new ConstructorConstructor(Collections.<Type, InstanceCreator<?>> emptyMap());
delegate = new MapTypeAdapterFactory(constructor, false).create(new Gson(), TOKEN);
}
@Override
public void write(JsonWriter out, Map<String, Object> value) throws IOException {
// write remains unchanged
delegate.write(out, value);
}
@Override
public Map<String, Object> read(JsonReader in) throws IOException {
// gson implementation code is modified when deserializing numbers
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
return null;
}
Map<String, Object> map = constructor.get(TOKEN).construct();
if (peek == JsonToken.BEGIN_ARRAY) {
in.beginArray();
while (in.hasNext()) {
in.beginArray(); // entry array
String key = keyAdapter.read(in);
// modification
Object value = getValue(in);
Object 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);
String key = keyAdapter.read(in);
// modification
Object value = getValue(in);
Object replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
}
in.endObject();
}
return map;
}
private Object getValue(JsonReader in) throws IOException {
Object value = null;
// if the next json token is a number we read it as a BigDecimal,
// otherwise use the default adapter to read it
if (JsonToken.NUMBER.equals(in.peek())) {
value = new BigDecimal(in.nextString());
} else {
value = valueAdapter.read(in);
}
return value;
}
}