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}