All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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