package helpers;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jsonschema.core.report.ProcessingMessage;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import models.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class implements the application/json encoding algorithm described in
* http://www.w3.org/TR/html-json-forms/#the-application-json-encoding-algorithm
*
* @author fo
*/
public class JSONForm {
public static JsonNode parseFormData(Map<String, String[]> formData) {
return parseFormData(formData, false);
}
public static JsonNode parseFormData(Map<String,String[]> formData, boolean removeEmptyValues) {
List<JsonNode> results = new ArrayList<>();
for (Map.Entry<String, String[]> entry : formData.entrySet()) {
JsonNode context = new ObjectNode(JsonNodeFactory.instance);
String path = entry.getKey();
String[] values = entry.getValue();
List<JSONForm.Step> steps = parsePath(path);
Collections.reverse(steps);
// TODO: implement file inputs
for (JSONForm.Step step : steps) {
if (step.last) {
ArrayNode vals = new ArrayNode(JsonNodeFactory.instance);
for (String value : values) {
if (!value.isEmpty()) {
try {
vals.add(Integer.parseInt(value));
} catch (NumberFormatException notInt) {
try {
vals.add(Double.parseDouble(value));
} catch (NumberFormatException notDouble) {
vals.add(value);
}
}
}
}
if (step.type == Step.Type.Array) {
int index = Integer.parseInt(step.key);
ArrayNode object = new ArrayNode(JsonNodeFactory.instance);
for (int i = 0; i < index; i++) {
object.addNull();
}
if ((!step.append) && (vals.size() == 1)) {
object.insert(index, vals.get(0));
} else if (vals.size() > 0) {
object.insert(index, vals);
}
context = object;
} else {
ObjectNode object = new ObjectNode(JsonNodeFactory.instance);
if ((!step.append) && (vals.size() == 1)) {
object.set(step.key, vals.get(0));
} else if (vals.size() > 0) {
object.set(step.key, vals);
}
context = object;
}
} else {
if (step.type == Step.Type.Array) {
int index = Integer.parseInt(step.key);
ArrayNode object = new ArrayNode(JsonNodeFactory.instance);
for (int i = 0; i < index; i++) {
object.addNull();
}
object.insert(index, context);
context = object;
} else {
ObjectNode object = new ObjectNode(JsonNodeFactory.instance);
object.put(step.key, context);
context = object;
}
}
}
results.add(context);
}
return removeEmptyValues
? setJsonLdTextValues(removeEmptyValues((ObjectNode) merge(results)))
: setJsonLdTextValues((ObjectNode) merge(results));
}
public static List<Map<String, Object>> generateErrorReport(ProcessingReport report) {
List<Map<String, Object>> errorReport = new ArrayList<>();
for (ProcessingMessage message : report) {
ObjectNode messageNode = (ObjectNode) message.asJson();
String messageText = messageNode.get("instance").get("pointer").asText() + ": "
+ messageNode.get("message").asText();
messageNode.put("message", messageText);
switch (messageNode.get("level").asText()) {
case "error":
messageNode.put("level", "danger");
break;
}
errorReport.add(Resource.fromJson(messageNode));
}
return errorReport;
}
private static ObjectNode merge(ObjectNode x, ObjectNode y) {
ObjectNode result = new ObjectNode(JsonNodeFactory.instance);
Set<String> keys = new HashSet<>();
Iterator<?> itx = x.fieldNames();
while (itx.hasNext()) {
String key = itx.next().toString();
keys.add(key);
}
Iterator<?> ity = y.fieldNames();
while (ity.hasNext()) {
String key = ity.next().toString();
keys.add(key);
}
for (String key : keys) {
JsonNode valx = x.get(key);
JsonNode valy = y.get(key);
boolean nullx = (valx == null);
boolean nully = (valy == null);
if (nullx && !nully) {
result.put(key, valy);
} else if (nully && !nullx) {
result.put(key, valx);
} else if (valx instanceof ArrayNode && valy instanceof ArrayNode) {
result.put(key, merge((ArrayNode) valx, (ArrayNode) valy));
} else if (valx instanceof ObjectNode && valy instanceof ObjectNode) {
result.put(key, merge((ObjectNode) valx, (ObjectNode) valy));
} else if (!nullx) {
ArrayNode val = new ArrayNode(JsonNodeFactory.instance);
val.add(valx);
val.add(valy);
result.put(key, val);
}
}
return result;
}
private static ArrayNode merge(ArrayNode x, ArrayNode y) {
ArrayNode result = new ArrayNode(JsonNodeFactory.instance);
int size = Math.max(x.size(), y.size());
for (int i = 0; i < size; i++) {
JsonNode valx = x.get(i);
JsonNode valy = y.get(i);
boolean nullx = (valx == null || valx.isNull());
boolean nully = (valy == null || valy.isNull());
if (nullx && nully) {
result.addNull();
} else if (nullx) {
result.insert(i, valy);
} else if (nully) {
result.insert(i, valx);
} else if (valx instanceof ArrayNode && valy instanceof ArrayNode) {
result.insert(i, merge((ArrayNode) valx, (ArrayNode) valy));
} else if (valx instanceof ObjectNode && valy instanceof ObjectNode) {
result.insert(i, merge((ObjectNode) valx, (ObjectNode) valy));
}
}
return result;
}
public static JsonNode merge(List<JsonNode> nodes) {
ObjectNode merged = new ObjectNode(JsonNodeFactory.instance);
for (JsonNode node : nodes) {
merged = merge(merged, (ObjectNode) node);
}
return merged;
}
private static ObjectNode removeEmptyValues(ObjectNode node) {
ObjectNode result = new ObjectNode((JsonNodeFactory.instance));
Iterator<String> fieldNames = node.fieldNames();
while(fieldNames.hasNext()) {
String fieldName = fieldNames.next();
JsonNode fieldValue = node.get(fieldName);
if (fieldValue.isArray() && fieldValue.size() > 0) {
ArrayNode value = removeEmptyValues((ArrayNode) fieldValue);
//if (value.size() > 0)
result.put(fieldName, value);
} else if (fieldValue.isObject() && fieldValue.size() > 0) {
ObjectNode value = removeEmptyValues((ObjectNode) fieldValue);
//if (value.size() > 0)
result.put(fieldName, value);
} else if (!fieldValue.isArray() && !fieldValue.isObject() && !fieldValue.isNull()) {
result.put(fieldName, fieldValue);
}
}
return result;
}
private static ArrayNode removeEmptyValues(ArrayNode node) {
ArrayNode result = new ArrayNode(JsonNodeFactory.instance);
for (JsonNode arrayValue : node) {
if (arrayValue.isArray() && arrayValue.size() > 0) {
ArrayNode value = removeEmptyValues((ArrayNode) arrayValue);
//if (value.size() > 0)
result.add(value);
} else if (arrayValue.isObject() && arrayValue.size() > 0) {
ObjectNode value = removeEmptyValues((ObjectNode) arrayValue);
//if (value.size() > 0)
result.add(value);
} else if (!arrayValue.isArray() && !arrayValue.isObject() && !arrayValue.isNull()) {
result.add(arrayValue);
}
}
return result;
}
private static ObjectNode setJsonLdTextValues(ObjectNode node) {
ObjectNode result = new ObjectNode((JsonNodeFactory.instance));
if (node.has(JsonLdConstants.LANGUAGE) && node.has(JsonLdConstants.VALUE)) {
result.put(JsonLdConstants.LANGUAGE, node.get(JsonLdConstants.LANGUAGE).asText());
result.put(JsonLdConstants.VALUE, node.get(JsonLdConstants.VALUE).asText());
} else {
Iterator<String> fieldNames = node.fieldNames();
while(fieldNames.hasNext()) {
String fieldName = fieldNames.next();
JsonNode fieldValue = node.get(fieldName);
if (fieldValue.isArray() && fieldValue.size() > 0) {
ArrayNode value = setJsonLdTextValues((ArrayNode) fieldValue);
result.put(fieldName, value);
} else if (fieldValue.isObject() && fieldValue.size() > 0) {
ObjectNode value = setJsonLdTextValues((ObjectNode) fieldValue);
result.put(fieldName, value);
} else if (!fieldValue.isArray() && !fieldValue.isObject()) {
result.put(fieldName, fieldValue);
}
}
}
return result;
}
private static ArrayNode setJsonLdTextValues(ArrayNode node) {
ArrayNode result = new ArrayNode(JsonNodeFactory.instance);
for (JsonNode arrayValue : node) {
if (arrayValue.isArray() && arrayValue.size() > 0) {
ArrayNode value = setJsonLdTextValues((ArrayNode) arrayValue);
result.add(value);
} else if (arrayValue.isObject() && arrayValue.size() > 0) {
ObjectNode value = setJsonLdTextValues((ObjectNode) arrayValue);
result.add(value);
} else if (!arrayValue.isArray() && !arrayValue.isObject()) {
result.add(arrayValue);
}
}
return result;
}
private static class Step {
private static enum Type {
Object, Array
}
public Type type;
public Type nextType;
public String key;
public boolean last;
public boolean append;
public String toString() {
return "{Type: " + type + ", Key: " + key + ", Last: " + last + ", Append: " + append
+ ", Next type: " + nextType + "}";
}
}
private static List<Step> parsePath(String path) {
String original = path;
List<Step> steps = new ArrayList<>();
String firstKey;
int delimiter = path.indexOf("[");
if (delimiter != -1) {
firstKey = path.substring(0, delimiter);
path = path.substring(delimiter);
} else {
firstKey = path;
path = "";
}
if (firstKey.length() == 0) {
return failParsePath(original);
}
Step firstStep = new Step();
firstStep.type = Step.Type.Object;
firstStep.key = firstKey;
steps.add(firstStep);
if (path.equals("")) {
steps.get(steps.size() - 1).last = true;
return steps;
}
Pattern keyPattern = Pattern.compile("^\\[([^\\]]*)\\]");
while (!path.equals("")) {
Matcher keyMatcher = keyPattern.matcher(path);
if (keyMatcher.find()) {
String keyPart = keyMatcher.group(1);
if (keyPart.equals("")) {
steps.get(steps.size() - 1).append = true;
path = path.substring(2);
if (path.length() > 0) {
return failParsePath(original);
}
} else {
Step step = new Step();
step.key = keyPart;
try {
Integer.parseInt(keyPart);
step.type = Step.Type.Array;
} catch (NumberFormatException e) {
step.type = Step.Type.Object;
}
steps.add(step);
path = path.substring(keyPart.length() + 2);
}
} else {
return failParsePath(original);
}
}
for (int i = 0; i < steps.size(); i++) {
if (i == steps.size() - 1) {
steps.get(i).last = true;
} else {
steps.get(i).nextType = steps.get(i + 1).type;
}
}
return steps;
}
private static List<Step> failParsePath(String path) {
List<Step> steps = new ArrayList<>();
Step step = new Step();
step.type = Step.Type.Object;
step.last = true;
step.key = path;
steps.add(step);
return steps;
}
}