Skip to content

Support generic jsonb types #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jan 6, 2023
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,13 @@ If [Avaje-Jsonb](https://github.com/avaje/avaje-jsonb) is detected, http generat
public class WidgetController$Route implements WebRoutes {

private final WidgetController controller;
private final JsonType<java.util.List<org.example.hello.WidgetController.Widget>> listWidgetJsonType;
private final JsonType<org.example.hello.WidgetController.Widget> widgetJsonType;
private final JsonType<List<Widget>> listWidgetJsonType;
private final JsonType<Widget> widgetJsonType;

public WidgetController$Route(WidgetController controller, Jsonb jsonB) {
this.controller = controller;
this.listWidgetJsonType = jsonB.type(org.example.hello.WidgetController.Widget.class).list();
this.widgetJsonType = jsonB.type(org.example.hello.WidgetController.Widget.class);
this.listWidgetJsonType = jsonB.type(Widget.class).list();
this.widgetJsonType = jsonB.type(Widget.class);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import javax.lang.model.element.TypeElement;
import java.util.Set;
import java.util.stream.Collectors;

/**
* Write code to register Web route for a given controller method.
Expand All @@ -22,24 +23,21 @@ class ClientMethodWriter {
private final UType returnType;
private MethodParam bodyHandlerParam;
private String methodGenericParams = "";
private final boolean useJsonb;

ClientMethodWriter(MethodReader method, Append writer, ProcessingContext ctx) {
ClientMethodWriter(MethodReader method, Append writer, ProcessingContext ctx, boolean useJsonb) {
this.method = method;
this.writer = writer;
this.webMethod = method.webMethod();
this.ctx = ctx;
this.returnType = Util.parseType(method.returnType());
this.useJsonb = useJsonb;
}

void addImportTypes(ControllerReader reader) {
reader.addImportTypes(returnType.importTypes());
for (final MethodParam param : method.params()) {
final var type = param.utype();
final var type0 = type.param0();
final var type1 = type.param1();
reader.addImportType(type.mainType().replace("[]", ""));
if (type0 != null) reader.addImportType(type0.replace("[]", ""));
if (type1 != null) reader.addImportType(type1.replace("[]", ""));
reader.addImportTypes(param.utype().importTypes());
}
}

Expand Down Expand Up @@ -116,37 +114,54 @@ private void writeEnd() {

private void writeSyncResponse() {
writer.append(" ");
String type0 = returnType.mainType();
String type1 = returnType.param0();
writeResponse(type0, type1);
writeResponse(returnType);
}

private void writeAsyncResponse() {
writer.append(" .async()");
String type0 = returnType.param0();
String type1 = returnType.param1();
writeResponse(type0, type1);
writeResponse(returnType.paramRaw());
}

private void writeCallResponse() {
writer.append(" .call()");
String type0 = returnType.param0();
String type1 = returnType.param1();
writeResponse(type0, type1);
writeResponse(returnType.paramRaw());
}

private void writeResponse(String type0, String type1) {
if (isList(type0)) {
writer.append(".list(%s.class);", Util.shortName(type1)).eol();
} else if (isStream(type0)) {
writer.append(".stream(%s.class);", Util.shortName(type1)).eol();
} else if (isHttpResponse(type0)){
private void writeResponse(UType type) {
final var mainType = type.mainType();
final var param1 = type.paramRaw();
if (isList(mainType)) {
writer.append(".list(");
writeGeneric(param1);
} else if (isStream(mainType)) {
writer.append(".stream(");
writeGeneric(param1);
} else if (isHttpResponse(mainType)) {
writeWithHandler();
} else {
writer.append(".bean(%s.class);", Util.shortName(type0)).eol();
writer.append(".bean(");
writeGeneric(type);
}
}

void writeGeneric(UType type) {
if (useJsonb && type.isGeneric()) {
final var params =
type.importTypes().stream()
.skip(1)
.map(Util::shortName)
.collect(Collectors.joining(".class, "));

writer.append(
"Types.newParameterizedType(%s.class, %s.class)", Util.shortName(type.mainType()), params);
} else {
writer.append("%s.class", Util.shortName(type.mainType()));
}
writer.append(");");

writer.eol();
}

private void writeWithHandler() {
if (bodyHandlerParam != null) {
writer.append(".handler(%s);", bodyHandlerParam.name()).eol();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ public class ClientProcessor extends AbstractProcessor {

protected ProcessingContext ctx;

private final boolean useJsonB;

public ClientProcessor() {
var jsonBOnClassPath = false;
try {
Class.forName("io.avaje.jsonb.Jsonb");
jsonBOnClassPath = true;
} catch (final ClassNotFoundException e) {
}
useJsonB = jsonBOnClassPath;
}

public ClientProcessor(boolean useJsonb) {
useJsonB = useJsonb;
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
Expand Down Expand Up @@ -107,7 +123,7 @@ private void writeClient(Element controller) {
}

protected String writeClientAdapter(ProcessingContext ctx, ControllerReader reader) throws IOException {
return new ClientWriter(reader, ctx).write();
return new ClientWriter(reader, ctx, useJsonB).write();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ class ClientWriter extends BaseControllerWriter {
private static final String SUFFIX = "HttpClient";

private final List<ClientMethodWriter> methodList = new ArrayList<>();
private final boolean useJsonb;

ClientWriter(ControllerReader reader, ProcessingContext ctx) throws IOException {
ClientWriter(ControllerReader reader, ProcessingContext ctx, boolean useJsonB) throws IOException {
super(reader, ctx, SUFFIX);
reader.addImportType(HTTP_CLIENT_CONTEXT);
reader.addImportType(HTTP_API_PROVIDER);
this.useJsonb = useJsonB;
readMethods();
if (useJsonB) reader.addImportType("io.avaje.jsonb.Types");
}

@Override
Expand All @@ -38,7 +41,7 @@ protected String initPackageName(String originName) {
private void readMethods() {
for (MethodReader method : reader.methods()) {
if (method.isWebMethod()) {
ClientMethodWriter methodWriter = new ClientMethodWriter(method, writer, ctx);
final var methodWriter = new ClientMethodWriter(method, writer, ctx, useJsonb);
methodWriter.addImportTypes(reader);
methodList.add(methodWriter);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class JsonBUtil {
private JsonBUtil() {}
Expand Down Expand Up @@ -40,23 +41,46 @@ public static void writeJsonbType(UType type, Append writer) {

writer.append(" this.%sJsonType = jsonB.type(", type.shortName());
if (!type.isGeneric()) {
writer.append("%s.class)", PrimitiveUtil.wrap(type.full()));
writer.append("%s.class)", Util.shortName(PrimitiveUtil.wrap(type.full())));
} else {
switch (type.mainType()) {
case "java.util.List":
writer.append("%s.class).list()", type.param0());
writeType(type.paramRaw(), writer);
writer.append(".list()");
break;
case "java.util.Set":
writer.append("%s.class).set()", type.param0());
writeType(type.paramRaw(), writer);
writer.append(".set()");
break;
case "java.util.Map":
writer.append("%s.class).map()", type.param1());
writeType(type.paramRaw(), writer);
writer.append(".map()");
break;
default:
throw new UnsupportedOperationException(
"Only java.util Map, Set and List are supported JsonB Controller Collection Types");
{
if (type.mainType().contains("java.util"))
throw new UnsupportedOperationException(
"Only java.util Map, Set and List are supported JsonB Controller Collection Types");
writeType(type, writer);
}
}
}
writer.append(";").eol();
}

static void writeType(UType type, Append writer) {
if (type.isGeneric()) {
final var params =
type.importTypes().stream()
.skip(1)
.map(Util::shortName)
.collect(Collectors.joining(".class, "));

writer.append(
"Types.newParameterizedType(%s.class, %s.class))",
Util.shortName(type.mainType()), params);
} else {
writer.append("%s.class)", Util.shortName(type.mainType()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ public interface UType {
static UType parse(TypeMirror type) {
return Util.parseType(type);
}
/** Create the UType from the given String. */
static UType parse(String type) {
return Util.parse(type);
}

UType VOID = new VoidType();

Expand Down Expand Up @@ -48,9 +52,12 @@ default String param1() {
return null;
}

/**
* Return the raw type.
*/
/** Return the raw generic parameter if this UType is a Collection. */
default UType paramRaw() {
return null;
}

/** Return the raw type. */
String full();

default boolean isGeneric() {
Expand Down Expand Up @@ -106,7 +113,9 @@ public String full() {

@Override
public Set<String> importTypes() {
return Collections.singleton(rawType);
return rawType.startsWith("java.lang.") && rawType.indexOf('.') > -1
? Set.of()
: Collections.singleton(rawType.replace("[]", ""));
}

@Override
Expand All @@ -130,15 +139,38 @@ public String mainType() {
*/
class Generic implements UType {
final String rawType;
final UType rawParamType;
final List<String> allTypes;
final String shortRawType;
final String shortName;

Generic(String rawTypeInput) {
this.rawType = rawTypeInput.replace(" ",""); // trim whitespace
this.rawType = rawTypeInput.replace(" ", ""); // trim whitespace
this.allTypes = Arrays.asList(rawType.split("[<|>|,]"));
this.shortRawType = shortRawType(rawType, allTypes);
this.shortName = Util.name(shortRawType);
final var paramTypeString = extractRawParam();
this.rawParamType = paramTypeString != null ? UType.parse(paramTypeString) : null;
}

private String extractRawParam() {

switch (mainType()) {
case "java.util.Set":
case "java.util.List":
case "java.util.stream.Stream":
case "java.util.concurrent.CompletableFuture":
case "io.avaje.http.client.HttpCall":
var first = rawType.indexOf("<") + 1;
var end = rawType.lastIndexOf(">");
return rawType.substring(first, end);
case "java.util.Map":
first = rawType.indexOf(",") + 1;
end = rawType.lastIndexOf(">");
return rawType.substring(first, end);
default:
return null;
}
}

private String shortRawType(String rawType, List<String> allTypes) {
Expand All @@ -163,7 +195,7 @@ public Set<String> importTypes() {
Set<String> set = new LinkedHashSet<>();
for (String type : allTypes) {
if (!type.startsWith("java.lang.") && type.indexOf('.') > -1) {
set.add(type);
set.add(type.replace("[]", ""));
}
}
return set;
Expand Down Expand Up @@ -210,5 +242,10 @@ public String param0() {
public String param1() {
return allTypes.size() < 3 ? null : allTypes.get(2);
}

@Override
public UType paramRaw() {
return rawParamType;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private void writeContextReturn() {
} else if (MediaType.TEXT_PLAIN.equalsIgnoreCase(produces)) {
writer.append(" res.writerContext().contentType(io.helidon.common.http.MediaType.TEXT_PLAIN);").eol();
} else {
writer.append( "res.writerContext().contentType(io.helidon.common.http.MediaType.parse(\"%s\"));", produces).eol();
writer.append(" res.writerContext().contentType(io.helidon.common.http.MediaType.parse(\"%s\"));", produces).eol();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package io.avaje.http.generator.javalin;

import io.avaje.http.generator.core.*;

import java.io.IOException;
import java.util.Map;

import io.avaje.http.generator.core.BaseControllerWriter;
import io.avaje.http.generator.core.Constants;
import io.avaje.http.generator.core.ControllerReader;
import io.avaje.http.generator.core.JsonBUtil;
import io.avaje.http.generator.core.MethodReader;
import io.avaje.http.generator.core.PrimitiveUtil;
import io.avaje.http.generator.core.ProcessingContext;
import io.avaje.http.generator.core.UType;

/**
* Write Javalin specific Controller WebRoute handling adapter.
*/
Expand All @@ -21,7 +28,11 @@ class ControllerWriter extends BaseControllerWriter {
if (useJsonB) {
reader.addImportType("io.avaje.jsonb.Jsonb");
reader.addImportType("io.avaje.jsonb.JsonType");
reader.addImportType("io.avaje.jsonb.Types");
this.jsonTypes = JsonBUtil.jsonTypes(reader);
jsonTypes.values().stream()
.map(UType::importTypes)
.forEach(reader::addImportTypes);
} else {
this.jsonTypes = Map.of();
}
Expand Down Expand Up @@ -77,7 +88,9 @@ private void writeClassStart() {
}

for (final UType type : jsonTypes.values()) {
writer.append(" private final JsonType<%s> %sJsonType;", PrimitiveUtil.wrap(type.full()), type.shortName()).eol();
final var typeString = PrimitiveUtil.wrap(type.shortType()).replace(",", ", ");

writer.append(" private final JsonType<%s> %sJsonType;", typeString, type.shortName()).eol();
}
writer.eol();

Expand Down
Loading