Skip to content

Commit 0849187

Browse files
committed
docs: generate a specific documentation file for each config group
The file can be included via \include::{generated-dir}/config/quarkus-your-extension-dashed-config-group-simple-name.adoc[opts=optional, leveloffset=+1]
1 parent 2425ec2 commit 0849187

File tree

11 files changed

+496
-332
lines changed

11 files changed

+496
-332
lines changed

core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.util.Collections;
2626
import java.util.HashSet;
2727
import java.util.List;
28-
import java.util.Map;
2928
import java.util.Properties;
3029
import java.util.Set;
3130
import java.util.TreeSet;
@@ -66,9 +65,9 @@
6665
import org.jboss.jdeparser.JType;
6766
import org.jboss.jdeparser.JTypes;
6867

69-
import io.quarkus.annotation.processor.generate_doc.ConfigDocItem;
7068
import io.quarkus.annotation.processor.generate_doc.ConfigDocItemScanner;
7169
import io.quarkus.annotation.processor.generate_doc.ConfigDocWriter;
70+
import io.quarkus.annotation.processor.generate_doc.ScannedConfigDocsItemHolder;
7271

7372
public class ExtensionAnnotationProcessor extends AbstractProcessor {
7473

@@ -236,9 +235,12 @@ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)
236235
}
237236

238237
try {
239-
final Map<String, List<ConfigDocItem>> extensionConfigurationItems = configDocItemScanner
238+
final ScannedConfigDocsItemHolder scannedConfigDocsItemHolder = configDocItemScanner
240239
.scanExtensionsConfigurationItems(javaDocProperties);
241-
configDocWriter.writeExtensionConfigDocumentation(extensionConfigurationItems);
240+
241+
configDocWriter.writeExtensionConfigDocumentation(scannedConfigDocsItemHolder.getAllConfigItemsPerExtension(),
242+
true); // generate extension doc with search engine activate
243+
configDocWriter.writeExtensionConfigDocumentation(scannedConfigDocsItemHolder.getConfigGroupConfigItems(), false); // generate config group docs with search engine deactivated
242244
} catch (IOException e) {
243245
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to generate extension doc: " + e);
244246
return;
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
package io.quarkus.annotation.processor.generate_doc;
2+
3+
import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getJavaDocSiteLink;
4+
import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getKnownGenericType;
5+
import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.hyphenate;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Properties;
11+
import java.util.Set;
12+
13+
import javax.lang.model.element.AnnotationMirror;
14+
import javax.lang.model.element.AnnotationValue;
15+
import javax.lang.model.element.Element;
16+
import javax.lang.model.element.ElementKind;
17+
import javax.lang.model.element.ExecutableElement;
18+
import javax.lang.model.element.Modifier;
19+
import javax.lang.model.element.Name;
20+
import javax.lang.model.element.TypeElement;
21+
import javax.lang.model.type.DeclaredType;
22+
import javax.lang.model.type.TypeMirror;
23+
24+
import io.quarkus.annotation.processor.Constants;
25+
26+
class ConfigDoItemFinder {
27+
28+
private static final String NAMED_MAP_CONFIG_ITEM_FORMAT = ".\"%s\"";
29+
private final JavaDocParser javaDocParser = new JavaDocParser();
30+
private final ScannedConfigDocsItemHolder holder = new ScannedConfigDocsItemHolder();
31+
32+
private final Set<ConfigRootInfo> configRoots;
33+
private final Map<String, TypeElement> configGroups;
34+
private final Properties javaDocProperties;
35+
36+
public ConfigDoItemFinder(Set<ConfigRootInfo> configRoots, Map<String, TypeElement> configGroups,
37+
Properties javaDocProperties) {
38+
this.configRoots = configRoots;
39+
this.configGroups = configGroups;
40+
this.javaDocProperties = javaDocProperties;
41+
}
42+
43+
/**
44+
* Find configuration items from current encountered configuration roots
45+
*/
46+
ScannedConfigDocsItemHolder findInMemoryConfigurationItems() {
47+
for (ConfigRootInfo configRootInfo : configRoots) {
48+
final TypeElement element = configRootInfo.getClazz();
49+
/**
50+
* Config sections will start at level 2 i.e the section title will be prefixed with
51+
* ('='* (N + 1)) where N is section level.
52+
*/
53+
final int sectionLevel = 2;
54+
final List<ConfigDocItem> configDocItems = recursivelyFindConfigItems(element, configRootInfo.getName(),
55+
configRootInfo.getConfigPhase(), false, sectionLevel);
56+
holder.addToAllConfigItems(configRootInfo.getClazz().getQualifiedName().toString(), configDocItems);
57+
}
58+
59+
return holder;
60+
}
61+
62+
/**
63+
* Recursively find config item found in a config root given as {@link Element}
64+
*
65+
* @param element - root element
66+
* @param parentName - root name
67+
* @param configPhase - configuration phase see {@link ConfigPhase}
68+
* @param withinAMap - indicates if a a key is within a map or is a map configuration key
69+
* @param sectionLevel - section sectionLevel
70+
*/
71+
private List<ConfigDocItem> recursivelyFindConfigItems(Element element, String parentName,
72+
ConfigPhase configPhase, boolean withinAMap, int sectionLevel) {
73+
List<ConfigDocItem> configDocItems = new ArrayList<>();
74+
for (Element enclosedElement : element.getEnclosedElements()) {
75+
if (!enclosedElement.getKind().isField()) {
76+
continue;
77+
}
78+
79+
boolean isStaticField = enclosedElement
80+
.getModifiers()
81+
.stream()
82+
.anyMatch(Modifier.STATIC::equals);
83+
84+
if (isStaticField) {
85+
continue;
86+
}
87+
88+
String name = Constants.EMPTY;
89+
String defaultValue = Constants.NO_DEFAULT;
90+
String defaultValueDoc = Constants.EMPTY;
91+
final TypeMirror typeMirror = enclosedElement.asType();
92+
String type = typeMirror.toString();
93+
List<String> acceptedValues = null;
94+
Element configGroup = configGroups.get(type);
95+
ConfigDocSection configSection = null;
96+
boolean isConfigGroup = configGroup != null;
97+
final TypeElement clazz = (TypeElement) element;
98+
final String fieldName = enclosedElement.getSimpleName().toString();
99+
final String javaDocKey = clazz.getQualifiedName().toString() + Constants.DOT + fieldName;
100+
final List<? extends AnnotationMirror> annotationMirrors = enclosedElement.getAnnotationMirrors();
101+
final String rawJavaDoc = javaDocProperties.getProperty(javaDocKey);
102+
103+
String hyphenatedFieldName = hyphenate(fieldName);
104+
String configDocMapKey = hyphenatedFieldName;
105+
106+
for (AnnotationMirror annotationMirror : annotationMirrors) {
107+
String annotationName = annotationMirror.getAnnotationType().toString();
108+
if (annotationName.equals(Constants.ANNOTATION_CONFIG_ITEM)
109+
|| annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_MAP_KEY)) {
110+
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror
111+
.getElementValues().entrySet()) {
112+
final String key = entry.getKey().toString();
113+
final String value = entry.getValue().getValue().toString();
114+
if (annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_MAP_KEY) && "value()".equals(key)) {
115+
configDocMapKey = value;
116+
} else if (annotationName.equals(Constants.ANNOTATION_CONFIG_ITEM)) {
117+
if ("name()".equals(key)) {
118+
switch (value) {
119+
case Constants.HYPHENATED_ELEMENT_NAME:
120+
name = parentName + Constants.DOT + hyphenatedFieldName;
121+
break;
122+
case Constants.PARENT:
123+
name = parentName;
124+
break;
125+
default:
126+
name = parentName + Constants.DOT + value;
127+
}
128+
} else if ("defaultValue()".equals(key)) {
129+
defaultValue = value;
130+
} else if ("defaultValueDocumentation()".equals(key)) {
131+
defaultValueDoc = value;
132+
}
133+
}
134+
}
135+
} else if (annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_SECTION)) {
136+
final JavaDocParser.SectionHolder sectionHolder = javaDocParser.parseConfigSection(rawJavaDoc,
137+
sectionLevel);
138+
139+
configSection = new ConfigDocSection();
140+
configSection.setWithinAMap(withinAMap);
141+
configSection.setConfigPhase(configPhase);
142+
configSection.setSectionDetails(sectionHolder.details);
143+
configSection.setSectionDetailsTitle(sectionHolder.title);
144+
configSection.setName(parentName + Constants.DOT + hyphenatedFieldName);
145+
}
146+
}
147+
148+
if (name.isEmpty()) {
149+
name = parentName + Constants.DOT + hyphenatedFieldName;
150+
}
151+
152+
if (Constants.NO_DEFAULT.equals(defaultValue)) {
153+
defaultValue = Constants.EMPTY;
154+
}
155+
if (Constants.EMPTY.equals(defaultValue)) {
156+
defaultValue = defaultValueDoc;
157+
}
158+
159+
if (isConfigGroup) {
160+
List<ConfigDocItem> groupConfigItems = recordConfigItemsFromConfigGroup(configPhase, name, configGroup,
161+
configSection, withinAMap, sectionLevel);
162+
configDocItems.addAll(groupConfigItems);
163+
} else {
164+
final ConfigDocKey configDocKey = new ConfigDocKey();
165+
configDocKey.setWithinAMap(withinAMap);
166+
boolean optional = false;
167+
boolean list = false;
168+
if (!typeMirror.getKind().isPrimitive()) {
169+
DeclaredType declaredType = (DeclaredType) typeMirror;
170+
TypeElement typeElement = (TypeElement) declaredType.asElement();
171+
Name qualifiedName = typeElement.getQualifiedName();
172+
optional = qualifiedName.toString().startsWith("java.util.Optional");
173+
list = qualifiedName.contentEquals("java.util.List");
174+
175+
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
176+
if (!typeArguments.isEmpty()) {
177+
// FIXME: this is super dodgy: we should check the type!!
178+
if (typeArguments.size() == 2) {
179+
final String mapKey = String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey);
180+
type = typeArguments.get(1).toString();
181+
configGroup = configGroups.get(type);
182+
name += mapKey;
183+
184+
if (configGroup != null) {
185+
name += String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey);
186+
List<ConfigDocItem> groupConfigItems = recordConfigItemsFromConfigGroup(configPhase, name,
187+
configGroup, configSection, true, sectionLevel);
188+
configDocItems.addAll(groupConfigItems);
189+
continue;
190+
} else {
191+
configDocKey.setWithinAMap(true);
192+
}
193+
} else {
194+
// FIXME: this is for Optional<T> and List<T>
195+
TypeMirror realTypeMirror = typeArguments.get(0);
196+
type = simpleTypeToString(realTypeMirror);
197+
198+
if (isEnumType(realTypeMirror)) {
199+
acceptedValues = extractEnumValues(realTypeMirror);
200+
}
201+
}
202+
} else {
203+
type = simpleTypeToString(declaredType);
204+
if (isEnumType(declaredType)) {
205+
acceptedValues = extractEnumValues(declaredType);
206+
}
207+
}
208+
}
209+
210+
final String configDescription = javaDocParser.parseConfigDescription(rawJavaDoc);
211+
212+
configDocKey.setKey(name);
213+
configDocKey.setType(type);
214+
configDocKey.setConfigPhase(configPhase);
215+
configDocKey.setDefaultValue(defaultValue);
216+
configDocKey.setOptional(optional);
217+
configDocKey.setList(list);
218+
configDocKey.setConfigDoc(configDescription);
219+
configDocKey.setAcceptedValues(acceptedValues);
220+
configDocKey.setJavaDocSiteLink(getJavaDocSiteLink(type));
221+
ConfigDocItem configDocItem = new ConfigDocItem();
222+
configDocItem.setConfigDocKey(configDocKey);
223+
configDocItems.add(configDocItem);
224+
}
225+
}
226+
227+
return configDocItems;
228+
}
229+
230+
private List<ConfigDocItem> recordConfigItemsFromConfigGroup(ConfigPhase configPhase, String name, Element configGroup,
231+
ConfigDocSection configSection, boolean withinAMap, int sectionLevel) {
232+
final List<ConfigDocItem> groupConfigItems;
233+
final List<ConfigDocItem> configDocItems = new ArrayList<>();
234+
235+
if (configSection != null) {
236+
final ConfigDocItem configDocItem = new ConfigDocItem();
237+
configDocItem.setConfigDocSection(configSection);
238+
configDocItems.add(configDocItem);
239+
groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap,
240+
sectionLevel + 1);
241+
configSection.addConfigDocItems(groupConfigItems);
242+
} else {
243+
groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap, sectionLevel);
244+
configDocItems.addAll(groupConfigItems);
245+
}
246+
247+
String configGroupName = configGroup.asType().toString();
248+
List<ConfigDocItem> previousConfigGroupConfigItems = holder.getConfigGroupConfigItems().get(configGroupName);
249+
if (previousConfigGroupConfigItems == null) {
250+
holder.addConfigGroupItems(configGroupName, groupConfigItems);
251+
} else {
252+
previousConfigGroupConfigItems.addAll(configDocItems);
253+
}
254+
255+
return configDocItems;
256+
}
257+
258+
private String simpleTypeToString(TypeMirror typeMirror) {
259+
if (typeMirror.getKind().isPrimitive()) {
260+
return typeMirror.toString();
261+
}
262+
263+
final String knownGenericType = getKnownGenericType((DeclaredType) typeMirror);
264+
return knownGenericType != null ? knownGenericType : typeMirror.toString();
265+
}
266+
267+
private List<String> extractEnumValues(TypeMirror realTypeMirror) {
268+
Element declaredTypeElement = ((DeclaredType) realTypeMirror).asElement();
269+
List<String> acceptedValues = new ArrayList<>();
270+
271+
for (Element field : declaredTypeElement.getEnclosedElements()) {
272+
if (field.getKind() == ElementKind.ENUM_CONSTANT) {
273+
acceptedValues.add(DocGeneratorUtil.hyphenateEnumValue(field.getSimpleName().toString()));
274+
}
275+
}
276+
277+
return acceptedValues;
278+
}
279+
280+
private boolean isEnumType(TypeMirror realTypeMirror) {
281+
return realTypeMirror instanceof DeclaredType
282+
&& ((DeclaredType) realTypeMirror).asElement().getKind() == ElementKind.ENUM;
283+
}
284+
}

0 commit comments

Comments
 (0)