001package gudusoft.gsqlparser.resolver2;
002
003import gudusoft.gsqlparser.TBaseType;
004import gudusoft.gsqlparser.TCustomSqlStatement;
005import gudusoft.gsqlparser.TStatementList;
006import gudusoft.gsqlparser.nodes.TObjectName;
007import gudusoft.gsqlparser.resolver2.namespace.OraclePackageNamespace;
008import gudusoft.gsqlparser.stmt.TCursorDeclStmt;
009import gudusoft.gsqlparser.stmt.TVarDeclStmt;
010import gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage;
011
012import java.util.*;
013
014/**
015 * Registry for Oracle packages encountered during scope building.
016 *
017 * <p>Pre-built during the initial scan of statements to allow:
018 * <ul>
019 *   <li>Cross-package references (pkg1 references pkg2.constant)</li>
020 *   <li>Spec/body merging for complete member visibility</li>
021 *   <li>O(1) lookup of package namespaces by name</li>
022 * </ul>
023 *
024 * <p>Usage in ScopeBuilder:
025 * <pre>
026 * // During build() pre-traversal:
027 * packageRegistry = new OraclePackageRegistry();
028 * packageRegistry.buildFromStatements(statements);
029 *
030 * // During visitor traversal (when entering package body):
031 * OraclePackageNamespace pkgNs = packageRegistry.getPackage(packageName);
032 * </pre>
033 */
034public class OraclePackageRegistry {
035
036    /** Map of package name (lowercase) -> OraclePackageNamespace */
037    private final Map<String, OraclePackageNamespace> packagesByName = new LinkedHashMap<>();
038
039    /** Map of qualified name (schema.package, lowercase) -> namespace */
040    private final Map<String, OraclePackageNamespace> packagesByQualifiedName = new LinkedHashMap<>();
041
042    /** Debug flag for logging */
043    private boolean debug = false;
044
045    /**
046     * Create a new package registry.
047     */
048    public OraclePackageRegistry() {
049    }
050
051    /**
052     * Enable/disable debug logging.
053     *
054     * @param debug true to enable debug output
055     */
056    public void setDebug(boolean debug) {
057        this.debug = debug;
058    }
059
060    /**
061     * Build the registry from a list of statements.
062     * Call this during ScopeBuilder pre-traversal.
063     *
064     * @param statements The statement list to scan
065     */
066    public void buildFromStatements(TStatementList statements) {
067        if (statements == null) return;
068
069        for (int i = 0; i < statements.size(); i++) {
070            TCustomSqlStatement stmt = statements.get(i);
071            if (stmt instanceof TPlsqlCreatePackage) {
072                processPackage((TPlsqlCreatePackage) stmt);
073            }
074        }
075    }
076
077    /**
078     * Process a single package statement (spec or body).
079     *
080     * @param pkg The package statement
081     */
082    private void processPackage(TPlsqlCreatePackage pkg) {
083        TObjectName pkgNameNode = pkg.getPackageName();
084        if (pkgNameNode == null) return;
085
086        String packageName = pkgNameNode.getObjectString();
087        if (packageName == null || packageName.isEmpty()) {
088            // Try to get the string representation
089            packageName = pkgNameNode.toString();
090            if (packageName == null || packageName.isEmpty()) return;
091        }
092
093        String schemaName = pkgNameNode.getSchemaString();
094        String nameLower = packageName.toLowerCase();
095        String qualifiedName = schemaName != null && !schemaName.isEmpty()
096            ? (schemaName + "." + packageName).toLowerCase()
097            : nameLower;
098
099        // Get or create namespace
100        OraclePackageNamespace ns = packagesByName.get(nameLower);
101        if (ns == null) {
102            ns = new OraclePackageNamespace(packageName, schemaName, pkg);
103            packagesByName.put(nameLower, ns);
104            packagesByQualifiedName.put(qualifiedName, ns);
105            if (schemaName != null && !schemaName.isEmpty()) {
106                // Also register without schema for unqualified lookup
107                packagesByName.putIfAbsent(nameLower, ns);
108            }
109
110            if (debug) {
111                System.out.println("[OraclePackageRegistry] Created namespace for package: " + packageName);
112            }
113        }
114
115        // Populate members from declarations
116        populatePackageMembers(ns, pkg);
117    }
118
119    /**
120     * Extract and register package members from declarations.
121     *
122     * @param ns The namespace to populate
123     * @param pkg The package AST node
124     */
125    private void populatePackageMembers(OraclePackageNamespace ns, TPlsqlCreatePackage pkg) {
126        TStatementList declarations = pkg.getDeclareStatements();
127        if (declarations == null) return;
128
129        for (int i = 0; i < declarations.size(); i++) {
130            TCustomSqlStatement decl = declarations.get(i);
131
132            if (decl instanceof TVarDeclStmt) {
133                ns.addVariable((TVarDeclStmt) decl);
134                if (debug) {
135                    TVarDeclStmt varDecl = (TVarDeclStmt) decl;
136                    if (varDecl.getElementName() != null) {
137                        System.out.println("[OraclePackageRegistry]   Added variable: " +
138                            varDecl.getElementName().toString());
139                    }
140                }
141            } else if (decl instanceof TCursorDeclStmt) {
142                ns.addCursor((TCursorDeclStmt) decl);
143                if (debug) {
144                    TCursorDeclStmt cursorDecl = (TCursorDeclStmt) decl;
145                    if (cursorDecl.getCursorName() != null) {
146                        System.out.println("[OraclePackageRegistry]   Added cursor: " +
147                            cursorDecl.getCursorName().toString());
148                    }
149                }
150            }
151            // Note: Nested procedures/functions are not added as they have their own scopes
152        }
153
154        // Also check body statements for package body
155        if (pkg.getKind() == TBaseType.kind_create_body) {
156            TStatementList bodyStatements = pkg.getBodyStatements();
157            // Body statements are usually executable code, not declarations
158            // But some package variables might be declared in initialization section
159            // For now, we focus on declareStatements which contains the declarations
160        }
161
162        if (debug) {
163            System.out.println("[OraclePackageRegistry] Package " + ns.getPackageName() +
164                " now has " + ns.getMembers().size() + " members");
165        }
166    }
167
168    /**
169     * Look up a package namespace by name.
170     *
171     * @param name Package name (can be simple or qualified)
172     * @return The namespace, or null if not found
173     */
174    public OraclePackageNamespace getPackage(String name) {
175        if (name == null) return null;
176        String nameLower = name.toLowerCase();
177
178        // Try qualified name first
179        OraclePackageNamespace ns = packagesByQualifiedName.get(nameLower);
180        if (ns != null) return ns;
181
182        // Fall back to simple name
183        return packagesByName.get(nameLower);
184    }
185
186    /**
187     * Check if a name corresponds to a known package.
188     *
189     * @param name The name to check
190     * @return true if this is a known package
191     */
192    public boolean isPackage(String name) {
193        return getPackage(name) != null;
194    }
195
196    /**
197     * Get all registered packages.
198     *
199     * @return Unmodifiable collection of all packages
200     */
201    public Collection<OraclePackageNamespace> getAllPackages() {
202        return Collections.unmodifiableCollection(packagesByName.values());
203    }
204
205    /**
206     * Get the number of registered packages.
207     *
208     * @return The package count
209     */
210    public int size() {
211        return packagesByName.size();
212    }
213
214    /**
215     * Clear the registry.
216     * Call this at reset.
217     */
218    public void clear() {
219        packagesByName.clear();
220        packagesByQualifiedName.clear();
221    }
222
223    @Override
224    public String toString() {
225        return "OraclePackageRegistry(packages=" + packagesByName.size() + ")";
226    }
227}