001package io.avaje.http.generator.core;
002
003import io.avaje.http.api.BeanParam;
004import io.avaje.http.api.Cookie;
005import io.avaje.http.api.Default;
006import io.avaje.http.api.Form;
007import io.avaje.http.api.FormParam;
008import io.avaje.http.api.Header;
009import io.avaje.http.api.QueryParam;
010import io.avaje.http.generator.core.openapi.MethodDocBuilder;
011import io.avaje.http.generator.core.openapi.MethodParamDocBuilder;
012
013import javax.lang.model.element.Element;
014import javax.lang.model.element.TypeElement;
015
016public class ElementReader {
017
018  private final ProcessingContext ctx;
019  private final Element element;
020  private final String rawType;
021  private final String shortType;
022  private final TypeHandler typeHandler;
023  private final String varName;
024  private final String snakeName;
025  private final boolean formMarker;
026  private final boolean contextType;
027
028  private String paramName;
029  private ParamType paramType;
030  private boolean impliedParamType;
031  private String paramDefault;
032
033  private boolean notNullKotlin;
034  //private boolean notNullJavax;
035
036  ElementReader(Element element, ProcessingContext ctx, ParamType defaultType, boolean formMarker) {
037    this(element, Util.typeDef(element.asType()), ctx, defaultType, formMarker);
038  }
039
040  ElementReader(Element element, String rawType, ProcessingContext ctx, ParamType defaultType, boolean formMarker) {
041    this.ctx = ctx;
042    this.element = element;
043    this.rawType = rawType;
044    this.shortType = Util.shortName(rawType);
045    this.contextType = ctx.platform().isContextType(rawType);
046    this.typeHandler = TypeMap.get(rawType);
047    this.formMarker = formMarker;
048    this.varName = element.getSimpleName().toString();
049    this.snakeName = Util.snakeCase(varName);
050    this.paramName = varName;
051    if (!contextType) {
052      readAnnotations(element, defaultType);
053    } else {
054      paramType = ParamType.CONTEXT;
055    }
056  }
057
058  private void readAnnotations(Element element, ParamType defaultType) {
059
060    notNullKotlin = (element.getAnnotation(org.jetbrains.annotations.NotNull.class) != null);
061    //notNullJavax = (element.getAnnotation(javax.validation.constraints.NotNull.class) != null);
062
063    Default defaultVal = element.getAnnotation(Default.class);
064    if (defaultVal != null) {
065      this.paramDefault = defaultVal.value();
066    }
067    Form form = element.getAnnotation(Form.class);
068    if (form != null) {
069      this.paramType = ParamType.FORM;
070      return;
071    }
072    BeanParam beanParam = element.getAnnotation(BeanParam.class);
073    if (beanParam != null) {
074      this.paramType = ParamType.BEANPARAM;
075      return;
076    }
077    QueryParam queryParam = element.getAnnotation(QueryParam.class);
078    if (queryParam != null) {
079      this.paramName = nameFrom(queryParam.value(), varName);
080      this.paramType = ParamType.QUERYPARAM;
081      return;
082    }
083    FormParam formParam = element.getAnnotation(FormParam.class);
084    if (formParam != null) {
085      this.paramName = nameFrom(formParam.value(), varName);
086      this.paramType = ParamType.FORMPARAM;
087      return;
088    }
089    Cookie cookieParam = element.getAnnotation(Cookie.class);
090    if (cookieParam != null) {
091      this.paramName = nameFrom(cookieParam.value(), varName);
092      this.paramType = ParamType.COOKIE;
093      this.paramDefault = null;
094      return;
095    }
096    Header headerParam = element.getAnnotation(Header.class);
097    if (headerParam != null) {
098      this.paramName = nameFrom(headerParam.value(), Util.initcapSnake(snakeName));
099      this.paramType = ParamType.HEADER;
100      this.paramDefault = null;
101      return;
102    }
103    if (paramType == null) {
104      this.impliedParamType = true;
105      if (typeHandler != null) {
106        // a scalar type that we know how to convert
107        this.paramType = defaultType;
108      } else {
109        this.paramType = formMarker ? ParamType.FORM : ParamType.BODY;
110      }
111    }
112  }
113
114  @Override
115  public String toString() {
116    return varName + " type:" + rawType + " paramType:" + paramType + " dft:" + paramDefault;
117  }
118
119  private String nameFrom(String name, String defaultName) {
120    if (name != null && !name.isEmpty()) {
121      return name;
122    }
123    return defaultName;
124  }
125
126  public String getVarName() {
127    return varName;
128  }
129
130  private boolean hasParamDefault() {
131    return paramDefault != null && !paramDefault.isEmpty();
132  }
133
134  private boolean isPlatformContext() {
135    return contextType;
136  }
137
138  private String platformVariable() {
139    return ctx.platform().platformVariable(rawType);
140  }
141
142  private String shortType() {
143    if (typeHandler != null) {
144      return typeHandler.shortName();
145    } else {
146      return shortType;
147    }
148  }
149
150  void addImports(ControllerReader bean) {
151    if (typeHandler != null) {
152      String importType = typeHandler.getImportType();
153      if (importType != null) {
154        bean.addImportType(rawType);
155      }
156    } else {
157      bean.addImportType(rawType);
158    }
159  }
160
161  void writeParamName(Append writer) {
162    if (isPlatformContext()) {
163      writer.append(platformVariable());
164    } else {
165      writer.append(varName);
166    }
167  }
168
169  /**
170   * Build the OpenAPI documentation for this parameter.
171   */
172  void buildApiDocumentation(MethodDocBuilder methodDoc) {
173    if (!isPlatformContext()) {
174      new MethodParamDocBuilder(methodDoc, this).build();
175    }
176  }
177
178  void writeValidate(Append writer) {
179    if (!isPlatformContext() && typeHandler == null) {
180      writer.append("validator.validate(%s);", varName).eol();
181      writer.append("      ");
182    }
183  }
184
185  void writeCtxGet(Append writer, PathSegments segments) {
186    if (isPlatformContext()) {
187      // no conversion for this parameter
188      return;
189    }
190    if (paramType == ParamType.BODY && ctx.platform().isBodyMethodParam()) {
191      // body passed as method parameter (Helidon)
192      return;
193    }
194    String shortType = shortType();
195    writer.append("%s  %s %s = ", ctx.platform().indent(), shortType, varName);
196    if (setValue(writer, segments, shortType)) {
197      writer.append(";").eol();
198    }
199  }
200
201  void setValue(Append writer) {
202    setValue(writer, PathSegments.EMPTY, shortType());
203  }
204
205  private boolean setValue(Append writer, PathSegments segments, String shortType) {
206//    if (formMarker && impliedParamType && typeHandler == null) {
207//      if (ParamType.FORM != paramType) {
208//        throw new IllegalStateException("Don't get here?");
209//      }
210////      // @Form on method and this type is a "bean" so treat is as a form bean
211////      writeForm(writer, shortType, varName, ParamType.FORMPARAM);
212////      paramType = ParamType.FORM;
213////      return false;
214//    }
215    if (ParamType.FORM == paramType) {
216      writeForm(writer, shortType, varName, ParamType.FORMPARAM);
217      return false;
218    }
219    if (ParamType.BEANPARAM == paramType) {
220      writeForm(writer, shortType, varName, ParamType.QUERYPARAM);
221      return false;
222    }
223    if (impliedParamType) {
224      PathSegments.Segment segment = segments.segment(varName);
225      if (segment != null) {
226        // path or matrix parameter
227        boolean requiredParam = segment.isRequired(varName);
228        String asMethod = (typeHandler == null) ? null : (requiredParam) ? typeHandler.asMethod() : typeHandler.toMethod();
229        if (asMethod != null) {
230          writer.append(asMethod);
231        }
232        segment.writeGetVal(writer, varName, ctx.platform());
233        if (asMethod != null) {
234          writer.append(")");
235        }
236        paramType = ParamType.PATHPARAM;
237        return true;
238      }
239    }
240
241    String asMethod = (typeHandler == null) ? null : typeHandler.toMethod();
242    if (asMethod != null) {
243      writer.append(asMethod);
244    }
245
246    if (typeHandler == null) {
247      // this is a body (POST, PATCH)
248      writer.append(ctx.platform().bodyAsClass(shortType));
249
250    } else {
251      if (hasParamDefault()) {
252        ctx.platform().writeReadParameter(writer, paramType, paramName, paramDefault);
253      } else {
254        boolean checkNull = notNullKotlin || (paramType == ParamType.FORMPARAM && typeHandler.isPrimitive());
255        if (checkNull) {
256          writer.append("checkNull(");
257        }
258        ctx.platform().writeReadParameter(writer, paramType, paramName);
259        //writer.append("ctx.%s(\"%s\")", paramType, paramName);
260        if (checkNull) {
261          writer.append(", \"%s\")", paramName);
262        }
263      }
264    }
265
266    if (asMethod != null) {
267      writer.append(")");
268    }
269    return true;
270  }
271
272  private void writeForm(Append writer, String shortType, String varName, ParamType defaultParamType) {
273    TypeElement formBeanType = ctx.getTypeElement(rawType);
274    BeanParamReader form = new BeanParamReader(ctx, formBeanType, varName, shortType, defaultParamType);
275    form.write(writer);
276  }
277
278  public ParamType getParamType() {
279    return paramType;
280  }
281
282  public String getParamName() {
283    return paramName;
284  }
285
286  public String getShortType() {
287    return shortType;
288  }
289
290  public String getRawType() {
291    return rawType;
292  }
293
294  public Element getElement() {
295    return element;
296  }
297}