diff --git a/README.md b/README.md index 38df8651e..81a34328e 100644 --- a/README.md +++ b/README.md @@ -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> listWidgetJsonType; - private final JsonType widgetJsonType; + private final JsonType> listWidgetJsonType; + private final JsonType 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 diff --git a/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientMethodWriter.java b/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientMethodWriter.java index 5c0abe1d2..927ad5fe7 100644 --- a/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientMethodWriter.java +++ b/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientMethodWriter.java @@ -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. @@ -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()); } } @@ -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(); diff --git a/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientProcessor.java b/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientProcessor.java index 0a8b68605..c3f4010bf 100644 --- a/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientProcessor.java +++ b/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientProcessor.java @@ -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(); @@ -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(); } } diff --git a/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientWriter.java b/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientWriter.java index 26dd12a6a..6f465bd73 100644 --- a/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientWriter.java +++ b/http-generator-client/src/main/java/io/avaje/http/generator/client/ClientWriter.java @@ -21,12 +21,15 @@ class ClientWriter extends BaseControllerWriter { private static final String SUFFIX = "HttpClient"; private final List 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 @@ -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); } diff --git a/http-generator-core/src/main/java/io/avaje/http/generator/core/JsonBUtil.java b/http-generator-core/src/main/java/io/avaje/http/generator/core/JsonBUtil.java index 78365b3f2..f338f69d0 100644 --- a/http-generator-core/src/main/java/io/avaje/http/generator/core/JsonBUtil.java +++ b/http-generator-core/src/main/java/io/avaje/http/generator/core/JsonBUtil.java @@ -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() {} @@ -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())); + } + } } diff --git a/http-generator-core/src/main/java/io/avaje/http/generator/core/UType.java b/http-generator-core/src/main/java/io/avaje/http/generator/core/UType.java index dd2f2d4d6..e4a4bd20b 100644 --- a/http-generator-core/src/main/java/io/avaje/http/generator/core/UType.java +++ b/http-generator-core/src/main/java/io/avaje/http/generator/core/UType.java @@ -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(); @@ -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() { @@ -106,7 +113,9 @@ public String full() { @Override public Set importTypes() { - return Collections.singleton(rawType); + return rawType.startsWith("java.lang.") && rawType.indexOf('.') > -1 + ? Set.of() + : Collections.singleton(rawType.replace("[]", "")); } @Override @@ -130,15 +139,38 @@ public String mainType() { */ class Generic implements UType { final String rawType; + final UType rawParamType; final List 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 allTypes) { @@ -163,7 +195,7 @@ public Set importTypes() { Set set = new LinkedHashSet<>(); for (String type : allTypes) { if (!type.startsWith("java.lang.") && type.indexOf('.') > -1) { - set.add(type); + set.add(type.replace("[]", "")); } } return set; @@ -210,5 +242,10 @@ public String param0() { public String param1() { return allTypes.size() < 3 ? null : allTypes.get(2); } + + @Override + public UType paramRaw() { + return rawParamType; + } } } diff --git a/http-generator-helidon/src/main/java/io/avaje/http/generator/helidon/ControllerMethodWriter.java b/http-generator-helidon/src/main/java/io/avaje/http/generator/helidon/ControllerMethodWriter.java index e2df2842e..a43192644 100644 --- a/http-generator-helidon/src/main/java/io/avaje/http/generator/helidon/ControllerMethodWriter.java +++ b/http-generator-helidon/src/main/java/io/avaje/http/generator/helidon/ControllerMethodWriter.java @@ -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(); } } diff --git a/http-generator-javalin/src/main/java/io/avaje/http/generator/javalin/ControllerWriter.java b/http-generator-javalin/src/main/java/io/avaje/http/generator/javalin/ControllerWriter.java index 7f19e1ccc..957c293fb 100644 --- a/http-generator-javalin/src/main/java/io/avaje/http/generator/javalin/ControllerWriter.java +++ b/http-generator-javalin/src/main/java/io/avaje/http/generator/javalin/ControllerWriter.java @@ -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. */ @@ -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(); } @@ -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(); diff --git a/http-generator-javalin/src/main/java/io/avaje/http/generator/javalin/JavalinProcessor.java b/http-generator-javalin/src/main/java/io/avaje/http/generator/javalin/JavalinProcessor.java index 4d6285675..ef65ccdf9 100644 --- a/http-generator-javalin/src/main/java/io/avaje/http/generator/javalin/JavalinProcessor.java +++ b/http-generator-javalin/src/main/java/io/avaje/http/generator/javalin/JavalinProcessor.java @@ -9,15 +9,16 @@ public class JavalinProcessor extends BaseProcessor { - private boolean useJsonB; + private final boolean useJsonB; public JavalinProcessor() { + var jsonBOnClassPath = false; try { Class.forName("io.avaje.jsonb.Jsonb"); - this.useJsonB = true; + jsonBOnClassPath = true; } catch (final ClassNotFoundException e) { - this.useJsonB = false; } + useJsonB = jsonBOnClassPath; } public JavalinProcessor(boolean useJsonb) { diff --git a/http-generator-nima/src/main/java/io/avaje/http/generator/helidon/nima/ControllerWriter.java b/http-generator-nima/src/main/java/io/avaje/http/generator/helidon/nima/ControllerWriter.java index 1d78d70ab..03118e01e 100644 --- a/http-generator-nima/src/main/java/io/avaje/http/generator/helidon/nima/ControllerWriter.java +++ b/http-generator-nima/src/main/java/io/avaje/http/generator/helidon/nima/ControllerWriter.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Set; /** * Write Helidon specific web route adapter (a Helidon Service). @@ -21,7 +22,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(); } @@ -87,7 +92,9 @@ private void writeClassStart() { writer.append(" private final Validator validator;").eol(); } 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(); diff --git a/http-generator-nima/src/main/java/io/avaje/http/generator/helidon/nima/NimaProcessor.java b/http-generator-nima/src/main/java/io/avaje/http/generator/helidon/nima/NimaProcessor.java index c65a83b6a..cf3464093 100644 --- a/http-generator-nima/src/main/java/io/avaje/http/generator/helidon/nima/NimaProcessor.java +++ b/http-generator-nima/src/main/java/io/avaje/http/generator/helidon/nima/NimaProcessor.java @@ -9,15 +9,16 @@ public class NimaProcessor extends BaseProcessor { - private boolean jsonB; + private final boolean jsonB; public NimaProcessor() { + var jsonBOnClassPath = false; try { Class.forName("io.avaje.jsonb.Jsonb"); - this.jsonB = true; + jsonBOnClassPath = true; } catch (final ClassNotFoundException e) { - this.jsonB = false; } + jsonB = jsonBOnClassPath; } public NimaProcessor(boolean useJsonb) { diff --git a/tests/test-client/pom.xml b/tests/test-client/pom.xml index 8dc3b7938..9babe4139 100644 --- a/tests/test-client/pom.xml +++ b/tests/test-client/pom.xml @@ -30,7 +30,7 @@ io.avaje avaje-http-client - 1.16 + 1.20