Skip to content

Commit 10692b1

Browse files
committed
Support controller extending super class with generic methods
1 parent 2b109b6 commit 10692b1

File tree

8 files changed

+144
-67
lines changed

8 files changed

+144
-67
lines changed

src/main/java/io/dinject/javalin/generator/BeanParamReader.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ class BeanParamReader {
3333

3434
private void read() {
3535

36-
final List<? extends Element> enclosedElements = beanType.getEnclosedElements();
37-
for (Element enclosedElement : enclosedElements) {
36+
for (Element enclosedElement : beanType.getEnclosedElements()) {
3837
switch (enclosedElement.getKind()) {
3938
case METHOD:
4039
readMethod(enclosedElement);

src/main/java/io/dinject/javalin/generator/ControllerReader.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import javax.lang.model.element.ElementKind;
99
import javax.lang.model.element.ExecutableElement;
1010
import javax.lang.model.element.TypeElement;
11+
import javax.lang.model.type.DeclaredType;
12+
import javax.lang.model.type.ExecutableType;
13+
import javax.lang.model.type.TypeKind;
1114
import javax.lang.model.type.TypeMirror;
1215
import javax.lang.model.util.ElementFilter;
1316
import java.lang.annotation.Annotation;
@@ -152,18 +155,53 @@ void read() {
152155

153156
for (Element element : beanType.getEnclosedElements()) {
154157
if (element.getKind() == ElementKind.METHOD) {
155-
readMethod(element);
158+
readMethod((ExecutableElement)element);
156159
}
157160
}
161+
162+
readSuper(beanType);
158163
}
159164

165+
/**
166+
* Read methods from superclasses taking into account generics.
167+
*/
168+
private void readSuper(TypeElement beanType) {
169+
170+
TypeMirror superclass = beanType.getSuperclass();
171+
if (superclass.getKind() != TypeKind.NONE) {
172+
DeclaredType declaredType = (DeclaredType)superclass;
173+
174+
final Element superElement = ctx.asElement(superclass);
175+
if (!"java.lang.Object".equals(superElement.toString())) {
176+
for (Element element : superElement.getEnclosedElements()) {
177+
if (element.getKind() == ElementKind.METHOD) {
178+
readMethod((ExecutableElement)element, declaredType);
179+
}
180+
}
181+
if (superElement instanceof TypeElement) {
182+
readSuper((TypeElement) superElement);
183+
}
184+
}
185+
}
186+
}
187+
188+
private void readMethod(ExecutableElement element) {
189+
readMethod(element, null);
190+
}
160191

161-
private void readMethod(Element element) {
192+
private void readMethod(ExecutableElement method, DeclaredType declaredType) {
162193

163-
ExecutableElement methodElement = (ExecutableElement) element;
164-
MethodReader methodReader = new MethodReader(this, methodElement, ctx);
165-
methodReader.read();
166-
methods.add(methodReader);
194+
ExecutableType actualExecutable = null;
195+
if (declaredType != null) {
196+
// actual taking into account generics
197+
actualExecutable = (ExecutableType)ctx.asMemberOf(declaredType, method);
198+
}
199+
200+
MethodReader methodReader = new MethodReader(this, method, actualExecutable, ctx);
201+
if (methodReader.isWebMethod()) {
202+
methodReader.read();
203+
methods.add(methodReader);
204+
}
167205
}
168206

169207
List<String> getRoles() {

src/main/java/io/dinject/javalin/generator/ElementReader.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@ class ElementReader {
3030
private String paramDefault;
3131

3232
ElementReader(Element element, ProcessingContext ctx, ParamType defaultType, boolean formMarker) {
33+
this(element, element.asType().toString(), ctx, defaultType, formMarker);
34+
}
35+
36+
ElementReader(Element element, String rawType, ProcessingContext ctx, ParamType defaultType, boolean formMarker) {
3337
this.ctx = ctx;
3438
this.element = element;
35-
this.rawType = element.asType().toString();
39+
this.rawType = rawType;
3640
this.typeHandler = TypeMap.get(rawType);
3741
this.formMarker = formMarker;
3842

@@ -148,7 +152,7 @@ void addMeta(ProcessingContext ctx, Javadoc javadoc, Operation operation) {
148152
param.setName(varName);
149153
param.setDescription(javadoc.getParams().get(paramName));
150154
param.setIn(paramType.getType());
151-
param.setSchema(ctx.toSchema(element.asType()));
155+
param.setSchema(ctx.toSchema(rawType, element));
152156

153157
operation.addParametersItem(param);
154158
}
@@ -169,8 +173,7 @@ void writeCtxGet(Append writer, PathSegments segments) {
169173
}
170174

171175
void setValue(Append writer) {
172-
String shortType = shortType();
173-
setValue(writer, PathSegments.EMPTY, shortType);
176+
setValue(writer, PathSegments.EMPTY, shortType());
174177
}
175178

176179
private boolean setValue(Append writer, PathSegments segments, String shortType) {

src/main/java/io/dinject/javalin/generator/MethodParam.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ class MethodParam {
99

1010
private final ElementReader elementParam;
1111

12-
MethodParam(VariableElement param, ProcessingContext ctx, ParamType defaultParamType, boolean formMarker) {
13-
this.elementParam = new ElementReader(param, ctx, defaultParamType, formMarker);
12+
MethodParam(VariableElement param, String rawType, ProcessingContext ctx, ParamType defaultParamType, boolean formMarker) {
13+
this.elementParam = new ElementReader(param, rawType, ctx, defaultParamType, formMarker);
1414
}
1515

1616
void buildCtxGet(Append writer, PathSegments segments) {

src/main/java/io/dinject/javalin/generator/MethodReader.java

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212
import io.swagger.v3.oas.annotations.Hidden;
1313
import io.swagger.v3.oas.models.Operation;
1414
import io.swagger.v3.oas.models.PathItem;
15-
import io.swagger.v3.oas.models.Paths;
1615
import io.swagger.v3.oas.models.responses.ApiResponse;
1716
import io.swagger.v3.oas.models.responses.ApiResponses;
1817

1918
import javax.lang.model.element.ExecutableElement;
2019
import javax.lang.model.element.VariableElement;
20+
import javax.lang.model.type.ExecutableType;
2121
import javax.lang.model.type.TypeKind;
22+
import javax.lang.model.type.TypeMirror;
2223
import java.lang.annotation.Annotation;
2324
import java.util.ArrayList;
2425
import java.util.List;
@@ -49,15 +50,20 @@ class MethodReader {
4950

5051
private final String produces;
5152

53+
private final ExecutableType actualExecutable;
54+
private final List<? extends TypeMirror> actualParams;
55+
5256
private String fullPath;
5357

5458
private PathSegments segments;
5559

56-
MethodReader(ControllerReader bean, ExecutableElement element, ProcessingContext ctx) {
60+
MethodReader(ControllerReader bean, ExecutableElement element, ExecutableType actualExecutable, ProcessingContext ctx) {
5761
this.ctx = ctx;
5862
this.bean = bean;
5963
this.beanPath = bean.getPath();
6064
this.element = element;
65+
this.actualExecutable = actualExecutable;
66+
this.actualParams = (actualExecutable == null) ? null :actualExecutable.getParameterTypes();
6167
this.isVoid = element.getReturnType().getKind() == TypeKind.VOID;
6268
this.methodRoles = Util.findRoles(element);
6369
this.javadoc = Javadoc.parse(ctx.getDocComment(element));
@@ -66,6 +72,10 @@ class MethodReader {
6672
readMethodAnnotation();
6773
}
6874

75+
boolean isWebMethod() {
76+
return webMethod != null;
77+
}
78+
6979
private String produces(ControllerReader bean) {
7080
final Produces produces = findAnnotation(Produces.class);
7181
return (produces != null) ? produces.value() : bean.getProduces();
@@ -92,8 +102,19 @@ void read() {
92102
// existence of @Form annotation on the method
93103
ParamType defaultParamType = (formMarker) ? ParamType.FORMPARAM : ParamType.QUERYPARAM;
94104

95-
for (VariableElement p : element.getParameters()) {
96-
MethodParam param = new MethodParam(p, ctx, defaultParamType, formMarker);
105+
final List<? extends VariableElement> parameters = element.getParameters();
106+
for (int i = 0; i < parameters.size(); i++) {
107+
108+
VariableElement p = parameters.get(i);
109+
110+
String rawType;
111+
if (actualParams != null) {
112+
rawType = actualParams.get(i).toString();
113+
} else {
114+
rawType = p.asType().toString();
115+
}
116+
117+
MethodParam param = new MethodParam(p, rawType, ctx, defaultParamType, formMarker);
97118
params.add(param);
98119
param.addImports(bean);
99120
}
@@ -103,14 +124,6 @@ void addMeta(ProcessingContext ctx) {
103124

104125
if (webMethod != null && notHidden()) {
105126

106-
Paths paths = ctx.getOpenAPI().getPaths();
107-
108-
PathItem pathItem = paths.get(fullPath);
109-
if (pathItem == null) {
110-
pathItem = new PathItem();
111-
paths.addPathItem(fullPath, pathItem);
112-
}
113-
114127
Operation operation = new Operation();
115128
//operation.setOperationId();
116129
operation.setSummary(javadoc.getSummary());
@@ -122,6 +135,7 @@ void addMeta(ProcessingContext ctx) {
122135
operation.setDeprecated(true);
123136
}
124137

138+
PathItem pathItem = ctx.pathItem(fullPath);
125139
switch (webMethod) {
126140
case GET:
127141
pathItem.setGet(operation);
@@ -156,12 +170,19 @@ void addMeta(ProcessingContext ctx) {
156170
}
157171
} else {
158172
String contentMediaType = (produces == null) ? MediaType.APPLICATION_JSON : produces;
159-
response.setContent(ctx.createContent(element.getReturnType(), contentMediaType));
173+
response.setContent(ctx.createContent(returnType(), contentMediaType));
160174
}
161175
responses.addApiResponse(httpStatusCode(), response);
162176
}
163177
}
164178

179+
private TypeMirror returnType() {
180+
if (actualExecutable != null) {
181+
return actualExecutable.getReturnType();
182+
}
183+
return element.getReturnType();
184+
}
185+
165186
private boolean isEmpty(String value) {
166187
return value == null || value.isEmpty();
167188
}

src/main/java/io/dinject/javalin/generator/ProcessingContext.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import io.dinject.javalin.generator.openapi.SchemaBuilder;
44
import io.swagger.v3.oas.models.OpenAPI;
5+
import io.swagger.v3.oas.models.PathItem;
6+
import io.swagger.v3.oas.models.Paths;
57
import io.swagger.v3.oas.models.media.Content;
68
import io.swagger.v3.oas.models.media.Schema;
79

@@ -10,6 +12,7 @@
1012
import javax.annotation.processing.ProcessingEnvironment;
1113
import javax.lang.model.element.Element;
1214
import javax.lang.model.element.TypeElement;
15+
import javax.lang.model.type.DeclaredType;
1316
import javax.lang.model.type.TypeMirror;
1417
import javax.lang.model.util.Elements;
1518
import javax.lang.model.util.Types;
@@ -18,6 +21,8 @@
1821
import javax.tools.JavaFileObject;
1922
import javax.tools.StandardLocation;
2023
import java.io.IOException;
24+
import java.util.Map;
25+
import java.util.TreeMap;
2126

2227
import static io.dinject.javalin.generator.Constants.GENERATED;
2328
import static io.dinject.javalin.generator.Constants.OPENAPIDEFINITION;
@@ -34,6 +39,8 @@ class ProcessingContext {
3439

3540
private OpenAPI openAPI;
3641

42+
private final Map<String, PathItem> pathMap = new TreeMap<>();
43+
3744
ProcessingContext(ProcessingEnvironment env) {
3845
this.messager = env.getMessager();
3946
this.filer = env.getFiler();
@@ -91,13 +98,32 @@ OpenAPI getOpenAPI() {
9198
return openAPI;
9299
}
93100

101+
OpenAPI getOpenAPIForWriting() {
102+
Paths paths = openAPI.getPaths();
103+
if (paths == null) {
104+
paths = new Paths();
105+
openAPI.setPaths(paths);
106+
}
107+
// add paths by natural order
108+
for (Map.Entry<String, PathItem> entry : pathMap.entrySet()) {
109+
paths.addPathItem(entry.getKey(), entry.getValue());
110+
}
111+
return openAPI;
112+
}
113+
94114
void setOpenAPI(OpenAPI openAPI) {
95115
this.openAPI = openAPI;
96116
this.schemaBuilder.setOpenAPI(openAPI);
97117
}
98118

99-
Schema toSchema(TypeMirror asType) {
100-
return schemaBuilder.toSchema(asType);
119+
Schema toSchema(String rawType, Element element) {
120+
TypeElement typeElement = elements.getTypeElement(rawType);
121+
if (typeElement == null) {
122+
// primitive types etc
123+
return schemaBuilder.toSchema(element.asType());
124+
} else {
125+
return schemaBuilder.toSchema(typeElement.asType());
126+
}
101127
}
102128

103129
Content createContent(TypeMirror returnType, String mediaType) {
@@ -107,4 +133,12 @@ Content createContent(TypeMirror returnType, String mediaType) {
107133
Element asElement(TypeMirror typeMirror) {
108134
return types.asElement(typeMirror);
109135
}
136+
137+
TypeMirror asMemberOf(DeclaredType declaredType, Element element) {
138+
return types.asMemberOf(declaredType, element);
139+
}
140+
141+
PathItem pathItem(String fullPath) {
142+
return pathMap.computeIfAbsent(fullPath, s -> new PathItem());
143+
}
110144
}

src/main/java/io/dinject/javalin/generator/Processor.java

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,7 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
5353
@Override
5454
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment round) {
5555

56-
loadOpenAPI();
57-
58-
logDebug("process controllers ...");
59-
56+
initOpenAPI();
6057
if (ctx.isOpenApiAvailable()) {
6158
readOpenApiDefinition(round);
6259
}
@@ -67,7 +64,6 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
6764
}
6865

6966
if (round.processingOver()) {
70-
logDebug("processingOver ...");
7167
writeOpenAPI();
7268
}
7369
return false;
@@ -82,9 +78,6 @@ private void readOpenApiDefinition(RoundEnvironment round) {
8278

8379
OpenAPIDefinition openApi = element.getAnnotation(OpenAPIDefinition.class);
8480
io.swagger.v3.oas.annotations.info.Info info = openApi.info();
85-
86-
logDebug("reading OpenAPIDefinition " + openApi + " info:" + info);
87-
8881
if (!info.title().isEmpty()) {
8982
info1.setTitle(info.title());
9083
}
@@ -98,9 +91,7 @@ private void writeOpenAPI() {
9891

9992
try (Writer metaWriter = createMetaWriter()) {
10093

101-
OpenAPI openAPI = ctx.getOpenAPI();
102-
logDebug("openAPI writing paths: " + openAPI.getPaths().keySet());
103-
94+
OpenAPI openAPI = ctx.getOpenAPIForWriting();
10495
ObjectMapper mapper = createObjectMapper();
10596
mapper.writeValue(metaWriter, openAPI);
10697

@@ -141,24 +132,14 @@ private void writeControllerAdapter(Element controller) {
141132
}
142133
}
143134

144-
private void loadOpenAPI() {
145-
logDebug("loading openAPI ...");
146-
OpenAPI openAPI = ctx.getOpenAPI();
147-
if (openAPI != null && !openAPI.getPaths().isEmpty()) {
148-
logDebug("openAPI already loaded ... " + openAPI.getPaths().keySet());
149-
return;
150-
}
151-
initOpenAPI();
152-
}
153-
154135
private void initOpenAPI() {
155136

156-
OpenAPI openAPI;
157-
openAPI = new OpenAPI();
158-
openAPI.setPaths(new Paths());
159-
openAPI.setInfo(new Info());
160-
161-
ctx.logDebug("initialise empty OpenAPI");
162-
ctx.setOpenAPI(openAPI);
137+
OpenAPI openAPI = ctx.getOpenAPI();
138+
if (openAPI == null) {
139+
openAPI = new OpenAPI();
140+
openAPI.setPaths(new Paths());
141+
openAPI.setInfo(new Info());
142+
ctx.setOpenAPI(openAPI);
143+
}
163144
}
164145
}

0 commit comments

Comments
 (0)