AstToModelMapper.java

package com.taxonomy.dsl.mapper;

import com.taxonomy.dsl.ast.*;
import com.taxonomy.dsl.model.*;

import java.util.*;

/**
 * Maps a {@link DocumentAst} (generic AST) into the {@link CanonicalArchitectureModel}.
 *
 * <p>Known block types are mapped to typed canonical objects. Unknown block types
 * are silently skipped (they remain accessible in the AST for lossless round-trips).
 */
public class AstToModelMapper {

    /**
     * Map a parsed document AST to a canonical architecture model.
     */
    public CanonicalArchitectureModel map(DocumentAst document) {
        CanonicalArchitectureModel model = new CanonicalArchitectureModel();

        for (BlockAst block : document.getBlocks()) {
            switch (block.getKind()) {
                case "element" -> mapElement(block, model);
                case "relation" -> mapRelation(block, model);
                case "requirement" -> mapRequirement(block, model);
                case "mapping" -> mapMapping(block, model);
                case "view" -> mapView(block, model);
                case "evidence" -> mapEvidence(block, model);
                case "source" -> mapSource(block, model);
                case "sourceVersion" -> mapSourceVersion(block, model);
                case "sourceFragment" -> mapSourceFragment(block, model);
                case "requirementSourceLink" -> mapRequirementSourceLink(block, model);
                default -> { /* unknown block type — preserved in AST */ }
            }
        }

        return model;
    }

    private void mapElement(BlockAst block, CanonicalArchitectureModel model) {
        List<String> tokens = block.getHeaderTokens();
        ArchitectureElement el = new ArchitectureElement();

        if (!tokens.isEmpty()) {
            el.setId(tokens.get(0));
        }
        // Parse "type <TypeName>" from header tokens
        for (int i = 1; i < tokens.size() - 1; i++) {
            if ("type".equals(tokens.get(i))) {
                el.setType(tokens.get(i + 1));
            }
        }

        el.setTitle(block.property("title"));
        el.setDescription(block.property("description"));
        el.setTaxonomy(block.property("taxonomy"));
        el.setExtensions(block.getExtensions());

        model.getElements().add(el);
    }

    private void mapRelation(BlockAst block, CanonicalArchitectureModel model) {
        // Header: sourceId RELATION_TYPE targetId
        List<String> tokens = block.getHeaderTokens();
        ArchitectureRelation rel = new ArchitectureRelation();

        if (tokens.size() >= 3) {
            rel.setSourceId(tokens.get(0));
            rel.setRelationType(tokens.get(1));
            rel.setTargetId(tokens.get(2));
        }

        rel.setStatus(block.property("status"));
        String conf = block.property("confidence");
        if (conf != null) {
            try { rel.setConfidence(Double.parseDouble(conf)); }
            catch (NumberFormatException ignored) { /* keep null */ }
        }
        rel.setProvenance(block.property("provenance"));
        rel.setExtensions(block.getExtensions());

        model.getRelations().add(rel);
    }

    private void mapRequirement(BlockAst block, CanonicalArchitectureModel model) {
        List<String> tokens = block.getHeaderTokens();
        ArchitectureRequirement req = new ArchitectureRequirement();

        if (!tokens.isEmpty()) {
            req.setId(tokens.get(0));
        }
        req.setTitle(block.property("title"));
        req.setText(block.property("text"));
        req.setExtensions(block.getExtensions());

        model.getRequirements().add(req);
    }

    private void mapMapping(BlockAst block, CanonicalArchitectureModel model) {
        // Header: REQ-ID -> ELEMENT-ID
        List<String> tokens = block.getHeaderTokens();
        RequirementMapping mapping = new RequirementMapping();

        if (tokens.size() >= 3 && "->".equals(tokens.get(1))) {
            mapping.setRequirementId(tokens.get(0));
            mapping.setElementId(tokens.get(2));
        }

        String score = block.property("score");
        if (score != null) {
            try { mapping.setScore(Double.parseDouble(score)); }
            catch (NumberFormatException ignored) { /* keep null */ }
        }
        mapping.setSource(block.property("source"));
        mapping.setExtensions(block.getExtensions());

        model.getMappings().add(mapping);
    }

    private void mapView(BlockAst block, CanonicalArchitectureModel model) {
        List<String> tokens = block.getHeaderTokens();
        ArchitectureView view = new ArchitectureView();

        if (!tokens.isEmpty()) {
            view.setId(tokens.get(0));
        }
        view.setTitle(block.property("title"));
        view.setIncludes(block.propertyValues("include"));
        view.setLayout(block.property("layout"));
        view.setExtensions(block.getExtensions());

        model.getViews().add(view);
    }

    private void mapEvidence(BlockAst block, CanonicalArchitectureModel model) {
        List<String> tokens = block.getHeaderTokens();
        ArchitectureEvidence ev = new ArchitectureEvidence();

        if (!tokens.isEmpty()) {
            ev.setId(tokens.get(0));
        }

        // Parse "for-relation" property: "SOURCE TYPE TARGET"
        String forRelation = block.property("for-relation");
        if (forRelation != null) {
            String[] parts = forRelation.strip().split("\\s+");
            if (parts.length >= 3) {
                ev.setForRelationSource(parts[0]);
                ev.setForRelationType(parts[1]);
                ev.setForRelationTarget(parts[2]);
            }
        }

        ev.setEvidenceType(block.property("type"));
        ev.setModel(block.property("model"));
        String conf = block.property("confidence");
        if (conf != null) {
            try { ev.setConfidence(Double.parseDouble(conf)); }
            catch (NumberFormatException ignored) { /* keep null */ }
        }
        ev.setSummary(block.property("summary"));
        ev.setExtensions(block.getExtensions());

        model.getEvidence().add(ev);
    }

    private void mapSource(BlockAst block, CanonicalArchitectureModel model) {
        List<String> tokens = block.getHeaderTokens();
        ArchitectureSource src = new ArchitectureSource();

        if (!tokens.isEmpty()) {
            src.setId(tokens.get(0));
        }
        src.setSourceType(block.property("type"));
        src.setTitle(block.property("title"));
        src.setCanonicalIdentifier(block.property("canonicalIdentifier"));
        src.setCanonicalUrl(block.property("canonicalUrl"));
        src.setOriginSystem(block.property("originSystem"));
        src.setLanguage(block.property("language"));
        src.setExtensions(block.getExtensions());

        model.getSources().add(src);
    }

    private void mapSourceVersion(BlockAst block, CanonicalArchitectureModel model) {
        List<String> tokens = block.getHeaderTokens();
        ArchitectureSourceVersion sv = new ArchitectureSourceVersion();

        if (!tokens.isEmpty()) {
            sv.setId(tokens.get(0));
        }
        sv.setSourceId(block.property("source"));
        sv.setVersionLabel(block.property("versionLabel"));
        sv.setRetrievedAt(block.property("retrievedAt"));
        sv.setEffectiveDate(block.property("effectiveDate"));
        sv.setMimeType(block.property("mimeType"));
        sv.setContentHash(block.property("contentHash"));
        sv.setExtensions(block.getExtensions());

        model.getSourceVersions().add(sv);
    }

    private void mapSourceFragment(BlockAst block, CanonicalArchitectureModel model) {
        List<String> tokens = block.getHeaderTokens();
        ArchitectureSourceFragment sf = new ArchitectureSourceFragment();

        if (!tokens.isEmpty()) {
            sf.setId(tokens.get(0));
        }
        sf.setSourceVersionId(block.property("sourceVersion"));
        sf.setSectionPath(block.property("sectionPath"));
        sf.setParagraphRef(block.property("paragraphRef"));
        String pageFrom = block.property("pageFrom");
        if (pageFrom != null) {
            try { sf.setPageFrom(Integer.parseInt(pageFrom)); }
            catch (NumberFormatException ignored) { /* keep null */ }
        }
        String pageTo = block.property("pageTo");
        if (pageTo != null) {
            try { sf.setPageTo(Integer.parseInt(pageTo)); }
            catch (NumberFormatException ignored) { /* keep null */ }
        }
        sf.setText(block.property("text"));
        sf.setFragmentHash(block.property("fragmentHash"));
        sf.setParentFragmentId(block.property("parentFragment"));
        String chunkLevel = block.property("chunkLevel");
        if (chunkLevel != null) {
            try { sf.setChunkLevel(Integer.parseInt(chunkLevel)); }
            catch (NumberFormatException ignored) { /* keep null */ }
        }
        sf.setExtensions(block.getExtensions());

        model.getSourceFragments().add(sf);
    }

    private void mapRequirementSourceLink(BlockAst block, CanonicalArchitectureModel model) {
        List<String> tokens = block.getHeaderTokens();
        ArchitectureRequirementSourceLink rsl = new ArchitectureRequirementSourceLink();

        if (!tokens.isEmpty()) {
            rsl.setId(tokens.get(0));
        }
        rsl.setRequirementId(block.property("requirement"));
        rsl.setSourceId(block.property("source"));
        rsl.setSourceVersionId(block.property("sourceVersion"));
        rsl.setSourceFragmentId(block.property("sourceFragment"));
        rsl.setLinkType(block.property("linkType"));
        String conf = block.property("confidence");
        if (conf != null) {
            try { rsl.setConfidence(Double.parseDouble(conf)); }
            catch (NumberFormatException ignored) { /* keep null */ }
        }
        rsl.setNote(block.property("note"));
        rsl.setExtensions(block.getExtensions());

        model.getRequirementSourceLinks().add(rsl);
    }
}