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}