001package gudusoft.gsqlparser.dlineage.symbol; 002 003import gudusoft.gsqlparser.dlineage.dataflow.model.ModelBindingManager; 004import gudusoft.gsqlparser.sqlenv.CanonKey; 005import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 006import gudusoft.gsqlparser.sqlenv.IdentifierInputForm; 007import gudusoft.gsqlparser.sqlenv.IdentifierService; 008 009/** 010 * The key of a local symbol of the lineage model: a variable, a cursor, a declared type, a trigger correlation name or 011 * a table variable, by the scope it is declared in and its name as written. 012 * 013 * <p>The scope is the declaring routine's frozen scope (see {@link LocalScopes}), or an overload pool's 014 * ({@link IdentityScope}), or null at top level. The name is keyed by the analysis's identifier rules under the object 015 * type of its namespace. Declared locals (variables, cursors, declared types, table variables) share one namespace, as 016 * a cursor and a variable of one name are one name in a declare section; a trigger's correlation names are another.</p> 017 */ 018public final class LocalSymbolKey { 019 020 /** Which names a local symbol competes with. */ 021 public enum Namespace { 022 /** Variables, cursors, declared types and table variables of a declare section or parameter list. */ 023 DECLARED(ESQLDataObjectType.dotParameter), 024 /** A trigger's correlation names ({@code :NEW}, {@code :OLD}, REFERENCING aliases): aliases of a table. */ 025 CORRELATION(ESQLDataObjectType.dotTable); 026 027 final ESQLDataObjectType type; 028 029 Namespace(ESQLDataObjectType type) { 030 this.type = type; 031 } 032 } 033 034 /** What a local symbol is; its namespace decides its identity and the object type its name is compared under. */ 035 public enum Kind { 036 VARIABLE(Namespace.DECLARED), 037 CURSOR(Namespace.DECLARED), 038 TYPE(Namespace.DECLARED), 039 TABLE_VARIABLE(Namespace.DECLARED), 040 CORRELATION(Namespace.CORRELATION); 041 042 final Namespace namespace; 043 044 Kind(Namespace namespace) { 045 this.namespace = namespace; 046 } 047 048 public Namespace getNamespace() { 049 return namespace; 050 } 051 } 052 053 private final Object scope; 054 /** The name's CanonKey, or its exact spelling (a String) when the rules cannot key it. */ 055 private final Object name; 056 /** The name as written: to key it again under another namespace's rules. Not part of the identity. */ 057 private final String written; 058 private final IdentifierService service; 059 private final Kind kind; 060 private final int hash; 061 062 private LocalSymbolKey(Object scope, Object name, String written, IdentifierService service, Kind kind) { 063 this.scope = scope; 064 this.name = name; 065 this.written = written; 066 this.service = service; 067 this.kind = kind; 068 this.hash = (31 * (scope == null ? 0 : scope.hashCode()) + name.hashCode()) * 31 + kind.namespace.hashCode(); 069 } 070 071 /** 072 * The key of {@code name}, a single identifier as written (a leading {@code :} already removed), declared in 073 * {@code scope}; null only for an empty name. A spelling the rules cannot read keys by its exact spelling. 074 */ 075 public static LocalSymbolKey of(Object scope, String name, Kind kind) { 076 return of(scope, name, kind, service()); 077 } 078 079 public static LocalSymbolKey of(Object scope, String name, Kind kind, IdentifierService service) { 080 if (name == null || name.isEmpty() || kind == null) { 081 return null; 082 } 083 if (service == null) { 084 // No identifier rules known on this thread: the exact spelling only. 085 return new LocalSymbolKey(scope, name, name, null, kind); 086 } 087 try { 088 return new LocalSymbolKey(scope, 089 service.canonKeyOf(name, kind.namespace.type, IdentifierInputForm.SQL_LEXICAL), name, service, kind); 090 } catch (RuntimeException unkeyable) { 091 // A spelling the rules cannot read is still one name: it matches its exact spelling only, never a fold. 092 return new LocalSymbolKey(scope, name, name, service, kind); 093 } 094 } 095 096 private static IdentifierService service() { 097 return ModelBindingManager.getGlobalIdentifierService(); 098 } 099 100 public Object getScope() { 101 return scope; 102 } 103 104 public Kind getKind() { 105 return kind; 106 } 107 108 /** The same name in another scope (a lookup walking a lexical chain). */ 109 public LocalSymbolKey inScope(Object otherScope) { 110 return new LocalSymbolKey(otherScope, name, written, service, kind); 111 } 112 113 /** The same written name and scope, keyed as a trigger correlation name (under that namespace's rules). */ 114 public LocalSymbolKey asCorrelation() { 115 return kind == Kind.CORRELATION ? this : of(scope, written, Kind.CORRELATION, service); 116 } 117 118 @Override 119 public boolean equals(Object o) { 120 if (this == o) { 121 return true; 122 } 123 if (!(o instanceof LocalSymbolKey)) { 124 return false; 125 } 126 LocalSymbolKey that = (LocalSymbolKey) o; 127 return hash == that.hash && kind.namespace == that.kind.namespace && name.equals(that.name) 128 && (scope == null ? that.scope == null : scope.equals(that.scope)); 129 } 130 131 @Override 132 public int hashCode() { 133 return hash; 134 } 135 136 /** For diagnostics only. */ 137 @Override 138 public String toString() { 139 return (scope == null ? "" : scope + ".") + (name instanceof CanonKey ? ((CanonKey) name).getCanonText() 140 : "'" + name + "'") + "<" + kind + ">"; 141 } 142}