Skip to content

Commit c0274ea

Browse files
committed
Support Java record for v1.6. Fixes #1830
1 parent 0b51a68 commit c0274ea

File tree

2 files changed

+154
-12
lines changed

2 files changed

+154
-12
lines changed

springdoc-openapi-common/src/main/java/org/springdoc/core/MethodParameterPojoExtractor.java

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525
import java.beans.Introspector;
2626
import java.beans.PropertyDescriptor;
2727
import java.lang.annotation.Annotation;
28-
import java.lang.reflect.Field;
29-
import java.lang.reflect.Type;
30-
import java.lang.reflect.TypeVariable;
28+
import java.lang.reflect.*;
3129
import java.nio.charset.Charset;
3230
import java.time.Duration;
3331
import java.time.LocalTime;
@@ -175,17 +173,39 @@ private static Stream<MethodParameter> fromSimpleClass(Class<?> paramClass, Fiel
175173
Annotation[] fieldAnnotations = field.getDeclaredAnnotations();
176174
try {
177175
Parameter parameter = field.getAnnotation(Parameter.class);
178-
boolean isNotRequired = parameter == null || !parameter.required();
176+
boolean isNotRequired = parameter == null || !parameter.required();
179177
Annotation[] finalFieldAnnotations = fieldAnnotations;
180-
return Stream.of(Introspector.getBeanInfo(paramClass).getPropertyDescriptors())
181-
.filter(d -> d.getName().equals(field.getName()))
182-
.map(PropertyDescriptor::getReadMethod)
183-
.filter(Objects::nonNull)
184-
.map(method -> new MethodParameter(method, -1))
185-
.map(methodParameter -> DelegatingMethodParameter.changeContainingClass(methodParameter, paramClass))
186-
.map(param -> new DelegatingMethodParameter(param, fieldNamePrefix + field.getName(), finalFieldAnnotations, true, isNotRequired));
178+
179+
if ("java.lang.Record".equals(paramClass.getSuperclass().getName())) {
180+
Method classGetRecordComponents = Class.class.getMethod("getRecordComponents");
181+
Object[] components = (Object[]) classGetRecordComponents.invoke(paramClass);
182+
183+
Class<?> c = Class.forName("java.lang.reflect.RecordComponent");
184+
Method recordComponentGetAccessor = c.getMethod("getAccessor");
185+
186+
List<Method> methods = new ArrayList<>();
187+
for (Object object : components) {
188+
methods.add((Method) recordComponentGetAccessor.invoke(object));
189+
}
190+
return methods.stream()
191+
.filter(method -> method.getName().equals(field.getName()))
192+
.map(method -> new MethodParameter(method, -1))
193+
.map(methodParameter -> DelegatingMethodParameter.changeContainingClass(methodParameter, paramClass))
194+
.map(param -> new DelegatingMethodParameter(param, fieldNamePrefix + field.getName(), finalFieldAnnotations, true, isNotRequired));
195+
196+
}
197+
else
198+
return Stream.of(Introspector.getBeanInfo(paramClass).getPropertyDescriptors())
199+
.filter(d -> d.getName().equals(field.getName()))
200+
.map(PropertyDescriptor::getReadMethod)
201+
.filter(Objects::nonNull)
202+
.map(method -> new MethodParameter(method, -1))
203+
.map(methodParameter -> DelegatingMethodParameter.changeContainingClass(methodParameter, paramClass))
204+
.map(param -> new DelegatingMethodParameter(param, fieldNamePrefix + field.getName(), finalFieldAnnotations, true, isNotRequired));
187205
}
188-
catch (IntrospectionException e) {
206+
catch (IntrospectionException | NoSuchMethodException |
207+
InvocationTargetException | IllegalAccessException |
208+
ClassNotFoundException e) {
189209
return Stream.of();
190210
}
191211
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * * Copyright 2019-2022 the original author or authors.
6+
* * * *
7+
* * * * Licensed under the Apache License, Version 2.0 (the "License");
8+
* * * * you may not use this file except in compliance with the License.
9+
* * * * You may obtain a copy of the License at
10+
* * * *
11+
* * * * https://www.apache.org/licenses/LICENSE-2.0
12+
* * * *
13+
* * * * Unless required by applicable law or agreed to in writing, software
14+
* * * * distributed under the License is distributed on an "AS IS" BASIS,
15+
* * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* * * * See the License for the specific language governing permissions and
17+
* * * * limitations under the License.
18+
* * *
19+
* *
20+
*
21+
*/
22+
23+
package org.springdoc.core;
24+
25+
import org.junit.jupiter.api.Nested;
26+
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.api.condition.EnabledForJreRange;
28+
import org.junit.jupiter.api.condition.JRE;
29+
import org.junit.jupiter.api.io.TempDir;
30+
31+
import org.springframework.core.MethodParameter;
32+
33+
import javax.tools.JavaCompiler;
34+
import javax.tools.ToolProvider;
35+
36+
import java.io.File;
37+
import java.io.FileWriter;
38+
import java.io.IOException;
39+
import java.io.PrintWriter;
40+
import java.lang.reflect.Method;
41+
import java.net.URL;
42+
import java.net.URLClassLoader;
43+
import java.util.stream.Stream;
44+
45+
import static org.assertj.core.api.Assertions.assertThat;
46+
47+
/**
48+
* Tests for {@link MethodParameterPojoExtractor}.
49+
*/
50+
class MethodParameterPojoExtractorTest {
51+
@TempDir
52+
File tempDir;
53+
54+
/**
55+
* Tests for {@link MethodParameterPojoExtractor#extractFrom(Class<?>)}.
56+
*/
57+
@Nested
58+
class extractFrom {
59+
@Test
60+
@EnabledForJreRange(min = JRE.JAVA_17)
61+
void ifRecordObjectShouldGetField() throws IOException, ClassNotFoundException {
62+
File recordObject = new File(tempDir, "RecordObject.java");
63+
try (PrintWriter writer = new PrintWriter(new FileWriter(recordObject))) {
64+
writer.println("public record RecordObject(String email, String firstName, String lastName){");
65+
writer.println("}");
66+
}
67+
String[] args = {
68+
recordObject.getAbsolutePath()
69+
};
70+
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
71+
int r = compiler.run(null, null, null, args);
72+
if (r != 0) {
73+
throw new IllegalStateException("Compilation failed");
74+
}
75+
URL[] urls = { tempDir.toURI().toURL() };
76+
ClassLoader loader = URLClassLoader.newInstance(urls);
77+
78+
Class<?> clazz = loader.loadClass("RecordObject");
79+
80+
Stream<MethodParameter> actual = MethodParameterPojoExtractor.extractFrom(clazz);
81+
assertThat(actual)
82+
.extracting(MethodParameter::getMethod)
83+
.extracting(Method::getName)
84+
.containsOnlyOnce("email", "firstName", "lastName");
85+
}
86+
87+
@Test
88+
void ifRecordObjectShouldGetMethod() {
89+
Stream<MethodParameter> actual = MethodParameterPojoExtractor.extractFrom(ClassObject.class);
90+
assertThat(actual)
91+
.extracting(MethodParameter::getMethod)
92+
.extracting(Method::getName)
93+
.containsOnlyOnce("getEmail", "getFirstName", "getLastName");
94+
}
95+
96+
public class ClassObject {
97+
private String email;
98+
99+
private String firstName;
100+
101+
private String lastName;
102+
103+
public ClassObject(String email, String firstName, String lastName) {
104+
this.email = email;
105+
this.firstName = firstName;
106+
this.lastName = lastName;
107+
}
108+
109+
public String getEmail() {
110+
return email;
111+
}
112+
113+
public String getFirstName() {
114+
return firstName;
115+
}
116+
117+
public String getLastName() {
118+
return lastName;
119+
}
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)