001package gudusoft.gsqlparser.ir.bound;
002
003import gudusoft.gsqlparser.ir.builder.common.DynamicSqlExtraction;
004
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.LinkedHashMap;
008import java.util.List;
009import java.util.Map;
010
011/**
012 * Aggregation root for the Bound IR phase output.
013 * <p>
014 * Contains all scopes, symbols, and references produced by the bound IR builder.
015 */
016public class BoundProgram {
017
018    /** All scopes in the program (root scope first). */
019    private final List<BoundScope> scopes;
020
021    /** Global routine symbol table indexed by routine ID (deterministic iteration order). */
022    private final Map<String, BoundRoutineSymbol> routineIndex;
023
024    /** Multi-value index: uppercase routine name → list of routines with that name. */
025    private final Map<String, List<BoundRoutineSymbol>> routinesByName;
026
027    /** All object references found during binding. */
028    private final List<BoundObjectRef> allObjectRefs;
029
030    /** All column references found during binding. */
031    private final List<BoundColumnRef> allColumnRefs;
032
033    /** All routine references found during binding. */
034    private final List<BoundRoutineRef> allRoutineRefs;
035
036    /** Dynamic SQL extractions (sidecar collection). */
037    private final List<DynamicSqlExtraction> dynamicSqlExtractions;
038
039    /** Unsupported construct counter (dialect/feature → count). */
040    private final Map<String, Integer> unsupportedCounters;
041
042    public BoundProgram() {
043        this.scopes = new ArrayList<BoundScope>();
044        this.routineIndex = new LinkedHashMap<String, BoundRoutineSymbol>();
045        this.routinesByName = new LinkedHashMap<String, List<BoundRoutineSymbol>>();
046        this.allObjectRefs = new ArrayList<BoundObjectRef>();
047        this.allColumnRefs = new ArrayList<BoundColumnRef>();
048        this.allRoutineRefs = new ArrayList<BoundRoutineRef>();
049        this.dynamicSqlExtractions = new ArrayList<DynamicSqlExtraction>();
050        this.unsupportedCounters = new LinkedHashMap<String, Integer>();
051    }
052
053    public BoundProgram(List<BoundScope> scopes,
054                        Map<String, BoundRoutineSymbol> routineIndex,
055                        List<BoundObjectRef> allObjectRefs,
056                        List<BoundColumnRef> allColumnRefs,
057                        List<BoundRoutineRef> allRoutineRefs) {
058        this.scopes = scopes != null ? scopes : new ArrayList<BoundScope>();
059        this.routineIndex = routineIndex != null
060                ? new LinkedHashMap<String, BoundRoutineSymbol>(routineIndex)
061                : new LinkedHashMap<String, BoundRoutineSymbol>();
062        this.routinesByName = new LinkedHashMap<String, List<BoundRoutineSymbol>>();
063        // Rebuild the by-name index from routineIndex
064        for (BoundRoutineSymbol sym : this.routineIndex.values()) {
065            addToNameIndex(sym);
066        }
067        this.allObjectRefs = allObjectRefs != null ? allObjectRefs : new ArrayList<BoundObjectRef>();
068        this.allColumnRefs = allColumnRefs != null ? allColumnRefs : new ArrayList<BoundColumnRef>();
069        this.allRoutineRefs = allRoutineRefs != null ? allRoutineRefs : new ArrayList<BoundRoutineRef>();
070        this.dynamicSqlExtractions = new ArrayList<DynamicSqlExtraction>();
071        this.unsupportedCounters = new LinkedHashMap<String, Integer>();
072    }
073
074    public List<BoundScope> getScopes() { return scopes; }
075    public Map<String, BoundRoutineSymbol> getRoutineIndex() {
076        return Collections.unmodifiableMap(routineIndex);
077    }
078    public Map<String, List<BoundRoutineSymbol>> getRoutinesByName() {
079        return Collections.unmodifiableMap(routinesByName);
080    }
081    public List<BoundObjectRef> getAllObjectRefs() { return allObjectRefs; }
082    public List<BoundColumnRef> getAllColumnRefs() { return allColumnRefs; }
083    public List<BoundRoutineRef> getAllRoutineRefs() { return allRoutineRefs; }
084
085    public void addScope(BoundScope scope) { scopes.add(scope); }
086
087    public void registerRoutine(BoundRoutineSymbol routine) {
088        routineIndex.put(routine.getRoutineId(), routine);
089        addToNameIndex(routine);
090    }
091
092    /**
093     * Removes a routine by its ID. Used to merge forward declarations
094     * with their implementations.
095     */
096    public void unregisterRoutine(String routineId) {
097        BoundRoutineSymbol removed = routineIndex.remove(routineId);
098        if (removed != null) {
099            removeFromNameIndex(removed);
100        }
101    }
102
103    private void removeFromNameIndex(BoundRoutineSymbol routine) {
104        String nameKey = routine.getRoutineName().toUpperCase();
105        List<BoundRoutineSymbol> list = routinesByName.get(nameKey);
106        if (list != null) {
107            list.remove(routine);
108            if (list.isEmpty()) {
109                routinesByName.remove(nameKey);
110            }
111        }
112    }
113
114    private void addToNameIndex(BoundRoutineSymbol routine) {
115        String nameKey = routine.getRoutineName().toUpperCase();
116        List<BoundRoutineSymbol> list = routinesByName.get(nameKey);
117        if (list == null) {
118            list = new ArrayList<BoundRoutineSymbol>();
119            routinesByName.put(nameKey, list);
120        }
121        list.add(routine);
122    }
123
124    public void addObjectRef(BoundObjectRef ref) { allObjectRefs.add(ref); }
125    public void addColumnRef(BoundColumnRef ref) { allColumnRefs.add(ref); }
126    public void addRoutineRef(BoundRoutineRef ref) { allRoutineRefs.add(ref); }
127
128    // ---- Dynamic SQL Extractions ----
129
130    public List<DynamicSqlExtraction> getDynamicSqlExtractions() {
131        return Collections.unmodifiableList(dynamicSqlExtractions);
132    }
133
134    public void addDynamicSqlExtraction(DynamicSqlExtraction extraction) {
135        dynamicSqlExtractions.add(extraction);
136    }
137
138    // ---- Unsupported Construct Counters ----
139
140    public Map<String, Integer> getUnsupportedCounters() {
141        return Collections.unmodifiableMap(unsupportedCounters);
142    }
143
144    public void incrementUnsupported(String key) {
145        Integer count = unsupportedCounters.get(key);
146        unsupportedCounters.put(key, count != null ? count + 1 : 1);
147    }
148
149    /**
150     * Looks up a routine by its ID.
151     */
152    public BoundRoutineSymbol lookupRoutine(String routineId) {
153        return routineIndex.get(routineId);
154    }
155
156    /**
157     * Replaces the entire routine ref list (used after post-resolution pass).
158     */
159    public void replaceRoutineRefs(List<BoundRoutineRef> resolved) {
160        allRoutineRefs.clear();
161        allRoutineRefs.addAll(resolved);
162    }
163
164    @Override
165    public String toString() {
166        return "BoundProgram{scopes=" + scopes.size()
167                + ", routines=" + routineIndex.size()
168                + ", objectRefs=" + allObjectRefs.size()
169                + ", columnRefs=" + allColumnRefs.size()
170                + ", routineRefs=" + allRoutineRefs.size()
171                + ", dynamicSql=" + dynamicSqlExtractions.size() + "}";
172    }
173}