PrimitiveArrayManager.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.HashMap;
import java.util.Map;
/**
* Manages primitive list arrays with optimized accumulation and finalization. Single
* Responsibility: Coordinates primitive array lifecycle.
*
* <p>Performance optimization: Uses accumulation + sort-at-end pattern for sorted lists (asc/desc)
* to achieve O(P + V log V) complexity instead of O(P × V) quadratic insertion. Insertion-order
* lists still use immediate append for optimal memory efficiency.
*/
public final class PrimitiveArrayManager {
private static final char CACHE_KEY_SEPARATOR = '|';
private final PrimitiveArrayRuleCache ruleCache;
private final PrimitiveArrayNodeFactory arrayFactory;
private final Map<String, ArrayNode> arrayNodes;
private final Map<String, PrimitiveArrayBucket> buckets;
private final Map<String, MappingConfig.OrderDirection> directions;
public PrimitiveArrayManager(final ObjectMapper objectMapper, final MappingConfig config) {
this.ruleCache = new PrimitiveArrayRuleCache(config);
this.arrayFactory = new PrimitiveArrayNodeFactory(objectMapper, config.separator());
this.arrayNodes = new HashMap<>();
this.buckets = new HashMap<>();
this.directions = new HashMap<>();
}
private record AddContext(
String cacheKey,
Path path,
JsonNode value,
ObjectNode targetRoot,
MappingConfig.PrimitiveListRule rule) {}
public boolean isPrimitiveListPath(final String path) {
return ruleCache.isPrimitiveListPath(path);
}
public void finalizePrimitiveArrays() {
final PrimitiveArrayFinalizer finalizer =
new PrimitiveArrayFinalizer(buckets, arrayNodes, directions);
finalizer.finalizeAll();
clearState();
}
public void addValue(
final String scope, final Path path, final JsonNode value, final ObjectNode targetRoot) {
if (isNullValue(value)) {
return;
}
routeValue(scope, path, value, targetRoot);
}
private void routeValue(
final String scope, final Path path, final JsonNode value, final ObjectNode targetRoot) {
final String cacheKey = buildCacheKey(scope, path.absolutePath());
final MappingConfig.PrimitiveListRule rule = ruleCache.getRuleFor(path.absolutePath());
final AddContext context = new AddContext(cacheKey, path, value, targetRoot, rule);
if (shouldInsertImmediately(rule)) {
addImmediately(context);
} else {
accumulateForSorting(context);
}
}
private boolean shouldInsertImmediately(final MappingConfig.PrimitiveListRule rule) {
return rule.orderDirection() == MappingConfig.OrderDirection.insertion;
}
private void addImmediately(final AddContext context) {
final ArrayNode array =
getOrCreateArrayNode(context.cacheKey(), context.path(), context.targetRoot());
final PrimitiveArrayBucket bucket = getOrCreateBucket(context.cacheKey(), context.rule());
if (bucket.shouldAdd(context.value())) {
array.add(context.value());
}
}
private void accumulateForSorting(final AddContext context) {
getOrCreateArrayNode(context.cacheKey(), context.path(), context.targetRoot());
final PrimitiveArrayBucket bucket = getOrCreateBucket(context.cacheKey(), context.rule());
bucket.add(context.value());
directions.put(context.cacheKey(), context.rule().orderDirection());
}
private PrimitiveArrayBucket getOrCreateBucket(
final String cacheKey, final MappingConfig.PrimitiveListRule rule) {
return buckets.computeIfAbsent(cacheKey, k -> new PrimitiveArrayBucket(rule.dedup()));
}
private ArrayNode getOrCreateArrayNode(
final String cacheKey, final Path path, final ObjectNode targetRoot) {
return arrayNodes.computeIfAbsent(
cacheKey, k -> arrayFactory.createAndAttach(targetRoot, path.relativePath()));
}
private String buildCacheKey(final String scope, final String path) {
return scope + CACHE_KEY_SEPARATOR + path;
}
private void clearState() {
arrayNodes.clear();
buckets.clear();
directions.clear();
}
private boolean isNullValue(final JsonNode value) {
return value == null || value.isNull();
}
}