Skip to content

Commit 5591578

Browse files
committed
jooby-apt: fix generation of kotlin generics
- jooby-apt: compilation error while using generics on controller fix #3476 - kotlin: Java type mismatch on @transactional during compile time fix #3477
1 parent 31cbd28 commit 5591578

File tree

9 files changed

+238
-48
lines changed

9 files changed

+238
-48
lines changed

modules/jooby-apt/src/main/java/io/jooby/internal/apt/CodeBlock.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,42 @@ public static String type(boolean kt, CharSequence value) {
5252
case "double" -> "Double";
5353
case "float" -> "Float";
5454
case "Object" -> "Any";
55-
default -> result;
55+
default -> {
56+
var arg = result.indexOf('<');
57+
var from = 0;
58+
var end = arg == -1 ? result.length() : arg;
59+
if (result.startsWith("java.util.")
60+
&& Character.isUpperCase(result.charAt("java.util.".length()))) {
61+
// java.util.List => List
62+
from = "java.util.".length();
63+
}
64+
yield result.substring(from, end) + generics(true, result, arg);
65+
}
5666
};
5767
}
5868
return result;
5969
}
70+
71+
private static String generics(boolean kt, String type, int i) {
72+
if (i == -1) {
73+
return "";
74+
}
75+
var buffer = new StringBuilder();
76+
buffer.append(type.charAt(i));
77+
var arg = new StringBuilder();
78+
for (int j = i + 1; j < type.length(); j++) {
79+
char ch = type.charAt(j);
80+
if (ch == '>' || ch == ',') {
81+
buffer.append(type(kt, arg));
82+
buffer.append(ch);
83+
if (ch == ',') {
84+
buffer.append(' ');
85+
}
86+
arg.setLength(0);
87+
} else if (!Character.isWhitespace(ch)) {
88+
arg.append(ch);
89+
}
90+
}
91+
return buffer.toString();
92+
}
6093
}

modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRoute.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.stream.Stream;
1717

1818
import javax.lang.model.element.*;
19+
import javax.lang.model.type.TypeKind;
1920
import javax.lang.model.type.TypeMirror;
2021
import javax.lang.model.type.WildcardType;
2122

@@ -119,8 +120,8 @@ public List<String> generateMapping(boolean kt) {
119120
"(",
120121
string(leadingSlash(path)),
121122
", ",
122-
context.pipeline(getReturnTypeHandler(), thisRef + methodName),
123-
")"));
123+
context.pipeline(
124+
getReturnTypeHandler(), methodReference(kt, thisRef, methodName))));
124125
if (context.nonBlocking(getReturnTypeHandler()) || isSuspendFun()) {
125126
block.add(statement(indent(2), ".setNonBlocking(true)"));
126127
}
@@ -137,7 +138,7 @@ public List<String> generateMapping(boolean kt) {
137138
block.add(statement(indent(2), ".setExecutorKey(", string(dispatch), ")")));
138139
/* attributes */
139140
attributeGenerator
140-
.toSourceCode(this, 2)
141+
.toSourceCode(kt, this, 2)
141142
.ifPresent(
142143
attributes -> block.add(statement(indent(2), ".setAttributes(", attributes, ")")));
143144
/* returnType */
@@ -161,6 +162,17 @@ public List<String> generateMapping(boolean kt) {
161162
return block;
162163
}
163164

165+
private String methodReference(boolean kt, String thisRef, String methodName) {
166+
if (kt) {
167+
var returnType = getReturnType();
168+
var generics = returnType.getArgumentsString(kt, true, Set.of(TypeKind.TYPEVAR));
169+
if (!generics.isEmpty()) {
170+
return CodeBlock.of(") { ", methodName, generics, "(ctx) }");
171+
}
172+
}
173+
return thisRef + methodName + ")";
174+
}
175+
164176
/**
165177
* Ensure path start with a <code>/</code>(leading slash).
166178
*
@@ -182,6 +194,8 @@ public List<String> generateHandlerCall(boolean kt) {
182194
paramList.add(parameter.generateMapping(kt));
183195
}
184196
var throwsException = !method.getThrownTypes().isEmpty();
197+
var returnTypeGenerics =
198+
getReturnType().getArgumentsString(kt, false, Set.of(TypeKind.TYPEVAR));
185199
var returnTypeString = type(kt, getReturnType().toString());
186200
if (kt) {
187201
if (throwsException) {
@@ -192,6 +206,7 @@ public List<String> generateHandlerCall(boolean kt) {
192206
statement(
193207
"suspend ",
194208
"fun ",
209+
returnTypeGenerics,
195210
getGeneratedName(),
196211
"(handler: io.jooby.kt.HandlerContext): ",
197212
returnTypeString,
@@ -200,12 +215,18 @@ public List<String> generateHandlerCall(boolean kt) {
200215
} else {
201216
buffer.add(
202217
statement(
203-
"fun ", getGeneratedName(), "(ctx: io.jooby.Context): ", returnTypeString, " {"));
218+
"fun ",
219+
returnTypeGenerics,
220+
getGeneratedName(),
221+
"(ctx: io.jooby.Context): ",
222+
returnTypeString,
223+
" {"));
204224
}
205225
} else {
206226
buffer.add(
207227
statement(
208228
"public ",
229+
returnTypeGenerics,
209230
returnTypeString,
210231
" ",
211232
getGeneratedName(),
@@ -267,13 +288,18 @@ public List<String> generateHandlerCall(boolean kt) {
267288
buffer.add(statement(indent(2), "return statusCode", semicolon(kt)));
268289
} else {
269290
controllerVar(kt, buffer);
270-
buffer.add(
271-
statement(
272-
indent(2),
273-
"return c.",
291+
var cast = getReturnType().getArgumentsString(kt, false, Set.of(TypeKind.TYPEVAR));
292+
var kotlinNotEnoughTypeInformation = !cast.isEmpty() && kt ? "<Any>" : "";
293+
var call =
294+
of(
295+
"c.",
274296
this.method.getSimpleName(),
275-
paramList.toString(),
276-
semicolon(kt)));
297+
kotlinNotEnoughTypeInformation,
298+
paramList.toString());
299+
if (!cast.isEmpty()) {
300+
call = kt ? call + " as " + returnTypeString : "(" + returnTypeString + ") " + call;
301+
}
302+
buffer.add(statement(indent(2), "return ", call, semicolon(kt)));
277303
}
278304
buffer.add(statement("}", System.lineSeparator()));
279305
return buffer;

modules/jooby-apt/src/main/java/io/jooby/internal/apt/ParameterGenerator.java

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,17 @@ public String toSourceCode(
3030
String name,
3131
boolean nullable) {
3232
if (type.is(Map.class)) {
33-
return CodeBlock.of(
34-
"java.util.Optional.ofNullable((java.util.Map) ctx.getAttribute(",
35-
CodeBlock.string(name),
36-
")).orElseGet(() ->" + " ctx.getAttributes())");
33+
if (kt) {
34+
return CodeBlock.of(
35+
"(ctx.attributes[",
36+
CodeBlock.string(name),
37+
"]?: ctx.attributes) as Map<String, Any>");
38+
} else {
39+
return CodeBlock.of(
40+
"java.util.Optional.ofNullable((java.util.Map) ctx.getAttribute(",
41+
CodeBlock.string(name),
42+
")).orElseGet(() ->" + " ctx.getAttributes())");
43+
}
3744
} else {
3845
return kt
3946
? CodeBlock.of(
@@ -207,40 +214,53 @@ public String toSourceCode(
207214
var paramSource = source(annotation);
208215
var builtin = builtinType(kt, annotation, type, name, nullable);
209216
if (builtin == null) {
217+
// List, Set,
210218
var toValue =
211219
CONTAINER.stream()
212220
.filter(type::is)
213221
.findFirst()
214222
.map(Class::getSimpleName)
215223
.map(it -> "to" + it)
216-
.orElse(nullable ? "toNullable" : "to");
217-
var elementType = type.getArguments().isEmpty() ? type : type.getArguments().get(0);
218-
if (paramSource.isEmpty() && BUILT_IN.stream().noneMatch(elementType::is)) {
224+
.map(it -> Map.entry(it, type.getArguments().get(0)))
225+
.orElseGet(
226+
() -> {
227+
var convertMethod = nullable ? "toNullable" : "to";
228+
return Map.entry(convertMethod, type);
229+
});
230+
if (paramSource.isEmpty() && BUILT_IN.stream().noneMatch(it -> toValue.getValue().is(it))) {
219231
// for unsupported types, we check if node with matching name is present, if not we fallback
220232
// to entire scope converter
221233
if (kt) {
234+
var prefix = "";
235+
var suffix = "";
236+
if (toValue.getValue().isParameterizedType()) {
237+
prefix = "(";
238+
suffix = ") as " + CodeBlock.type(true, toValue.getValue().toString());
239+
}
222240
return CodeBlock.of(
241+
prefix,
223242
"if(ctx.",
224243
method,
225244
"(",
226245
CodeBlock.string(name),
227246
").isMissing()) ctx.",
228247
method,
229248
"().",
230-
toValue,
249+
toValue.getKey(),
231250
"(",
232-
CodeBlock.type(kt, elementType.getName()),
251+
CodeBlock.type(kt, toValue.getValue().getName()),
233252
CodeBlock.clazz(kt),
234253
") else ctx.",
235254
method,
236255
"(",
237256
CodeBlock.string(name),
238257
").",
239-
toValue,
258+
toValue.getKey(),
240259
"(",
241-
CodeBlock.type(kt, elementType.getName()),
260+
CodeBlock.type(kt, toValue.getValue().getName()),
242261
CodeBlock.clazz(kt),
243-
")");
262+
")",
263+
suffix);
244264
} else {
245265
return CodeBlock.of(
246266
"ctx.",
@@ -250,26 +270,33 @@ public String toSourceCode(
250270
").isMissing() ? ctx.",
251271
method,
252272
"().",
253-
toValue,
273+
toValue.getKey(),
254274
"(",
255-
CodeBlock.type(kt, elementType.getName()),
275+
CodeBlock.type(kt, toValue.getValue().getName()),
256276
CodeBlock.clazz(kt),
257277
") : ctx.",
258278
method,
259279
"(",
260280
CodeBlock.string(name),
261281
").",
262-
toValue,
282+
toValue.getKey(),
263283
"(",
264-
CodeBlock.type(kt, elementType.getName()),
284+
CodeBlock.type(kt, toValue.getValue().getName()),
265285
CodeBlock.clazz(kt),
266286
")");
267287
}
268288
} else {
269289
// container of supported types: List<Integer>, Optional<UUID>
270-
if (elementType.is(String.class)) {
290+
if (toValue.getValue().is(String.class)) {
271291
return CodeBlock.of(
272-
"ctx.", method, "(", CodeBlock.string(name), paramSource, ").", toValue, "()");
292+
"ctx.",
293+
method,
294+
"(",
295+
CodeBlock.string(name),
296+
paramSource,
297+
").",
298+
toValue.getKey(),
299+
"()");
273300
} else {
274301
return CodeBlock.of(
275302
"ctx.",
@@ -278,9 +305,9 @@ public String toSourceCode(
278305
CodeBlock.string(name),
279306
paramSource,
280307
").",
281-
toValue,
308+
toValue.getKey(),
282309
"(",
283-
CodeBlock.type(kt, elementType.getName()),
310+
CodeBlock.type(kt, toValue.getValue().getName()),
284311
CodeBlock.clazz(kt),
285312
")");
286313
}

modules/jooby-apt/src/main/java/io/jooby/internal/apt/RouteAttributesGenerator.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import static io.jooby.apt.JoobyProcessor.Options.SKIP_ATTRIBUTE_ANNOTATIONS;
99
import static io.jooby.internal.apt.CodeBlock.indent;
10+
import static io.jooby.internal.apt.CodeBlock.type;
1011

1112
import java.lang.annotation.Retention;
1213
import java.lang.annotation.RetentionPolicy;
@@ -54,49 +55,50 @@ public RouteAttributesGenerator(MvcContext context) {
5455
this.skip = Options.stringListOpt(environment, SKIP_ATTRIBUTE_ANNOTATIONS);
5556
}
5657

57-
public Optional<String> toSourceCode(MvcRoute route, int indent) {
58+
public Optional<String> toSourceCode(boolean kt, MvcRoute route, int indent) {
5859
var attributes = annotationMap(route.getMethod());
5960
if (attributes.isEmpty()) {
6061
return Optional.empty();
6162
} else {
62-
return Optional.of(toSourceCode(annotationMap(route.getMethod()), indent + 6));
63+
return Optional.of(toSourceCode(kt, annotationMap(route.getMethod()), indent + 6));
6364
}
6465
}
6566

66-
private String toSourceCode(Map<String, Object> attributes, int indent) {
67+
private String toSourceCode(boolean kt, Map<String, Object> attributes, int indent) {
6768
var buffer = new StringBuilder();
6869
var separator = ",\n";
6970
var pairPrefix = "";
7071
var pairSuffix = "";
71-
var factoryMethod = "of";
72+
var typeInfo = kt ? "<String, Any>" : "";
73+
var factoryMethod = "of" + typeInfo;
7274
if (attributes.size() > 10) {
7375
// Map.of Max size is 10
7476
pairPrefix = "java.util.Map.entry(";
7577
pairSuffix = ")";
76-
factoryMethod = "ofEntries";
78+
factoryMethod = "ofEntries" + typeInfo;
7779
}
7880
buffer.append("java.util.Map.").append(factoryMethod).append("(\n");
7981
for (var e : attributes.entrySet()) {
8082
buffer.append(indent(indent + 4));
8183
buffer.append(pairPrefix);
8284
buffer.append(CodeBlock.string(e.getKey())).append(", ");
83-
buffer.append(valueToSourceCode(e.getValue(), indent + 4));
85+
buffer.append(valueToSourceCode(kt, e.getValue(), indent + 4));
8486
buffer.append(pairSuffix).append(separator);
8587
}
8688
buffer.setLength(buffer.length() - separator.length());
8789
buffer.append(")");
8890
return buffer.toString();
8991
}
9092

91-
private Object valueToSourceCode(Object value, int indent) {
93+
private Object valueToSourceCode(boolean kt, Object value, int indent) {
9294
if (value instanceof String) {
9395
return CodeBlock.string((String) value);
9496
} else if (value instanceof Character) {
9597
return "'" + value + "'";
9698
} else if (value instanceof Map attributeMap) {
97-
return "\n " + indent(indent) + toSourceCode(attributeMap, indent + 1);
99+
return "\n " + indent(indent) + toSourceCode(kt, attributeMap, indent + 1);
98100
} else if (value instanceof List list) {
99-
return valueToSourceCode(list, indent);
101+
return valueToSourceCode(kt, list, indent);
100102
} else if (value instanceof EnumValue enumValue) {
101103
return enumValue.type + "." + enumValue.value;
102104
} else if (value instanceof TypeMirror) {
@@ -116,12 +118,12 @@ private Object valueToSourceCode(Object value, int indent) {
116118
}
117119
}
118120

119-
private String valueToSourceCode(List values, int indent) {
121+
private String valueToSourceCode(boolean kt, List values, int indent) {
120122
var buffer = new StringBuilder();
121123
buffer.append("java.util.List.of(");
122124
var separator = ", ";
123125
for (Object value : values) {
124-
buffer.append(valueToSourceCode(value, indent)).append(separator);
126+
buffer.append(valueToSourceCode(kt, value, indent)).append(separator);
125127
}
126128
buffer.setLength(buffer.length() - separator.length());
127129
buffer.append(")");

0 commit comments

Comments
 (0)