bug 2775: Initial changes for reflection-free settings and serialization

This commit is contained in:
Steffen Schaefer committed 2016-03-15 11:14:17 +01:00
1 parent e623bb448d
commit fbd6651866
37 files changed
+913 -554

No files matched your search

@@ -1,24 +1,12 @@
package com.sap.sse.settings;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.common.settings.EnumSetting;
import com.sap.sse.common.settings.ListSetting;
import com.sap.sse.common.settings.MapSettings;
import com.sap.sse.common.settings.NumberSetting;
import com.sap.sse.common.settings.Setting;
import com.sap.sse.common.settings.SettingType;
import com.sap.sse.common.settings.Settings;
import com.sap.sse.common.settings.StringSetting;
import com.sap.sse.common.settings.serializer.AbstractSettingsToJsonSerializer;
/**
* Serializes a {@link Settings} object to a {@link JSONObject}. All setting types are supported as top-level entities.
@@ -35,189 +23,39 @@ import com.sap.sse.common.settings.StringSetting;
* @author Axel Uhl (D043530)
*
*/
public class SettingsToJsonSerializer {
private final static String TYPE_PROPERTY_SUFFIX = "___TYPE";
private final static char TYPE_PROPERTY_SUFFIX_ESCAPE = ':';
public JSONObject serialize(Settings settings) {
final Map<String, Setting> settingsToSerialize = settings.getNonDefaultSettings();
JSONObject jsonObject = new JSONObject();
for (Entry<String, Setting> e : settingsToSerialize.entrySet()) {
Object settingAsJson = serialize(e.getValue());
jsonObject.put(escapePropertyName(e.getKey()), settingAsJson);
final String typePropertyName = getTypePropertyName(e.getKey());
if (e.getValue() instanceof EnumSetting<?>) {
jsonObject.put(typePropertyName, getEnumTypeName((EnumSetting<?>) e.getValue()));
} else if (e.getValue() instanceof ListSetting<?>) {
Iterator<?> i = ((ListSetting<?>) e.getValue()).iterator();
if (i.hasNext()) {
Object o = i.next();
if (o instanceof EnumSetting<?>) {
jsonObject.put(typePropertyName, getEnumListTypeName(((EnumSetting<?>) o).getValue()));
}
}
}
}
return jsonObject;
public class SettingsToJsonSerializer extends AbstractSettingsToJsonSerializer<JSONObject, JSONArray> {
@Override
protected JSONObject newOBJECT() {
return new JSONObject();
}
/**
* A list containing enum literals shall be represented as simply holding these literals as strings. However,
* when de-serializing, the list elements need to be constructed as the proper enum's literals again, so we need
* to store the enumeration class name together with the information that it's a list.
*/
private Object getEnumListTypeName(Enum<?> literal) {
return SettingType.LIST.name()+"/"+literal.getClass().getName();
@Override
protected void set(JSONObject jsonObject, String property, Object value) {
jsonObject.put(property, value);
}
private Object getEnumTypeName(EnumSetting<?> value) {
return SettingType.ENUM.name()+"/"+value.getValue().getClass().getName();
}
private String getTypePropertyName(String unescapedKey) {
return unescapedKey+TYPE_PROPERTY_SUFFIX;
}
private boolean isTypePropertyName(String unescapedKey) {
return unescapedKey.endsWith(TYPE_PROPERTY_SUFFIX) && !unescapedKey.endsWith(TYPE_PROPERTY_SUFFIX_ESCAPE+TYPE_PROPERTY_SUFFIX);
@Override
protected Object get(JSONObject jsonObject, String property) {
return jsonObject.get(property);
}
/**
* If <code>key</code> looks like a result of {@link #getTypePropertyName(String)}, the string is escaped such that
* it does not more and that {@link #unescapePropertyName} will return <code>key</code>.<p>
*/
String escapePropertyName(String key) {
return key.replaceAll(TYPE_PROPERTY_SUFFIX, TYPE_PROPERTY_SUFFIX_ESCAPE+TYPE_PROPERTY_SUFFIX);
@Override
protected boolean hasProperty(JSONObject jsonObject, String property) {
return jsonObject.containsKey(property);
}
String unescapePropertyName(String escapedKey) {
return escapedKey.replaceAll(TYPE_PROPERTY_SUFFIX_ESCAPE+TYPE_PROPERTY_SUFFIX, TYPE_PROPERTY_SUFFIX);
}
private Object serialize(Setting setting) {
switch (setting.getType()) {
case ENUM:
return serialize((EnumSetting<?>) setting);
case LIST:
return serialize((ListSetting<?>) setting);
case MAP:
return serialize((Settings) setting);
case NUMBER:
return serialize((NumberSetting) setting);
case STRING:
return serialize((StringSetting) setting);
default:
throw new IllegalArgumentException("Don't know setting of type "+setting.getType());
@Override
protected JSONArray ToJsonArray(Iterable<Object> values) {
JSONArray jsonArray = new JSONArray();
for(Object value : values) {
jsonArray.add(value);
}
}
private <T extends Enum<T>> String serialize(EnumSetting<T> enumSetting) {
return enumSetting.getValue().name();
return jsonArray;
}
private <T extends Setting> JSONArray serialize(ListSetting<T> listSetting) {
JSONArray result = new JSONArray();
for (T t : listSetting) {
result.add(serialize(t));
}
return result;
}
private Number serialize(NumberSetting numberSetting) {
return numberSetting.getNumber();
}
private String serialize(StringSetting stringSetting) {
return stringSetting.getString();
}
public Settings deserialize(JSONObject json) throws ClassNotFoundException {
final Map<String, Setting> settings = new HashMap<>();
for (Entry<Object, Object> e : json.entrySet()) {
String escapedKey = (String) e.getKey();
String unescapedKey = unescapePropertyName(escapedKey);
if (!isTypePropertyName(escapedKey)) {
final Object value = e.getValue();
String typePropertyName = getTypePropertyName(unescapedKey);
// all properties must specify their type unless they are of type String, JSONArray or Number;
// however, properties of type enum will be represented as strings
final SettingType type;
final Class<?> clazz;
if (json.containsKey(typePropertyName)) {
String[] typeNameAndClassName = ((String) json.get(typePropertyName)).split("/");
type = SettingType.valueOf(typeNameAndClassName[0]);
if (typeNameAndClassName.length > 1) {
clazz = Class.forName(typeNameAndClassName[1]);
} else {
clazz = null;
}
} else {
type = getSettingType(value);
clazz = null;
}
Setting setting = createSettingFromObjectAndType(value, type, clazz);
settings.put(unescapePropertyName(escapedKey), setting);
}
}
return new AbstractSettings() {
@Override
public Map<String, Setting> getNonDefaultSettings() {
return settings;
}
};
}
private SettingType getSettingType(final Object value) {
final SettingType type;
if (value instanceof String) {
type = SettingType.STRING;
} else if (value instanceof Number){
type = SettingType.NUMBER;
} else if (value instanceof JSONArray) {
type = SettingType.LIST;
} else if (value instanceof JSONObject) {
type = SettingType.MAP;
} else {
throw new IllegalArgumentException("Don't know how to de-serialize setting "+value+" of type "+value.getClass());
}
return type;
}
private Setting createSettingFromObjectAndType(Object obj, final SettingType type, Class<?> clazz)
throws ClassNotFoundException {
final Setting setting;
switch (type) {
case ENUM:
Enum<?> value = null;
@SuppressWarnings("unchecked")
Class<Enum<?>> enumClass = (Class<Enum<?>>) clazz;
for (Enum<?> literal : enumClass.getEnumConstants()) {
if (literal.name().equals((String) obj)) {
value = literal;
}
}
setting = new EnumSetting<>(value);
break;
case LIST:
JSONArray array = (JSONArray) obj;
List<Setting> settingList = new ArrayList<>();
for (Object o : array) {
settingList.add(createSettingFromObjectAndType(o, clazz==null?getSettingType(o):SettingType.ENUM, clazz));
}
setting = new ListSetting<>(settingList);
break;
case MAP:
setting = deserialize((JSONObject) obj);
break;
case NUMBER:
setting = new NumberSetting((Number) obj);
break;
case STRING:
setting = new StringSetting((String) obj);
break;
default:
throw new IllegalArgumentException("Don't know how to de-serialize setting "+obj+" of type "+obj.getClass());
}
return setting;
@Override
protected Iterable<Object> fromJsonArray(JSONArray jsonArray) {
return jsonArray;
}
}