
com.xlrit.gears.engine.form.MultipleFormField Maven / Gradle / Ivy
package com.xlrit.gears.engine.form;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.xlrit.gears.base.execution.Execution;
import com.xlrit.gears.base.form.Line;
import com.xlrit.gears.base.meta.Multiple;
import com.xlrit.gears.base.util.JsonUtils;
import com.xlrit.gears.engine.meta.PathEval;
import com.xlrit.gears.engine.meta.PrintOptions;
import com.xlrit.gears.engine.meta.Property;
import com.xlrit.gears.engine.meta.TypeInfo;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
public class MultipleFormField extends FormField {
private static final String ORIGINAL_INDEX = "__original_index";
private final boolean open;
private final String elementName;
private final String baseName;
private final ValueRef basedOnRef;
private final ValueRef collectionRef;
private final LineInfo lineInfo;
public MultipleFormField(FormPath path, Property property, ValueRef basedOnRef, ValueRef collectionRef, LineInfo lineInfo) {
super(path, property, property.getAnnotation(Multiple.class).label());
this.open = property.getAnnotation(Multiple.class).open();
this.baseName = property.getAnnotation(Multiple.class).baseName();
this.elementName = property.getAnnotation(Multiple.class).elementName();
this.basedOnRef = basedOnRef; // can be null
this.collectionRef = collectionRef; // will be null if mode=open
this.lineInfo = checkNotNull(lineInfo);
checkState(open || collectionRef != null, "In closed mode, collectionRef is mandatory");
}
@Override
public TypeInfo getTypeInfo() {
throw new UnsupportedOperationException();
}
public Object getBasedOnValue(Execution execution) {
return basedOnRef == null ? null : basedOnRef.getValue(execution);
}
public List> getCollection(Execution execution) {
return collectionRef == null ? null : (List>) collectionRef.getValue(execution);
}
// === rendering === //
@Override
public ObjectNode renderView(RenderContext ctx, Execution execution) {
ObjectNode fieldNode = ctx.createObjectNode();
fieldNode.set("mode", TextNode.valueOf(open ? "open" : "closed"));
fieldNode.set("name", TextNode.valueOf(getName()));
fieldNode.set("baseName", TextNode.valueOf(baseName));
fieldNode.set("path", TextNode.valueOf(path.toString()));
fieldNode.set("type", TextNode.valueOf("MULTIPLE"));
fieldNode.set("label", TextNode.valueOf(label));
fieldNode.set("elementName", TextNode.valueOf(elementName));
fieldNode.set("traits", renderTraits(ctx));
fieldNode.set("fields", ctx.renderView(lineInfo.getFields(), execution));
return fieldNode;
}
@Override
@SuppressWarnings("rawtypes")
public JsonNode renderModel(RenderContext ctx, Execution execution) {
ArrayNode linesNode = ctx.createArrayNode();
if (open && collectionRef == null) {
// in open mode:
// - we need at least one line
// - the individual lines do *not* have their own execution
linesNode.add(ctx.renderModel(lineInfo.getFields(), execution, getBasedOnValue(execution)));
}
else {
List elements = (List) collectionRef.getValue(execution);
execution.forEach(elementName, elements, elementExecution -> {
Object basedOn = getBasedOnValue(elementExecution);
JsonNode value = ctx.renderModel(lineInfo.getFields(), elementExecution, basedOn);
if (value instanceof ObjectNode lineNode) {
lineNode.put(ORIGINAL_INDEX, elementExecution.getElementIndex(elementName));
}
linesNode.add(value);
});
}
return linesNode;
}
@Override
public JsonNode renderAux(RenderContext ctx, Execution execution) {
ArrayNode linesNode = ctx.createArrayNode();
if (open && collectionRef == null) {
// in open mode the individual lines do not have their own execution
// hence their choices and defaults can be found in the model
linesNode.add(ctx.renderAux(lineInfo.getFields(), execution));
return linesNode;
}
else {
List> elements = getCollection(execution);
execution.forEach(elementName, elements, elementExecution ->
linesNode.add(ctx.renderAux(lineInfo.getFields(), elementExecution))
);
}
return linesNode;
}
// === submission === //
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Object convert(SubmitContext ctx, JsonNode value) {
if (!(value instanceof ArrayNode linesArray)) {
throw new RuntimeException("Value for multiple form field '" + getName() + "' must be an array");
}
List result = new ArrayList<>(linesArray.size());
linesArray.forEach(lineNode -> {
Line line = lineInfo.newInstance();
JsonNode originalIndexNode = lineNode.get(ORIGINAL_INDEX);
if (originalIndexNode != null && originalIndexNode.isInt()) {
int originalIndex = originalIndexNode.asInt();
line.setOriginalIndex(originalIndex);
}
for (FormField innerField : lineInfo.getFields()) {
JsonNode fieldValue = lineNode.get(innerField.getName());
innerField.setValue(line, innerField.convert(ctx, fieldValue));
}
result.add(line);
});
return result;
}
@Override
@SuppressWarnings({"rawtypes"})
public void validate(SubmitContext ctx, Execution execution, Object value, Validation validation) {
if (!(value instanceof List lines)) {
throw new RuntimeException("Value for multiple form field must be a list: " + value);
}
List elements = getElements(execution, lines.size());
execution.forEachLinked(elementName, elements, lines, elementExecution -> {
Object line = elementExecution.getLinkedElement(elementName, lineInfo.getJavaType());
lineInfo.validate(ctx, elementExecution, line, validation);
});
}
private List> getElements(Execution execution, int size) {
return open ? Arrays.asList(new Object[size]) : getCollection(execution);
}
// === (de)serialization ===
@Override
@SuppressWarnings("rawtypes")
public JsonNode serialize(ObjectMapper objectMapper, Object value, PrintOptions printOptions) {
ArrayNode multipleNode = objectMapper.createArrayNode();
Iterable lines = (Iterable) value;
for (Object line : lines) {
multipleNode.add(lineInfo.serialize(line, objectMapper, printOptions));
}
return multipleNode;
}
@Override
public Object deserialize(ObjectMapper objectMapper, JsonNode node) {
return JsonUtils.stream(node)
.map(elem -> lineInfo.deserialize(elem, objectMapper))
.collect(Collectors.toList());
}
// === misc === //
@Override
public R evaluate(String path, Execution execution, FieldFunction f) {
String[] result = PathEval.extractIndex(path);
int index = Integer.parseInt(result[0]); String rest = result[1];
Execution elementExecution = open ? execution : execution.createElementExecution(elementName, getCollection(execution), index);
return lineInfo.evaluate(rest, elementExecution, f);
}
@Override
public String toString() {
return "MultipleFormField[" +
"elementName='" + elementName + "'" +
']';
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy