Flat2PojoCore.java

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

import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.pojotools.flat2pojo.core.api.Flat2Pojo;
import io.github.pojotools.flat2pojo.core.config.ListHierarchyCache;
import io.github.pojotools.flat2pojo.core.config.MappingConfig;
import io.github.pojotools.flat2pojo.core.config.MappingConfigLoader;
import io.github.pojotools.flat2pojo.core.engine.ArrayManager;
import io.github.pojotools.flat2pojo.core.engine.PrimitiveArrayManager;
import io.github.pojotools.flat2pojo.core.engine.ValueTransformer;
import io.github.pojotools.flat2pojo.core.util.PathResolver;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

public final class Flat2PojoCore implements Flat2Pojo {
  private final ObjectMapper objectMapper;

  public Flat2PojoCore(ObjectMapper objectMapper) {
    this.objectMapper = objectMapper;
  }

  @Override
  public <T> Optional<T> convertOptional(
      Map<String, ?> flatRow, Class<T> type, MappingConfig config) {
    List<T> all = convertAll(List.of(flatRow), type, config);
    return all.isEmpty() ? Optional.empty() : Optional.of(all.getFirst());
  }

  /**
   * Converts flat key-value maps into structured POJOs using hierarchical list grouping.
   *
   * <p>Algorithm: Validate → Group by rootKeys → Process each group → Materialize to POJO
   *
   * @param rows flat key-value maps (e.g., from CSV, database JOIN results)
   * @param type target POJO class to convert to
   * @param config mapping configuration defining list rules, separators, conflict policies
   * @return list of structured POJOs, one per root group
   */
  @Override
  public <T> List<T> convertAll(
      final List<? extends Map<String, ?>> rows, final Class<T> type, final MappingConfig config) {
    MappingConfigLoader.validateHierarchy(config);
    final ProcessingPipeline pipeline = buildProcessingPipeline(config);

    return config.rootKeys().isEmpty()
        ? convertWithoutGrouping(rows, type, pipeline)
        : convertWithGrouping(rows, type, config, pipeline);
  }

  private ProcessingPipeline buildProcessingPipeline(final MappingConfig config) {
    final AssemblerDependencies dependencies = buildAssemblerDependencies(config);
    final ProcessingContext context = buildProcessingContext(config);
    return new ProcessingPipeline(dependencies, context);
  }

  private ProcessingContext buildProcessingContext(final MappingConfig config) {
    final PathResolver pathResolver = new PathResolver(config.separator());
    final ListHierarchyCache hierarchyCache = new ListHierarchyCache(config, pathResolver);
    return new ProcessingContext(config, hierarchyCache, pathResolver);
  }

  private AssemblerDependencies buildAssemblerDependencies(final MappingConfig config) {
    return AssemblerDependencies.builder()
        .objectMapper(objectMapper)
        .arrayManager(new ArrayManager(objectMapper, config))
        .valueTransformer(new ValueTransformer(objectMapper, config))
        .primitiveArrayManager(new PrimitiveArrayManager(objectMapper, config))
        .materializer(new ResultMaterializer(objectMapper))
        .build();
  }

  private <T> List<T> convertWithoutGrouping(
      final List<? extends Map<String, ?>> rows,
      final Class<T> type,
      final ProcessingPipeline pipeline) {
    final RowProcessor processor = pipeline.createAssembler();
    rows.forEach(processor::processRow);
    return List.of(processor.materialize(type));
  }

  private <T> List<T> convertWithGrouping(
      final List<? extends Map<String, ?>> rows,
      final Class<T> type,
      final MappingConfig config,
      final ProcessingPipeline pipeline) {
    final Map<Object, List<Map<String, ?>>> rowGroups =
        RootKeyGrouper.groupByRootKeys(rows, config.rootKeys());
    final List<T> results = new ArrayList<>(rowGroups.size());
    for (final List<Map<String, ?>> groupRows : rowGroups.values()) {
      results.add(processGroup(groupRows, type, pipeline));
    }
    return results;
  }

  private <T> T processGroup(
      final List<Map<String, ?>> groupRows,
      final Class<T> type,
      final ProcessingPipeline pipeline) {
    final RowProcessor processor = pipeline.createAssembler();
    groupRows.forEach(processor::processRow);
    return processor.materialize(type);
  }

  @Override
  public <T> Stream<T> stream(
      final Iterator<? extends Map<String, ?>> rows,
      final Class<T> type,
      final MappingConfig config) {
    final List<Map<String, ?>> list = new ArrayList<>();
    rows.forEachRemaining(list::add);
    return convertAll(list, type, config).stream();
  }
}