ReportRendererRegistry.java

package com.taxonomy.architecture.report;

import com.taxonomy.extension.api.report.ReportFormatDescriptor;
import com.taxonomy.extension.api.report.ReportRendererExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * Spring registry for report-renderer extensions grouped by report family and format.
 */
@Service
public class ReportRendererRegistry {

    private final Map<String, Map<String, ReportRendererExtension>> byReportType;

    /** Backward-compatible constructor for focused unit tests. */
    public ReportRendererRegistry(List<ReportRendererExtension> extensions) {
        this(extensions, List.of());
    }

    /**
     * Production constructor applying application-layer decorators before registration.
     */
    @Autowired
    public ReportRendererRegistry(
            List<ReportRendererExtension> extensions,
            List<ReportRendererDecorator> decorators) {
        List<ReportRendererExtension> safeExtensions = extensions == null
                ? List.of() : new ArrayList<>(extensions);
        List<ReportRendererDecorator> safeDecorators = decorators == null
                ? List.of() : List.copyOf(decorators);

        List<ReportRendererExtension> effectiveExtensions = safeExtensions.stream()
                .map(extension -> applyDecorators(extension, safeDecorators))
                .toList();

        for (ReportRendererExtension extension : effectiveExtensions) {
            Objects.requireNonNull(extension, "report renderer extension must not be null");
            Objects.requireNonNull(extension.descriptor(),
                    "report renderer descriptor must not be null");
            Objects.requireNonNull(extension.reportModelType(),
                    "report renderer model type must not be null");
            normalizeRequired(extension.reportTypeId(), "report type ID");
            normalizeRequired(extension.descriptor().id(), "report format ID");
        }

        Map<String, Map<String, ReportRendererExtension>> reportTypes = new LinkedHashMap<>();
        effectiveExtensions.stream()
                .sorted(Comparator
                        .comparing((ReportRendererExtension extension) ->
                                normalize(extension.reportTypeId()))
                        .thenComparing(extension -> normalize(extension.descriptor().id())))
                .forEach(extension -> {
                    String reportType = normalizeRequired(
                            extension.reportTypeId(), "report type ID");
                    String formatId = normalizeRequired(
                            extension.descriptor().id(), "report format ID");
                    Map<String, ReportRendererExtension> formats =
                            reportTypes.computeIfAbsent(reportType,
                                    ignored -> new LinkedHashMap<>());
                    ReportRendererExtension previous =
                            formats.putIfAbsent(formatId, extension);
                    if (previous != null) {
                        if (ReportRendererExtension.DEFAULT_REPORT_TYPE_ID
                                .equals(reportType)) {
                            throw new IllegalStateException(
                                    "Duplicate report renderer format ID: " + formatId);
                        }
                        throw new IllegalStateException(
                                "Duplicate report renderer for report type " + reportType
                                        + " and format " + formatId);
                    }
                });

        Map<String, Map<String, ReportRendererExtension>> immutable =
                new LinkedHashMap<>();
        reportTypes.forEach((reportType, formats) ->
                immutable.put(reportType, Map.copyOf(formats)));
        this.byReportType = Map.copyOf(immutable);
    }

    /** Backward-compatible lookup for the architecture-report family. */
    public ReportRendererExtension getRequired(String formatId) {
        return getRequired(ReportRendererExtension.DEFAULT_REPORT_TYPE_ID, formatId);
    }

    public ReportRendererExtension getRequired(String reportTypeId, String formatId) {
        return findByFormatId(reportTypeId, formatId)
                .orElseThrow(() -> new IllegalArgumentException(
                        "Unknown report renderer: " + reportTypeId + "/" + formatId));
    }

    /** Backward-compatible lookup for the architecture-report family. */
    public Optional<ReportRendererExtension> findByFormatId(String formatId) {
        return findByFormatId(ReportRendererExtension.DEFAULT_REPORT_TYPE_ID, formatId);
    }

    public Optional<ReportRendererExtension> findByFormatId(
            String reportTypeId,
            String formatId) {
        if (reportTypeId == null || reportTypeId.isBlank()
                || formatId == null || formatId.isBlank()) {
            return Optional.empty();
        }
        Map<String, ReportRendererExtension> formats =
                byReportType.get(normalize(reportTypeId));
        if (formats == null) {
            return Optional.empty();
        }
        return Optional.ofNullable(formats.get(normalize(formatId)));
    }

    /** Backward-compatible descriptor list for architecture reports. */
    public List<ReportFormatDescriptor> listDescriptors() {
        return listDescriptors(ReportRendererExtension.DEFAULT_REPORT_TYPE_ID);
    }

    public List<ReportFormatDescriptor> listDescriptors(String reportTypeId) {
        if (reportTypeId == null || reportTypeId.isBlank()) {
            return List.of();
        }
        Map<String, ReportRendererExtension> formats =
                byReportType.get(normalize(reportTypeId));
        if (formats == null) {
            return List.of();
        }
        return formats.values().stream()
                .map(ReportRendererExtension::descriptor)
                .sorted(Comparator.comparing(ReportFormatDescriptor::id))
                .toList();
    }

    public List<String> listReportTypeIds() {
        return byReportType.keySet().stream().sorted().toList();
    }

    private static ReportRendererExtension applyDecorators(
            ReportRendererExtension source,
            List<ReportRendererDecorator> decorators) {
        ReportRendererExtension effective =
                Objects.requireNonNull(source, "report renderer extension must not be null");
        for (ReportRendererDecorator decorator : decorators) {
            Objects.requireNonNull(decorator, "report renderer decorator must not be null");
            if (!decorator.supports(effective)) {
                continue;
            }
            effective = Objects.requireNonNull(
                    decorator.decorate(effective),
                    "report renderer decorator returned null");
        }
        return effective;
    }

    private String normalizeRequired(String value, String label) {
        if (value == null || value.isBlank()) {
            throw new IllegalArgumentException(label + " must not be blank");
        }
        String normalized = normalize(value);
        if (normalized.contains(":")) {
            throw new IllegalArgumentException(label + " must not contain ':'");
        }
        return normalized;
    }

    private String normalize(String value) {
        return value.trim().toLowerCase(Locale.ROOT);
    }
}