001package gudusoft.gsqlparser.resolver2.model;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.List;
006
007/**
008 * Enhanced column source that supports multiple reference traceability.
009 *
010 * <p>A normalized column name may correspond to multiple original references in SQL.
011 * For example, in Oracle SQL {@code SELECT "Column1", column1 FROM dual}:</p>
012 * <ul>
013 *   <li>Both "Column1" and column1 normalize to COLUMN1</li>
014 *   <li>This class stores the canonical ColumnSource plus all original references</li>
015 *   <li>Output shows one column (deduplication)</li>
016 *   <li>All original positions can be traced back</li>
017 * </ul>
018 *
019 * <p>Design pattern: Aggregator for Many-to-One mapping</p>
020 * <ul>
021 *   <li>Multiple original identifiers (different syntax) → One semantic identifier</li>
022 *   <li>Maintains link from semantic identifier back to all original identifiers</li>
023 * </ul>
024 *
025 * @since 3.1.0.9
026 */
027public class ColumnSourceWithReferences {
028
029    /** Normalized column name (unique identifier) */
030    private final String normalizedName;
031
032    /** Canonical column source (for resolution) */
033    private final ColumnSource canonicalSource;
034
035    /** All original references (in order of appearance) */
036    private final List<ColumnReference> references;
037
038    /**
039     * Create a new column source with references.
040     *
041     * @param normalizedName normalized column name
042     * @param canonicalSource the canonical column source
043     */
044    public ColumnSourceWithReferences(String normalizedName, ColumnSource canonicalSource) {
045        this.normalizedName = normalizedName;
046        this.canonicalSource = canonicalSource;
047        this.references = new ArrayList<>();
048    }
049
050    /**
051     * Add an original reference.
052     *
053     * @param ref the column reference to add
054     */
055    public void addReference(ColumnReference ref) {
056        if (ref != null && !references.contains(ref)) {
057            references.add(ref);
058        }
059    }
060
061    /**
062     * Get all original references.
063     *
064     * @return unmodifiable list of all references in order of appearance
065     */
066    public List<ColumnReference> getAllReferences() {
067        return Collections.unmodifiableList(references);
068    }
069
070    /**
071     * Get the number of references.
072     *
073     * @return reference count
074     */
075    public int getReferenceCount() {
076        return references.size();
077    }
078
079    /**
080     * Check if there are multiple references (indicating deduplication occurred).
081     *
082     * @return true if more than one reference exists
083     */
084    public boolean hasMultipleReferences() {
085        return references.size() > 1;
086    }
087
088    /**
089     * Get the first (canonical) reference.
090     *
091     * @return the first reference, or null if none
092     */
093    public ColumnReference getCanonicalReference() {
094        return references.isEmpty() ? null : references.get(0);
095    }
096
097    /**
098     * Get the normalized name.
099     *
100     * @return normalized column name
101     */
102    public String getNormalizedName() {
103        return normalizedName;
104    }
105
106    /**
107     * Get the canonical column source.
108     *
109     * @return the column source for resolution
110     */
111    public ColumnSource getCanonicalSource() {
112        return canonicalSource;
113    }
114
115    /**
116     * Get all original texts from all references.
117     *
118     * @return list of original text strings
119     */
120    public List<String> getAllOriginalTexts() {
121        List<String> texts = new ArrayList<>(references.size());
122        for (ColumnReference ref : references) {
123            if (ref.getOriginalText() != null) {
124                texts.add(ref.getOriginalText());
125            }
126        }
127        return texts;
128    }
129
130    @Override
131    public String toString() {
132        StringBuilder sb = new StringBuilder();
133        sb.append(normalizedName);
134        if (references.size() > 1) {
135            sb.append(" (").append(references.size()).append(" refs: ");
136            for (int i = 0; i < references.size(); i++) {
137                if (i > 0) sb.append(", ");
138                sb.append(references.get(i).getOriginalText());
139            }
140            sb.append(")");
141        } else if (references.size() == 1) {
142            ColumnReference ref = references.get(0);
143            if (!normalizedName.equals(ref.getOriginalText())) {
144                sb.append(" (from ").append(ref.getOriginalText()).append(")");
145            }
146        }
147        return sb.toString();
148    }
149}