RowGraphAssembler.java
package io.github.pojotools.flat2pojo.core.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.github.pojotools.flat2pojo.core.config.MappingConfig;
import io.github.pojotools.flat2pojo.core.engine.Path;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
/**
* Assembles object graphs from flat rows by processing list rules and direct values. Single
* Responsibility: Builds nested JSON tree structure from flat key-value rows.
*/
final class RowGraphAssembler implements RowProcessor {
private final ObjectNode root;
private final AssemblerDependencies dependencies;
private final ProcessingContext context;
private final ListRuleProcessor listRuleProcessor;
private final DirectValueWriter directValueWriter;
private final Function<Map<String, ?>, Map<String, ?>> preprocessor;
RowGraphAssembler(final AssemblerDependencies dependencies, final ProcessingContext context) {
this.dependencies = dependencies;
this.root = dependencies.objectMapper().createObjectNode();
this.context = context;
this.directValueWriter = new DirectValueWriter(context, dependencies.primitiveArrayManager());
this.listRuleProcessor = new ListRuleProcessor(dependencies, context);
this.preprocessor = buildPreprocessor(context.config());
}
@Override
public void processRow(final Map<String, ?> row) {
final Map<String, ?> preprocessed = preprocessor.apply(row);
final Map<String, JsonNode> rowValues =
dependencies.valueTransformer().transformRowValuesToJsonNodes(preprocessed);
final Set<String> skippedListPaths = processListRules(rowValues);
processDirectValues(rowValues, skippedListPaths);
}
@Override
public <T> T materialize(final Class<T> type) {
dependencies.arrayManager().finalizeArrays(root);
dependencies.primitiveArrayManager().finalizePrimitiveArrays();
return dependencies.materializer().materialize(root, type);
}
private static Function<Map<String, ?>, Map<String, ?>> buildPreprocessor(
final MappingConfig config) {
return config
.valuePreprocessor()
.map(p -> (Function<Map<String, ?>, Map<String, ?>>) p::process)
.orElse(Function.identity());
}
private Set<String> processListRules(final Map<String, JsonNode> rowValues) {
final Set<String> skippedListPaths = new HashSet<>();
for (final MappingConfig.ListRule rule : context.config().lists()) {
listRuleProcessor.processRule(rowValues, skippedListPaths, rule, root);
}
return skippedListPaths;
}
private void processDirectValues(
final Map<String, JsonNode> rowValues, final Set<String> skippedListPaths) {
for (final var entry : rowValues.entrySet()) {
final String absolutePath = entry.getKey();
if (isDirectValuePath(absolutePath, skippedListPaths)) {
// absolute and relative paths are the same for direct values
final Path path = new Path(absolutePath, absolutePath);
directValueWriter.writeDirectly(root, path, entry.getValue());
}
}
}
private boolean isDirectValuePath(final String path, final Set<String> skippedListPaths) {
return isEligibleForDirectWrite(path, skippedListPaths);
}
private boolean isEligibleForDirectWrite(final String path, final Set<String> skippedListPaths) {
final boolean underAnyList = context.hierarchyCache().isUnderAnyList(path);
final boolean skipped = context.pathResolver().isUnderAny(path, skippedListPaths);
return !underAnyList && !skipped;
}
}