ArrayFinalizer.java

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

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;

/**
 * Finalizes array nodes by applying sorting and deduplication. Single Responsibility: Array
 * finalization logic only.
 */
final class ArrayFinalizer {
  private final IdentityHashMap<ArrayNode, ArrayBucket> buckets;
  private final IdentityHashMap<ArrayNode, List<Comparator<ObjectNode>>> comparators;

  ArrayFinalizer(
      final IdentityHashMap<ArrayNode, ArrayBucket> buckets,
      final IdentityHashMap<ArrayNode, List<Comparator<ObjectNode>>> comparators) {
    this.buckets = buckets;
    this.comparators = comparators;
  }

  void finalizeArrays(final ObjectNode root) {
    final Deque<JsonNode> stack = new ArrayDeque<>();
    stack.push(root);
    while (!stack.isEmpty()) {
      final JsonNode current = stack.pop();
      if (current instanceof ObjectNode objectNode) {
        processObjectNode(objectNode, stack);
      } else if (current instanceof ArrayNode arrayNode) {
        finalizeArrayNode(arrayNode, stack);
      }
    }
  }

  private void processObjectNode(final ObjectNode objectNode, final Deque<JsonNode> stack) {
    final Iterator<String> fieldIterator = objectNode.fieldNames();
    final List<String> fieldNames = new ArrayList<>();
    fieldIterator.forEachRemaining(fieldNames::add);
    for (final String fieldName : fieldNames) {
      final JsonNode value = objectNode.get(fieldName);
      if (value instanceof ObjectNode || value instanceof ArrayNode) {
        stack.push(value);
      }
    }
  }

  private void finalizeArrayNode(final ArrayNode arrayNode, final Deque<JsonNode> stack) {
    applyBucketOrdering(arrayNode);
    pushChildrenToStack(arrayNode, stack);
  }

  private void applyBucketOrdering(final ArrayNode arrayNode) {
    final ArrayBucket bucket = buckets.get(arrayNode);
    if (bucket == null) {
      return;
    }
    final List<Comparator<ObjectNode>> nodeComparators =
        comparators.getOrDefault(arrayNode, List.of());
    arrayNode.removeAll();
    for (final ObjectNode element : bucket.ordered(nodeComparators)) {
      arrayNode.add(element);
    }
  }

  private void pushChildrenToStack(final ArrayNode arrayNode, final Deque<JsonNode> stack) {
    for (final JsonNode child : arrayNode) {
      stack.push(child);
    }
  }
}