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}