Skip to content

Commit bc83bb5

Browse files
committed
make it inherit
1 parent 6292783 commit bc83bb5

File tree

10 files changed

+309
-29
lines changed

10 files changed

+309
-29
lines changed

http-generator-core/src/main/java/io/avaje/http/generator/core/MethodReader.java

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.Arrays;
66
import java.util.List;
77
import java.util.Optional;
8+
import java.util.function.Predicate;
89
import java.util.stream.Collectors;
910
import java.util.stream.Stream;
1011

@@ -60,6 +61,7 @@ public class MethodReader {
6061

6162
private final PathSegments pathSegments;
6263
private final boolean hasValid;
64+
private final List<ExecutableElement> superMethods;
6365

6466
MethodReader(ControllerReader bean, ExecutableElement element, ExecutableType actualExecutable, ProcessingContext ctx) {
6567
this.ctx = ctx;
@@ -69,18 +71,46 @@ public class MethodReader {
6971
this.actualParams = (actualExecutable == null) ? null : actualExecutable.getParameterTypes();
7072
this.isVoid = element.getReturnType().getKind() == TypeKind.VOID;
7173
this.methodRoles = Util.findRoles(element);
72-
this.javadoc = Javadoc.parse(ctx.docComment(element));
7374
this.produces = produces(bean);
74-
this.apiResponses = getApiResponses();
7575
initWebMethodViaAnnotation();
76+
77+
this.superMethods =
78+
ctx.getSuperMethods(element.getEnclosingElement(), element.toString().replace("()", ""));
79+
80+
this.apiResponses = getApiResponses();
81+
this.javadoc =
82+
Optional.of(Javadoc.parse(ctx.docComment(element)))
83+
.filter(Predicate.not(Javadoc::isEmpty))
84+
.orElseGet(
85+
() ->
86+
superMethods.stream()
87+
.map(e -> Javadoc.parse(ctx.docComment(e)))
88+
.filter(Predicate.not(Javadoc::isEmpty))
89+
.findFirst()
90+
.orElse(Javadoc.parse("")));
91+
7692
if (isWebMethod()) {
77-
Annotation jakartaValidAnnotation = null;
93+
94+
Class<Annotation> jakartaValidAnnotation;
7895
try {
79-
jakartaValidAnnotation = findAnnotation(jakartaValidAnnotation());
96+
jakartaValidAnnotation = jakartaValidAnnotation();
8097
} catch (final ClassNotFoundException e) {
81-
// ignore
98+
jakartaValidAnnotation = null;
8299
}
83-
this.hasValid = findAnnotation(Valid.class) != null || jakartaValidAnnotation != null;
100+
101+
final var jakartaAnnotation = jakartaValidAnnotation;
102+
103+
this.hasValid =
104+
findAnnotation(Valid.class) != null
105+
|| (jakartaValidAnnotation != null && findAnnotation(jakartaValidAnnotation) != null)
106+
|| superMethods.stream()
107+
.map(
108+
e ->
109+
findAnnotation(Valid.class, e) != null
110+
|| (jakartaAnnotation != null
111+
&& findAnnotation(jakartaAnnotation, e) != null))
112+
.anyMatch(b -> b);
113+
84114
this.pathSegments = PathSegments.parse(Util.combinePath(bean.path(), webMethodPath));
85115
} else {
86116
this.hasValid = false;
@@ -99,31 +129,31 @@ public String toString() {
99129
}
100130

101131
private void initWebMethodViaAnnotation() {
102-
Form form = findAnnotation(Form.class);
132+
final var form = findAnnotation(Form.class);
103133
if (form != null) {
104134
this.formMarker = true;
105135
}
106-
Get get = findAnnotation(Get.class);
136+
final var get = findAnnotation(Get.class);
107137
if (get != null) {
108138
initSetWebMethod(WebMethod.GET, get.value());
109139
return;
110140
}
111-
Put put = findAnnotation(Put.class);
141+
final var put = findAnnotation(Put.class);
112142
if (put != null) {
113143
initSetWebMethod(WebMethod.PUT, put.value());
114144
return;
115145
}
116-
Post post = findAnnotation(Post.class);
146+
final var post = findAnnotation(Post.class);
117147
if (post != null) {
118148
initSetWebMethod(WebMethod.POST, post.value());
119149
return;
120150
}
121-
Patch patch = findAnnotation(Patch.class);
151+
final var patch = findAnnotation(Patch.class);
122152
if (patch != null) {
123153
initSetWebMethod(WebMethod.PATCH, patch.value());
124154
return;
125155
}
126-
Delete delete = findAnnotation(Delete.class);
156+
final var delete = findAnnotation(Delete.class);
127157
if (delete != null) {
128158
initSetWebMethod(WebMethod.DELETE, delete.value());
129159
}
@@ -149,19 +179,34 @@ private List<OpenAPIResponse> getApiResponses() {
149179
.map(OpenAPIResponses::value)
150180
.flatMap(Arrays::stream);
151181

152-
return Stream.concat(container, Arrays.stream(element.getAnnotationsByType(OpenAPIResponse.class)))
153-
.collect(Collectors.toList());
182+
final var methodResponses =
183+
Stream.concat(
184+
container, Arrays.stream(element.getAnnotationsByType(OpenAPIResponse.class)));
154185

186+
final var superMethodResponses =
187+
superMethods.stream()
188+
.flatMap(
189+
m -> Stream.concat(
190+
Optional.ofNullable(findAnnotation(OpenAPIResponses.class,m)).stream()
191+
.map(OpenAPIResponses::value)
192+
.flatMap(Arrays::stream),
193+
Arrays.stream(m.getAnnotationsByType(OpenAPIResponse.class))));
155194

195+
return Stream.concat(methodResponses, superMethodResponses).collect(Collectors.toList());
156196
}
157197

158198
public <A extends Annotation> A findAnnotation(Class<A> type) {
159-
A annotation = element.getAnnotation(type);
199+
200+
return findAnnotation(type, element);
201+
}
202+
203+
public <A extends Annotation> A findAnnotation(Class<A> type, ExecutableElement elem) {
204+
final var annotation = elem.getAnnotation(type);
160205
if (annotation != null) {
161206
return annotation;
162207
}
163208

164-
return bean.findMethodAnnotation(type, element);
209+
return bean.findMethodAnnotation(type, elem);
165210
}
166211

167212
private List<String> addTagsToList(Element element, List<String> list) {
@@ -172,15 +217,17 @@ private List<String> addTagsToList(Element element, List<String> list) {
172217
list.add(element.getAnnotation(Tag.class).name());
173218
}
174219
if (element.getAnnotation(Tags.class) != null) {
175-
for (Tag tag : element.getAnnotation(Tags.class).value())
220+
for (final Tag tag : element.getAnnotation(Tags.class).value())
176221
list.add(tag.name());
177222
}
178223
return list;
179224
}
180225

181226
public List<String> tags() {
182-
List<String> tags = new ArrayList<>();
183-
tags = addTagsToList(element, tags);
227+
final List<String> tags = new ArrayList<>();
228+
addTagsToList(element, tags);
229+
superMethods.forEach(e -> addTagsToList(e, tags));
230+
184231
return addTagsToList(element.getEnclosingElement(), tags);
185232
}
186233

@@ -191,20 +238,20 @@ void read() {
191238

192239
// non-path parameters default to form or query parameters based on the
193240
// existence of @Form annotation on the method
194-
ParamType defaultParamType = (formMarker) ? ParamType.FORMPARAM : ParamType.QUERYPARAM;
241+
final var defaultParamType = (formMarker) ? ParamType.FORMPARAM : ParamType.QUERYPARAM;
195242

196243
final List<? extends VariableElement> parameters = element.getParameters();
197-
for (int i = 0; i < parameters.size(); i++) {
198-
VariableElement p = parameters.get(i);
244+
for (var i = 0; i < parameters.size(); i++) {
245+
final VariableElement p = parameters.get(i);
199246
TypeMirror typeMirror;
200247
if (actualParams != null) {
201248
typeMirror = actualParams.get(i);
202249
} else {
203250
typeMirror = p.asType();
204251
}
205-
String rawType = Util.typeDef(typeMirror);
206-
UType type = Util.parse(typeMirror.toString());
207-
MethodParam param = new MethodParam(p, type, rawType, ctx, defaultParamType, formMarker);
252+
final var rawType = Util.typeDef(typeMirror);
253+
final var type = Util.parse(typeMirror.toString());
254+
final var param = new MethodParam(p, type, rawType, ctx, defaultParamType, formMarker);
208255
params.add(param);
209256
param.addImports(bean);
210257
}
@@ -286,7 +333,7 @@ public String simpleName() {
286333
}
287334

288335
public boolean isFormBody() {
289-
for (MethodParam param : params) {
336+
for (final MethodParam param : params) {
290337
if (param.isForm()) {
291338
return true;
292339
}
@@ -295,7 +342,7 @@ public boolean isFormBody() {
295342
}
296343

297344
public String bodyType() {
298-
for (MethodParam param : params) {
345+
for (final MethodParam param : params) {
299346
if (param.isBody()) {
300347
return param.shortType();
301348
}
@@ -304,7 +351,7 @@ public String bodyType() {
304351
}
305352

306353
public String bodyName() {
307-
for (MethodParam param : params) {
354+
for (final MethodParam param : params) {
308355
if (param.isBody()) {
309356
return param.name();
310357
}

http-generator-core/src/main/java/io/avaje/http/generator/core/ProcessingContext.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package io.avaje.http.generator.core;
22

33
import java.io.IOException;
4+
import java.util.List;
5+
import java.util.Objects;
6+
import java.util.stream.Collectors;
47

58
import javax.annotation.processing.Filer;
69
import javax.annotation.processing.Messager;
710
import javax.annotation.processing.ProcessingEnvironment;
811
import javax.lang.model.element.Element;
12+
import javax.lang.model.element.ExecutableElement;
913
import javax.lang.model.element.TypeElement;
1014
import javax.lang.model.type.DeclaredType;
1115
import javax.lang.model.type.TypeMirror;
16+
import javax.lang.model.util.ElementFilter;
1217
import javax.lang.model.util.Elements;
1318
import javax.lang.model.util.Types;
1419
import javax.tools.Diagnostic;
@@ -115,6 +120,25 @@ public TypeMirror asMemberOf(DeclaredType declaredType, Element element) {
115120
return types.asMemberOf(declaredType, element);
116121
}
117122

123+
public List<ExecutableElement> getSuperMethods(Element element, String methodName) {
124+
125+
return types.directSupertypes(element.asType()).stream()
126+
.filter(t -> !t.toString().contains("java.lang.Object"))
127+
.map(
128+
superType -> {
129+
final var superClass = (TypeElement) types.asElement(superType);
130+
for (final ExecutableElement method :
131+
ElementFilter.methodsIn(elements.getAllMembers(superClass))) {
132+
if (method.getSimpleName().contentEquals(methodName)) {
133+
return method;
134+
}
135+
}
136+
return null;
137+
})
138+
.filter(Objects::nonNull)
139+
.collect(Collectors.toList());
140+
}
141+
118142
public PlatformAdapter platform() {
119143
return readAdapter;
120144
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.avaje.http.generator.core;
2+
3+
import javax.lang.model.element.Element;
4+
import javax.lang.model.element.ExecutableElement;
5+
import javax.lang.model.element.TypeElement;
6+
import javax.lang.model.type.TypeMirror;
7+
import javax.lang.model.util.ElementFilter;
8+
import javax.lang.model.util.Elements;
9+
import javax.lang.model.util.Types;
10+
11+
public class SuperMethodFinder {
12+
private SuperMethodFinder() {}
13+
14+
public static ExecutableElement getSuperMethod(
15+
Element element, String methodName, Types types, Elements elements) {
16+
final TypeMirror superType = types.directSupertypes(element.asType()).get(0);
17+
final var superClass = (TypeElement) types.asElement(superType);
18+
for (final ExecutableElement method :
19+
ElementFilter.methodsIn(elements.getAllMembers(superClass))) {
20+
if (method.getSimpleName().contentEquals(methodName)) {
21+
return method;
22+
}
23+
}
24+
return null;
25+
}
26+
}

http-generator-core/src/main/java/io/avaje/http/generator/core/javadoc/Javadoc.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,12 @@ public String getReturnDescription() {
6363
public boolean isDeprecated() {
6464
return deprecated;
6565
}
66+
67+
public boolean isEmpty() {
68+
return summary.isBlank()
69+
&& description.isBlank()
70+
&& params.isEmpty()
71+
&& returnDescription.isEmpty()
72+
&& !deprecated;
73+
}
6674
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.example.myapp.web.test;
2+
3+
import io.avaje.http.api.Get;
4+
import io.avaje.http.api.MediaType;
5+
import io.avaje.http.api.OpenAPIResponse;
6+
import io.avaje.http.api.Path;
7+
import io.avaje.http.api.Produces;
8+
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
9+
import io.swagger.v3.oas.annotations.info.Info;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
11+
12+
@Path("javalin")
13+
@OpenAPIDefinition(
14+
info =
15+
@Info(
16+
title = "Example service showing off the Path extension method of controller",
17+
description = ""))
18+
public interface HealthController {
19+
/**
20+
* Standard Get
21+
*
22+
* @return a health check
23+
*/
24+
@Get("/health")
25+
@Produces(MediaType.TEXT_PLAIN)
26+
@Tag(name = "tag1", description = "it's somethin")
27+
@OpenAPIResponse(responseCode = "500", type = ErrorResponse.class)
28+
String health();
29+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.example.myapp.web.test;
2+
3+
import io.avaje.http.api.Controller;
4+
5+
@Controller
6+
public class HealthControllerImpl implements HealthController {
7+
8+
@Override
9+
public String health() {
10+
11+
return "this feels like a picnic *chew*";
12+
}
13+
}

tests/test-javalin-jsonb/src/main/resources/public/openapi.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
"version" : ""
77
},
88
"tags" : [
9+
{
10+
"name" : "tag1",
11+
"description" : "it's somethin"
12+
},
913
{
1014
"name" : "tag1",
1115
"description" : "this is added to openapi tags"
@@ -764,6 +768,37 @@
764768
"deprecated" : true
765769
}
766770
},
771+
"/javalin/health" : {
772+
"get" : {
773+
"tags" : [
774+
"tag1"
775+
],
776+
"summary" : "Standard Get",
777+
"description" : "",
778+
"responses" : {
779+
"500" : {
780+
"description" : "a health check",
781+
"content" : {
782+
"text/plain" : {
783+
"schema" : {
784+
"$ref" : "#/components/schemas/ErrorResponse"
785+
}
786+
}
787+
}
788+
},
789+
"200" : {
790+
"description" : "a health check",
791+
"content" : {
792+
"text/plain" : {
793+
"schema" : {
794+
"type" : "string"
795+
}
796+
}
797+
}
798+
}
799+
}
800+
}
801+
},
767802
"/openapi/get" : {
768803
"get" : {
769804
"tags" : [

0 commit comments

Comments
 (0)