001package gudusoft.gsqlparser.dlineage.dataflow.model;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.common.structured.StructuredPathSegment;
005import gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind;
006import gudusoft.gsqlparser.dlineage.util.DlineageUtil;
007
008import java.util.ArrayList;
009import java.util.Collections;
010import java.util.List;
011
012/**
013 * The identity of a field node of a STRUCT tree (plan section 4.7): the column the tree hangs below, by object
014 * identity, and the steps below it, each FIELD step keyed by the analysis's identifier rules from the name as written,
015 * every other step by its kind. Two reads of one field of one column give equal keys whatever their spelling the
016 * rules equate ({@code s.`Foo`} and {@code s.foo} in Hive); a field named {@code "a.b"} (one step) and the path
017 * {@code a.b} (two steps) give different keys although they display alike.
018 * <p>
019 * In-process only: the owner is compared by identity, so a key never leaves the analysis that made it.
020 */
021public final class FieldNodeKey {
022
023    /** One step of the path: its kind, and for a FIELD the name's identity key. */
024    public static final class StepKey {
025        private final StructuredPathSegmentKind kind;
026        private final Object nameKey;
027
028        private StepKey(StructuredPathSegmentKind kind, Object nameKey) {
029            this.kind = kind;
030            this.nameKey = nameKey;
031        }
032
033        /** The key of {@code segment} under the analysis's identifier rules. */
034        public static StepKey of(StructuredPathSegment segment) {
035            if (segment.getKind() != StructuredPathSegmentKind.FIELD) {
036                return new StepKey(segment.getKind(), null);
037            }
038            EDbVendor vendor = ModelBindingManager.getGlobalVendor();
039            Object key = vendor == null ? null : DlineageUtil.lexicalColumnKey(vendor, segment.getName());
040            if (key == null) {
041                // no rules bound (a model used outside an analysis): DlineageUtil.sameName then compares ignoring
042                // case, and so does this key
043                key = "\u0000" + segment.getName().toUpperCase(java.util.Locale.ROOT).toLowerCase(java.util.Locale.ROOT);
044            }
045            return new StepKey(segment.getKind(), key);
046        }
047
048        @Override
049        public boolean equals(Object o) {
050            if (this == o) {
051                return true;
052            }
053            if (!(o instanceof StepKey)) {
054                return false;
055            }
056            StepKey other = (StepKey) o;
057            return kind == other.kind && (nameKey == null ? other.nameKey == null : nameKey.equals(other.nameKey));
058        }
059
060        @Override
061        public int hashCode() {
062            return 31 * kind.hashCode() + (nameKey == null ? 0 : nameKey.hashCode());
063        }
064    }
065
066    private final Object owner;
067    private final List<StepKey> steps;
068
069    private FieldNodeKey(Object owner, List<StepKey> steps) {
070        this.owner = owner;
071        this.steps = Collections.unmodifiableList(steps);
072    }
073
074    /** The key of the node {@code segments} below {@code owner} (a column, or a result column). */
075    public static FieldNodeKey of(Object owner, List<StructuredPathSegment> segments) {
076        List<StepKey> steps = new ArrayList<StepKey>(segments.size());
077        for (StructuredPathSegment segment : segments) {
078            steps.add(StepKey.of(segment));
079        }
080        return new FieldNodeKey(owner, steps);
081    }
082
083    /** The key of a table's field path node: its declared column and its path. */
084    public static FieldNodeKey of(TableColumn node) {
085        return of(node.getDeclaredColumn(), node.getFieldPath().getSegments());
086    }
087
088    public Object getOwner() {
089        return owner;
090    }
091
092    public List<StepKey> getSteps() {
093        return steps;
094    }
095
096    @Override
097    public boolean equals(Object o) {
098        if (this == o) {
099            return true;
100        }
101        if (!(o instanceof FieldNodeKey)) {
102            return false;
103        }
104        FieldNodeKey other = (FieldNodeKey) o;
105        return owner == other.owner && steps.equals(other.steps);
106    }
107
108    @Override
109    public int hashCode() {
110        return 31 * System.identityHashCode(owner) + steps.hashCode();
111    }
112}