ArrayManager.java

package io.github.pojotools.flat2pojo.core.engine;

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 io.github.pojotools.flat2pojo.core.config.MappingConfig;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

/**
 * Manages list array lifecycle with grouping and sorting. Single Responsibility: Coordinates array
 * operations.
 */
public final class ArrayManager {
  private final ObjectMapper objectMapper;
  private final ArrayNodeResolver arrayResolver;
  private final CompositeKeyExtractor keyExtractor;
  private final ComparatorBuilder comparatorBuilder;
  private final IdentityHashMap<ArrayNode, ArrayBucket> buckets;
  private final IdentityHashMap<ArrayNode, List<Comparator<ObjectNode>>> comparators;

  public ArrayManager(final ObjectMapper objectMapper, final MappingConfig config) {
    this.objectMapper = objectMapper;
    this.arrayResolver = new ArrayNodeResolver(config.separator());
    this.keyExtractor = new CompositeKeyExtractor(config.separator());
    this.comparatorBuilder = new ComparatorBuilder(config.separator());
    this.buckets = new IdentityHashMap<>();
    this.comparators = new IdentityHashMap<>();
    comparatorBuilder.precomputeComparators(config);
  }

  public ObjectNode upsertListElement(
      final ObjectNode base,
      final String relativeListPath,
      final Map<String, JsonNode> rowValues,
      final MappingConfig.ListRule rule) {
    final ArrayNode arrayNode = arrayResolver.resolveArrayNode(base, relativeListPath);
    final ArrayBucket bucket = ensureBucket(arrayNode, rule);
    final CompositeKey key = keyExtractor.extractFrom(rowValues, rule);
    return upsertElement(bucket, key);
  }

  public void finalizeArrays(final ObjectNode root) {
    final ArrayFinalizer finalizer = new ArrayFinalizer(buckets, comparators);
    finalizer.finalizeArrays(root);
    clearState();
  }

  private ArrayBucket ensureBucket(final ArrayNode arrayNode, final MappingConfig.ListRule rule) {
    buckets.computeIfAbsent(arrayNode, k -> new ArrayBucket());
    comparators.computeIfAbsent(
        arrayNode, k -> comparatorBuilder.getComparatorsForPath(rule.path()));
    return buckets.get(arrayNode);
  }

  private ObjectNode upsertElement(final ArrayBucket bucket, final CompositeKey key) {
    return key == null ? null : bucket.upsert(key, objectMapper.createObjectNode());
  }

  private void clearState() {
    buckets.clear();
    comparators.clear();
  }
}