001package io.avaje.http.generator.core;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Collections;
006import java.util.HashSet;
007import java.util.LinkedHashSet;
008import java.util.List;
009import java.util.Set;
010
011public class PathSegments {
012
013  static final PathSegments EMPTY = new PathSegments(new Chunks(), Collections.emptySet());
014
015  static PathSegments parse(String fullPath) {
016
017    Set<Segment> segments = new LinkedHashSet<>();
018
019    Chunks chunks = new Chunks();
020    if ("/".equals(fullPath)) {
021      chunks.literal("/");
022
023    } else {
024      for (String section : fullPath.split("/")) {
025        if (!section.isEmpty()) {
026          chunks.literal("/");
027          if (section.startsWith(":")) {
028            final String name = section.substring(1);
029            Segment segment = createSegment(name);
030            segments.add(segment);
031            chunks.named(segment.path(name));
032
033          } else if ((section.startsWith("{") && (section.endsWith("}")))) {
034            String name = section.substring(1, section.length() - 1);
035            Segment segment = createSegment(name);
036            segments.add(segment);
037            chunks.named(segment.path(name));
038
039          } else {
040            chunks.literal(section);
041          }
042        }
043      }
044    }
045
046    return new PathSegments(chunks, segments);
047  }
048
049  private static Segment createSegment(String val) {
050    String[] matrixSplit = val.split(";");
051    if (matrixSplit.length == 1) {
052      return new Segment(matrixSplit[0]);
053    }
054    Set<String> matrixKeys = new HashSet<>(Arrays.asList(matrixSplit).subList(1, matrixSplit.length));
055    return new Segment(matrixSplit[0], matrixKeys);
056  }
057
058  private final Chunks chunks;
059
060  private final Set<Segment> segments;
061
062  private final List<Segment> withMatrixs = new ArrayList<>();
063
064  private final Set<String> allNames = new HashSet<>();
065
066  private PathSegments(Chunks chunks, Set<Segment> segments) {
067    this.chunks = chunks;
068    this.segments = segments;
069    for (Segment segment : segments) {
070      segment.addNames(allNames);
071      if (segment.hasMatrixParams()) {
072        withMatrixs.add(segment);
073      }
074    }
075  }
076
077
078  public boolean contains(String varName) {
079    return allNames.contains(varName);
080  }
081
082  public List<Segment> matrixSegments() {
083    return withMatrixs;
084  }
085
086  public Segment segment(String varName) {
087
088    for (Segment segment : segments) {
089      if (segment.isPathParameter(varName)) {
090        return segment;
091      }
092    }
093    return null;
094  }
095
096  /**
097   * Return full path with <code>{}</code for named path params.
098   */
099  public String fullPath() {
100    return fullPath("{", "}");
101  }
102
103  /**
104   * Return full path with colon for named path params (Javalin).
105   */
106  public String fullPathColon() {
107    return fullPath(":", "");
108  }
109
110  private String fullPath(String prefix, String suffix) {
111    return chunks.fullPath(prefix, suffix);
112  }
113
114  public static class Segment {
115
116    private final String name;
117
118    /**
119     * Matrix keys.
120     */
121    private final Set<String> matrixKeys;
122
123    /**
124     * Variable names the matrix map to (Java method param names).
125     */
126    private final Set<String> matrixVarNames;
127
128    Segment(String name) {
129      this.name = name;
130      this.matrixKeys = null;
131      this.matrixVarNames = null;
132    }
133
134    Segment(String name, Set<String> matrixKeys) {
135      this.name = name;
136      this.matrixKeys = matrixKeys;
137      this.matrixVarNames = new HashSet<>();
138      for (String key : matrixKeys) {
139        matrixVarNames.add(combine(name, key));
140      }
141    }
142
143    void addNames(Set<String> allNames) {
144      allNames.add(name);
145    }
146
147    boolean hasMatrixParams() {
148      return matrixKeys != null && !matrixKeys.isEmpty();
149    }
150
151    private String combine(String name, String key) {
152      return name + Character.toUpperCase(key.charAt(0)) + key.substring(1);
153    }
154
155    Set<String> matrixKeys() {
156      return matrixKeys;
157    }
158
159    String name() {
160      return name;
161    }
162
163    boolean isPathParameter(String varName) {
164      return name.equals(varName) || (matrixKeys != null && (matrixVarNames.contains(varName) || matrixKeys.contains(varName)));
165    }
166
167    /**
168     * Reading the value from a segment (rather than directly from pathParam).
169     */
170    void writeGetVal(Append writer, String varName, PlatformAdapter platform) {
171      if (!hasMatrixParams()) {
172        platform.writeReadParameter(writer, ParamType.PATHPARAM, name);
173      } else {
174        writer.append("%s_segment.", name);
175        if (name.equals(varName)) {
176          writer.append("val()");
177        } else {
178          writer.append("matrix(\"%s\")", matrixKey(varName));
179        }
180      }
181    }
182
183    private String matrixKey(String varName) {
184      if (!varName.startsWith(name)) {
185        return varName;
186      }
187      String key = varName.substring(name.length());
188      return Character.toLowerCase(key.charAt(0)) + key.substring(1);
189    }
190
191    public void writeCreateSegment(Append writer, PlatformAdapter platform) {
192      writer.append(platform.indent());
193      writer.append("  PathSegment %s_segment = PathSegment.of(", name);
194      platform.writeReadParameter(writer, ParamType.PATHPARAM, name + "_segment");
195      writer.append(");").eol();
196    }
197
198    boolean isRequired(String varName) {
199      return name.equals(varName);
200    }
201
202    String path(String section) {
203      if (!hasMatrixParams()) {
204        return section;
205      }
206      return name + "_segment";
207    }
208  }
209
210  private static class Chunks {
211    private final List<Chunk> chunks = new ArrayList<>();
212
213    void named(String name) {
214      chunks.add(new Chunk(name));
215    }
216
217    void literal(String val) {
218      chunks.add(new LiteralChunk(val));
219    }
220
221    String fullPath(String prefix, String suffix) {
222      StringBuilder sb = new StringBuilder();
223      for (Chunk chunk : chunks) {
224        chunk.append(sb, prefix, suffix);
225      }
226      return sb.toString();
227    }
228  }
229
230  private static class LiteralChunk extends Chunk {
231    private LiteralChunk(String value) {
232      super(value);
233    }
234
235    @Override
236    void append(StringBuilder fullPath, String prefix, String suffix) {
237      fullPath.append(value);
238    }
239  }
240
241  private static class Chunk {
242    final String value;
243    private Chunk(String value) {
244      this.value = value;
245    }
246
247    void append(StringBuilder fullPath, String prefix, String suffix) {
248      fullPath.append(prefix).append(value).append(suffix);
249    }
250  }
251
252}