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}