001package gudusoft.gsqlparser.common.structured;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.List;
006import java.util.Objects;
007
008/**
009 * Typed path under a root column. Display string examples:
010 * {@code nodes[*].key}, {@code nodes1.key}, {@code nodes}.
011 *
012 * <p>The path is immutable. Use the {@link #builder(String)} or the static
013 * {@link #of(String)} entry point with chained mutators to construct.
014 */
015public final class StructuredColumnPath {
016
017    private final String rootColumn;
018    private final List<StructuredPathSegment> segments;
019
020    public StructuredColumnPath(String rootColumn, List<StructuredPathSegment> segments) {
021        if (rootColumn == null || rootColumn.isEmpty()) {
022            throw new IllegalArgumentException("rootColumn must be non-empty");
023        }
024        if (segments == null) {
025            throw new IllegalArgumentException("segments must not be null");
026        }
027        this.rootColumn = rootColumn;
028        this.segments = Collections.unmodifiableList(new ArrayList<>(segments));
029    }
030
031    public String getRootColumn() {
032        return rootColumn;
033    }
034
035    public List<StructuredPathSegment> getSegments() {
036        return segments;
037    }
038
039    public boolean isRootOnly() {
040        return segments.isEmpty();
041    }
042
043    public String toDisplayString() {
044        StringBuilder sb = new StringBuilder(rootColumn);
045        for (StructuredPathSegment seg : segments) {
046            sb.append(seg.toDisplayString());
047        }
048        return sb.toString();
049    }
050
051    /** Returns a new path with one additional segment appended. */
052    public StructuredColumnPath append(StructuredPathSegment segment) {
053        List<StructuredPathSegment> copy = new ArrayList<>(segments);
054        copy.add(segment);
055        return new StructuredColumnPath(rootColumn, copy);
056    }
057
058    public static Builder of(String rootColumn) {
059        return new Builder(rootColumn);
060    }
061
062    public static Builder builder(String rootColumn) {
063        return new Builder(rootColumn);
064    }
065
066    public static final class Builder {
067        private final String rootColumn;
068        private final List<StructuredPathSegment> segments = new ArrayList<>();
069
070        private Builder(String rootColumn) {
071            this.rootColumn = rootColumn;
072        }
073
074        public Builder arrayElement() {
075            segments.add(StructuredPathSegment.arrayElement());
076            return this;
077        }
078
079        public Builder field(String name) {
080            segments.add(StructuredPathSegment.field(name));
081            return this;
082        }
083
084        public Builder mapKey() {
085            segments.add(StructuredPathSegment.mapKey());
086            return this;
087        }
088
089        public Builder mapValue() {
090            segments.add(StructuredPathSegment.mapValue());
091            return this;
092        }
093
094        public StructuredColumnPath build() {
095            return new StructuredColumnPath(rootColumn, segments);
096        }
097    }
098
099    @Override
100    public boolean equals(Object o) {
101        if (this == o) return true;
102        if (!(o instanceof StructuredColumnPath)) return false;
103        StructuredColumnPath other = (StructuredColumnPath) o;
104        return rootColumn.equals(other.rootColumn) && segments.equals(other.segments);
105    }
106
107    @Override
108    public int hashCode() {
109        return Objects.hash(rootColumn, segments);
110    }
111
112    @Override
113    public String toString() {
114        return toDisplayString();
115    }
116}