001package gudusoft.gsqlparser.resolver2.binding;
002
003import gudusoft.gsqlparser.TCustomSqlStatement;
004
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.List;
008import java.util.Objects;
009import java.util.Optional;
010
011/**
012 * One binding diagnostic record produced by the resolver2 binding post-pass.
013 *
014 * <p>Immutable; build via {@link #builder()}. Mirrors the shape of
015 * {@code gudusoft.gsqlparser.catalog.diagnostic.CatalogDiagnostic} on purpose
016 * — consumers see one diagnostic shape across two contexts (catalog load vs.
017 * SQL binding), but the two types are distinct (plan §5.2, R12).</p>
018 *
019 * <p>The public surface deliberately exposes textual values
020 * ({@code objectNameText}, {@code candidates}) instead of resolver2-internal
021 * nodes (plan §15 R5/R6). Diagnostic codes are stable; message text is NOT —
022 * tests assert on stable substrings only.</p>
023 */
024public final class BindingDiagnostic {
025
026    private final BindingDiagnosticSeverity severity;
027    private final BindingDiagnosticCode code;
028    private final String message;
029    private final String objectNameText;
030    private final BindingReferenceSite site;
031    private final TCustomSqlStatement statement;
032    private final List<String> candidates;
033
034    private BindingDiagnostic(Builder b) {
035        if (b.severity == null) {
036            throw new IllegalArgumentException("BindingDiagnostic.severity is required");
037        }
038        if (b.code == null) {
039            throw new IllegalArgumentException("BindingDiagnostic.code is required");
040        }
041        if (b.message == null || b.message.isEmpty()) {
042            throw new IllegalArgumentException("BindingDiagnostic.message is required");
043        }
044        this.severity = b.severity;
045        this.code = b.code;
046        this.message = b.message;
047        this.objectNameText = b.objectNameText;
048        this.site = b.site;
049        this.statement = b.statement;
050        this.candidates = b.candidates == null
051            ? Collections.<String>emptyList()
052            : Collections.unmodifiableList(new ArrayList<String>(b.candidates));
053    }
054
055    public static Builder builder() {
056        return new Builder();
057    }
058
059    public BindingDiagnosticSeverity severity() {
060        return severity;
061    }
062
063    public BindingDiagnosticCode code() {
064        return code;
065    }
066
067    public String message() {
068        return message;
069    }
070
071    public Optional<String> objectNameText() {
072        return Optional.ofNullable(objectNameText);
073    }
074
075    public Optional<BindingReferenceSite> site() {
076        return Optional.ofNullable(site);
077    }
078
079    public Optional<TCustomSqlStatement> statement() {
080        return Optional.ofNullable(statement);
081    }
082
083    public List<String> candidates() {
084        return candidates;
085    }
086
087    @Override
088    public boolean equals(Object o) {
089        if (this == o) return true;
090        if (!(o instanceof BindingDiagnostic)) return false;
091        BindingDiagnostic that = (BindingDiagnostic) o;
092        return severity == that.severity
093            && code == that.code
094            && message.equals(that.message)
095            && Objects.equals(objectNameText, that.objectNameText)
096            && Objects.equals(site, that.site)
097            && statement == that.statement
098            && candidates.equals(that.candidates);
099    }
100
101    @Override
102    public int hashCode() {
103        return Objects.hash(severity, code, message, objectNameText, site,
104            System.identityHashCode(statement), candidates);
105    }
106
107    @Override
108    public String toString() {
109        StringBuilder sb = new StringBuilder("BindingDiagnostic{")
110            .append(severity).append(' ').append(code).append(": ").append(message);
111        if (objectNameText != null) sb.append(" ref=").append(objectNameText);
112        if (site != null) sb.append(" site=").append(site);
113        if (!candidates.isEmpty()) sb.append(" candidates=").append(candidates);
114        return sb.append('}').toString();
115    }
116
117    public static final class Builder {
118
119        private BindingDiagnosticSeverity severity;
120        private BindingDiagnosticCode code;
121        private String message;
122        private String objectNameText;
123        private BindingReferenceSite site;
124        private TCustomSqlStatement statement;
125        private List<String> candidates;
126
127        private Builder() {
128        }
129
130        public Builder severity(BindingDiagnosticSeverity v) {
131            this.severity = v;
132            return this;
133        }
134
135        public Builder code(BindingDiagnosticCode v) {
136            this.code = v;
137            return this;
138        }
139
140        public Builder message(String v) {
141            this.message = v;
142            return this;
143        }
144
145        public Builder objectNameText(String v) {
146            this.objectNameText = v;
147            return this;
148        }
149
150        public Builder site(BindingReferenceSite v) {
151            this.site = v;
152            return this;
153        }
154
155        public Builder statement(TCustomSqlStatement v) {
156            this.statement = v;
157            return this;
158        }
159
160        public Builder candidates(List<String> v) {
161            this.candidates = v;
162            return this;
163        }
164
165        public BindingDiagnostic build() {
166            return new BindingDiagnostic(this);
167        }
168    }
169}