Skip to content

Commit

Permalink
Fixed a bug that javadoc of record class parameters was not recognized.
Browse files Browse the repository at this point in the history
  • Loading branch information
uc4w6c committed Mar 15, 2023
1 parent 9a6c1b9 commit eda5e70
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
*
* *
* * *
* * * * Copyright 2019-2022 the original author or authors.
* * * * Copyright 2019-2023 the original author or authors.
* * * *
* * * * Licensed under the Apache License, Version 2.0 (the "License");
* * * * you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -720,17 +720,29 @@ public boolean isRequestBodyPresent(ParameterInfo parameterInfo) {
String getParamJavadoc(JavadocProvider javadocProvider, MethodParameter methodParameter) {
String pName = methodParameter.getParameterName();
DelegatingMethodParameter delegatingMethodParameter = (DelegatingMethodParameter) methodParameter;
final String paramJavadocDescription;
if (delegatingMethodParameter.isParameterObject()) {
String fieldName;
if (StringUtils.isNotEmpty(pName) && pName.contains(DOT))
fieldName = StringUtils.substringAfterLast(pName, DOT);
else fieldName = pName;
Field field = FieldUtils.getDeclaredField(((DelegatingMethodParameter) methodParameter).getExecutable().getDeclaringClass(), fieldName, true);
paramJavadocDescription = javadocProvider.getFieldJavadoc(field);
if (!delegatingMethodParameter.isParameterObject()) {
return javadocProvider.getParamJavadoc(methodParameter.getMethod(), pName);
}
else
paramJavadocDescription = javadocProvider.getParamJavadoc(methodParameter.getMethod(), pName);
String fieldName;
if (StringUtils.isNotEmpty(pName) && pName.contains(DOT))
fieldName = StringUtils.substringAfterLast(pName, DOT);
else fieldName = pName;

String paramJavadocDescription = null;
Class cls = ((DelegatingMethodParameter) methodParameter).getExecutable().getDeclaringClass();
if (cls.getSuperclass() != null && "java.lang.Record".equals(cls.getSuperclass().getName())) {
Map<String, String> recordParamMap = javadocProvider.getRecordClassParamJavadoc(cls);
if (recordParamMap.containsKey(fieldName)) {
paramJavadocDescription = recordParamMap.get(fieldName);
}
}

Field field = FieldUtils.getDeclaredField(cls, fieldName, true);
String fieldJavadoc = javadocProvider.getFieldJavadoc(field);
if (StringUtils.isNotBlank(fieldJavadoc)) {
paramJavadocDescription = fieldJavadoc;
}

return paramJavadocDescription;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
*
* *
* * *
* * * * Copyright 2019-2022 the original author or authors.
* * * * Copyright 2019-2023 the original author or authors.
* * * *
* * * * Licensed under the Apache License, Version 2.0 (the "License");
* * * * you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -41,11 +41,19 @@ public interface JavadocProvider {
String getClassJavadoc(Class<?> cl);

/**
* Gets method description.
* Gets param descripton of record class.
*
* @param method the method
* @return the method description
* @param cl the class
* @return map of field and param descriptions
*/
Map<String, String> getRecordClassParamJavadoc(Class<?> cl);

/**
* Gets method description.
*
* @param method the method
* @return the method description
*/
String getMethodJavadocDescription(Method method);

/**
Expand Down Expand Up @@ -88,4 +96,3 @@ public interface JavadocProvider {
*/
String getFirstSentence(String text);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
*
* *
* * *
* * * * Copyright 2019-2023 the original author or authors.
* * * *
* * * * Licensed under the Apache License, Version 2.0 (the "License");
* * * * you may not use this file except in compliance with the License.
* * * * You may obtain a copy of the License at
* * * *
* * * * https://www.apache.org/licenses/LICENSE-2.0
* * * *
* * * * Unless required by applicable law or agreed to in writing, software
* * * * distributed under the License is distributed on an "AS IS" BASIS,
* * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * * * See the License for the specific language governing permissions and
* * * * limitations under the License.
* * *
* *
*
*/

package org.springdoc.core;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springdoc.core.customizers.DelegatingMethodParameterCustomizer;
import org.springdoc.core.providers.JavadocProvider;
import org.springdoc.core.providers.ObjectMapperProvider;
import org.springdoc.core.providers.WebConversionServiceProvider;

import org.springframework.core.MethodParameter;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
* Tests for {@link GenericParameterService}.
*/
class GenericParameterServiceTest {
@TempDir
private File tempDir;

@Mock
private PropertyResolverUtils propertyResolverUtils;

@Mock
private DelegatingMethodParameterCustomizer delegatingMethodParameterCustomizer;

@Mock
private WebConversionServiceProvider webConversionServiceProvider;

@Mock
private ObjectMapperProvider objectMapperProvider;

@Mock
private JavadocProvider javadocProvider;

private GenericParameterService genericParameterService;

@BeforeEach
void setup() {
MockitoAnnotations.openMocks(this);
this.genericParameterService = new GenericParameterService(propertyResolverUtils, Optional.of(delegatingMethodParameterCustomizer), Optional.of(webConversionServiceProvider), objectMapperProvider, Optional.of(javadocProvider));
}

/**
* Tests for {@link GenericParameterService#getParamJavadoc(JavadocProvider, MethodParameter)}.
*/
@Nested
class getParamJavadoc {
@Mock
private DelegatingMethodParameter methodParameter;

@BeforeEach
void setup() {
MockitoAnnotations.openMocks(this);
}

@Test
@EnabledForJreRange(min = JRE.JAVA_17)
void hasDescriptionOfRecordObject() throws IOException, ClassNotFoundException, NoSuchMethodException {
Class cls = createRecordObject();
Method method = cls.getMethod("name");

when(methodParameter.getParameterName()).thenReturn("name");
when(methodParameter.isParameterObject()).thenReturn(true);
when(methodParameter.getExecutable()).thenReturn(method);

Map<String, String> recordParamMap = new HashMap<>();
recordParamMap.put("id", "the id");
recordParamMap.put("name", "the name");
when(javadocProvider.getRecordClassParamJavadoc(cls)).thenReturn(recordParamMap);

when(javadocProvider.getFieldJavadoc(any())).thenReturn(null);

String actual = genericParameterService.getParamJavadoc(javadocProvider, methodParameter);
assertEquals("the name", actual);

verify(methodParameter).getParameterName();
verify(methodParameter).isParameterObject();
verify(methodParameter).getExecutable();
verify(javadocProvider).getRecordClassParamJavadoc(cls);
verify(javadocProvider).getFieldJavadoc(any());
}

@Test
void hasDescriptionOfClassObject() throws IOException, ClassNotFoundException, NoSuchMethodException {
Class cls = ClassObject.class;
Method method = cls.getMethod("getName");

when(methodParameter.getParameterName()).thenReturn("name");
when(methodParameter.isParameterObject()).thenReturn(true);
when(methodParameter.getExecutable()).thenReturn(method);

when(javadocProvider.getFieldJavadoc(any())).thenReturn("the name");

String actual = genericParameterService.getParamJavadoc(javadocProvider, methodParameter);
assertEquals("the name", actual);

verify(methodParameter).getParameterName();
verify(methodParameter).isParameterObject();
verify(methodParameter).getExecutable();
verify(javadocProvider).getFieldJavadoc(any());
}

private Class<?> createRecordObject() throws IOException, ClassNotFoundException {
File recordObject = new File(tempDir, "RecordObject.java");
try (PrintWriter writer = new PrintWriter(new FileWriter(recordObject))) {
writer.println("public record RecordObject(String id, String name){");
writer.println("}");
}
String[] args = {
recordObject.getAbsolutePath()
};
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int r = compiler.run(null, null, null, args);
if (r != 0) {
throw new IllegalStateException("Compilation failed");
}
URL[] urls = { tempDir.toURI().toURL() };
ClassLoader loader = URLClassLoader.newInstance(urls);

return loader.loadClass("RecordObject");
}

private class ClassObject {
/**
* the id
*/
private String id;

/**
* the name
*/
private String name;

public ClassObject(String id, String name) {
this.id = id;
this.name = name;
}

public String getId() {
return id;
}

public String getName() {
return name;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
*
* *
* * *
* * * * Copyright 2019-2022 the original author or authors.
* * * * Copyright 2019-2023 the original author or authors.
* * * *
* * * * Licensed under the Apache License, Version 2.0 (the "License");
* * * * you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -109,13 +109,23 @@ else if (resolvedSchema != null && resolvedSchema.get$ref() != null && resolvedS
* @param fields the fields
* @param existingSchema the existing schema
*/
private void setJavadocDescription(Class<?> cls, List<Field> fields, Schema existingSchema) {
void setJavadocDescription(Class<?> cls, List<Field> fields, Schema existingSchema) {
if (existingSchema != null) {
if (StringUtils.isBlank(existingSchema.getDescription())) {
existingSchema.setDescription(javadocProvider.getClassJavadoc(cls));
}
Map<String, Schema> properties = existingSchema.getProperties();
if (!CollectionUtils.isEmpty(properties))
if (!CollectionUtils.isEmpty(properties)) {
if (cls.getSuperclass() != null && "java.lang.Record".equals(cls.getSuperclass().getName())) {
Map<String, String> recordParamMap = javadocProvider.getRecordClassParamJavadoc(cls);
properties.entrySet().stream()
.filter(stringSchemaEntry -> StringUtils.isBlank(stringSchemaEntry.getValue().getDescription()))
.forEach(stringSchemaEntry -> {
if (recordParamMap.containsKey(stringSchemaEntry.getKey()))
stringSchemaEntry.getValue().setDescription(recordParamMap.get(stringSchemaEntry.getKey()));
});
}

properties.entrySet().stream()
.filter(stringSchemaEntry -> StringUtils.isBlank(stringSchemaEntry.getValue().getDescription()))
.forEach(stringSchemaEntry -> {
Expand All @@ -126,6 +136,7 @@ private void setJavadocDescription(Class<?> cls, List<Field> fields, Schema exis
stringSchemaEntry.getValue().setDescription(fieldJavadoc);
});
});
}
fields.stream().filter(f -> f.isAnnotationPresent(JsonUnwrapped.class))
.forEach(f -> setJavadocDescription(f.getType(), FieldUtils.getAllFieldsList(f.getType()), existingSchema));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
*
* *
* * *
* * * * Copyright 2019-2022 the original author or authors.
* * * * Copyright 2019-2023 the original author or authors.
* * * *
* * * * Licensed under the Apache License, Version 2.0 (the "License");
* * * * you may not use this file except in compliance with the License.
Expand All @@ -26,6 +26,7 @@
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.github.therapi.runtimejavadoc.ClassJavadoc;
import com.github.therapi.runtimejavadoc.CommentFormatter;
Expand Down Expand Up @@ -64,6 +65,19 @@ public String getClassJavadoc(Class<?> cl) {
return formatter.format(classJavadoc.getComment());
}

/**
* Gets param descripton of record class.
*
* @param cl the class
* @return map of field and param descriptions
*/
@Override
public Map<String, String> getRecordClassParamJavadoc(Class<?> cl) {
ClassJavadoc classJavadoc = RuntimeJavadoc.getJavadoc(cl);
return classJavadoc.getRecordComponents().stream()
.collect(Collectors.toMap(ParamJavadoc::getName, record -> formatter.format(record.getComment())));
}

/**
* Gets method javadoc description.
*
Expand Down

0 comments on commit eda5e70

Please sign in to comment.