package io.divolte.server.config; import static com.fasterxml.jackson.core.JsonToken.*; import java.io.IOException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.Properties; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.google.common.base.Joiner; import javax.annotation.ParametersAreNonnullByDefault; @ParametersAreNonnullByDefault public class PropertiesDeserializer extends JsonDeserializer<Properties> { private final static Joiner DOT_JOINER = Joiner.on('.'); private final static Joiner COMMA_JOINER = Joiner.on(','); @Override public Properties deserialize(JsonParser p, DeserializationContext ctx) throws IOException { if (START_OBJECT == p.getCurrentToken()) { final Properties properties = new Properties(); final Deque<String> stack = new ArrayDeque<>(); final ArrayList<String> array = new ArrayList<>(); for (JsonToken nextToken = p.nextToken(); nextToken != END_OBJECT || !stack.isEmpty(); nextToken = p.nextToken()) { switch(nextToken) { case FIELD_NAME: stack.addLast(p.getCurrentName()); break; case VALUE_STRING: case VALUE_NUMBER_INT: case VALUE_NUMBER_FLOAT: case VALUE_TRUE: case VALUE_FALSE: if (p.getParsingContext().inArray()) { array.add(p.getText()); } else { properties.put(DOT_JOINER.join(stack), p.getText()); stack.removeLast(); } break; case START_OBJECT: if (p.getParsingContext().inArray()) { throw ctx.mappingException("Nested objects within arrays not allowed in Properties object."); } break; case END_OBJECT: stack.removeLast(); break; case START_ARRAY: array.clear(); break; case END_ARRAY: properties.put(DOT_JOINER.join(stack), COMMA_JOINER.join(array)); stack.removeLast(); break; case VALUE_NULL: throw ctx.mappingException("Null values not allowed in Properties object."); case VALUE_EMBEDDED_OBJECT: throw ctx.mappingException("Embedded object not allowed as part of Properties object."); case NOT_AVAILABLE: break; } } return properties; } else { throw ctx.mappingException("Expected nested object for Properties mapping."); } } }