-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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]
- Loading branch information
Showing
11 changed files
with
496 additions
and
332 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
284 changes: 284 additions & 0 deletions
284
...cessor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
package io.quarkus.annotation.processor.generate_doc; | ||
|
||
import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getJavaDocSiteLink; | ||
import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getKnownGenericType; | ||
import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.hyphenate; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Properties; | ||
import java.util.Set; | ||
|
||
import javax.lang.model.element.AnnotationMirror; | ||
import javax.lang.model.element.AnnotationValue; | ||
import javax.lang.model.element.Element; | ||
import javax.lang.model.element.ElementKind; | ||
import javax.lang.model.element.ExecutableElement; | ||
import javax.lang.model.element.Modifier; | ||
import javax.lang.model.element.Name; | ||
import javax.lang.model.element.TypeElement; | ||
import javax.lang.model.type.DeclaredType; | ||
import javax.lang.model.type.TypeMirror; | ||
|
||
import io.quarkus.annotation.processor.Constants; | ||
|
||
class ConfigDoItemFinder { | ||
|
||
private static final String NAMED_MAP_CONFIG_ITEM_FORMAT = ".\"%s\""; | ||
private final JavaDocParser javaDocParser = new JavaDocParser(); | ||
private final ScannedConfigDocsItemHolder holder = new ScannedConfigDocsItemHolder(); | ||
|
||
private final Set<ConfigRootInfo> configRoots; | ||
private final Map<String, TypeElement> configGroups; | ||
private final Properties javaDocProperties; | ||
|
||
public ConfigDoItemFinder(Set<ConfigRootInfo> configRoots, Map<String, TypeElement> configGroups, | ||
Properties javaDocProperties) { | ||
this.configRoots = configRoots; | ||
this.configGroups = configGroups; | ||
this.javaDocProperties = javaDocProperties; | ||
} | ||
|
||
/** | ||
* Find configuration items from current encountered configuration roots | ||
*/ | ||
ScannedConfigDocsItemHolder findInMemoryConfigurationItems() { | ||
for (ConfigRootInfo configRootInfo : configRoots) { | ||
final TypeElement element = configRootInfo.getClazz(); | ||
/** | ||
* Config sections will start at level 2 i.e the section title will be prefixed with | ||
* ('='* (N + 1)) where N is section level. | ||
*/ | ||
final int sectionLevel = 2; | ||
final List<ConfigDocItem> configDocItems = recursivelyFindConfigItems(element, configRootInfo.getName(), | ||
configRootInfo.getConfigPhase(), false, sectionLevel); | ||
holder.addToAllConfigItems(configRootInfo.getClazz().getQualifiedName().toString(), configDocItems); | ||
} | ||
|
||
return holder; | ||
} | ||
|
||
/** | ||
* Recursively find config item found in a config root given as {@link Element} | ||
* | ||
* @param element - root element | ||
* @param parentName - root name | ||
* @param configPhase - configuration phase see {@link ConfigPhase} | ||
* @param withinAMap - indicates if a a key is within a map or is a map configuration key | ||
* @param sectionLevel - section sectionLevel | ||
*/ | ||
private List<ConfigDocItem> recursivelyFindConfigItems(Element element, String parentName, | ||
ConfigPhase configPhase, boolean withinAMap, int sectionLevel) { | ||
List<ConfigDocItem> configDocItems = new ArrayList<>(); | ||
for (Element enclosedElement : element.getEnclosedElements()) { | ||
if (!enclosedElement.getKind().isField()) { | ||
continue; | ||
} | ||
|
||
boolean isStaticField = enclosedElement | ||
.getModifiers() | ||
.stream() | ||
.anyMatch(Modifier.STATIC::equals); | ||
|
||
if (isStaticField) { | ||
continue; | ||
} | ||
|
||
String name = Constants.EMPTY; | ||
String defaultValue = Constants.NO_DEFAULT; | ||
String defaultValueDoc = Constants.EMPTY; | ||
final TypeMirror typeMirror = enclosedElement.asType(); | ||
String type = typeMirror.toString(); | ||
List<String> acceptedValues = null; | ||
Element configGroup = configGroups.get(type); | ||
ConfigDocSection configSection = null; | ||
boolean isConfigGroup = configGroup != null; | ||
final TypeElement clazz = (TypeElement) element; | ||
final String fieldName = enclosedElement.getSimpleName().toString(); | ||
final String javaDocKey = clazz.getQualifiedName().toString() + Constants.DOT + fieldName; | ||
final List<? extends AnnotationMirror> annotationMirrors = enclosedElement.getAnnotationMirrors(); | ||
final String rawJavaDoc = javaDocProperties.getProperty(javaDocKey); | ||
|
||
String hyphenatedFieldName = hyphenate(fieldName); | ||
String configDocMapKey = hyphenatedFieldName; | ||
|
||
for (AnnotationMirror annotationMirror : annotationMirrors) { | ||
String annotationName = annotationMirror.getAnnotationType().toString(); | ||
if (annotationName.equals(Constants.ANNOTATION_CONFIG_ITEM) | ||
|| annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_MAP_KEY)) { | ||
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror | ||
.getElementValues().entrySet()) { | ||
final String key = entry.getKey().toString(); | ||
final String value = entry.getValue().getValue().toString(); | ||
if (annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_MAP_KEY) && "value()".equals(key)) { | ||
configDocMapKey = value; | ||
} else if (annotationName.equals(Constants.ANNOTATION_CONFIG_ITEM)) { | ||
if ("name()".equals(key)) { | ||
switch (value) { | ||
case Constants.HYPHENATED_ELEMENT_NAME: | ||
name = parentName + Constants.DOT + hyphenatedFieldName; | ||
break; | ||
case Constants.PARENT: | ||
name = parentName; | ||
break; | ||
default: | ||
name = parentName + Constants.DOT + value; | ||
} | ||
} else if ("defaultValue()".equals(key)) { | ||
defaultValue = value; | ||
} else if ("defaultValueDocumentation()".equals(key)) { | ||
defaultValueDoc = value; | ||
} | ||
} | ||
} | ||
} else if (annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_SECTION)) { | ||
final JavaDocParser.SectionHolder sectionHolder = javaDocParser.parseConfigSection(rawJavaDoc, | ||
sectionLevel); | ||
|
||
configSection = new ConfigDocSection(); | ||
configSection.setWithinAMap(withinAMap); | ||
configSection.setConfigPhase(configPhase); | ||
configSection.setSectionDetails(sectionHolder.details); | ||
configSection.setSectionDetailsTitle(sectionHolder.title); | ||
configSection.setName(parentName + Constants.DOT + hyphenatedFieldName); | ||
} | ||
} | ||
|
||
if (name.isEmpty()) { | ||
name = parentName + Constants.DOT + hyphenatedFieldName; | ||
} | ||
|
||
if (Constants.NO_DEFAULT.equals(defaultValue)) { | ||
defaultValue = Constants.EMPTY; | ||
} | ||
if (Constants.EMPTY.equals(defaultValue)) { | ||
defaultValue = defaultValueDoc; | ||
} | ||
|
||
if (isConfigGroup) { | ||
List<ConfigDocItem> groupConfigItems = recordConfigItemsFromConfigGroup(configPhase, name, configGroup, | ||
configSection, withinAMap, sectionLevel); | ||
configDocItems.addAll(groupConfigItems); | ||
} else { | ||
final ConfigDocKey configDocKey = new ConfigDocKey(); | ||
configDocKey.setWithinAMap(withinAMap); | ||
boolean optional = false; | ||
boolean list = false; | ||
if (!typeMirror.getKind().isPrimitive()) { | ||
DeclaredType declaredType = (DeclaredType) typeMirror; | ||
TypeElement typeElement = (TypeElement) declaredType.asElement(); | ||
Name qualifiedName = typeElement.getQualifiedName(); | ||
optional = qualifiedName.toString().startsWith("java.util.Optional"); | ||
list = qualifiedName.contentEquals("java.util.List"); | ||
|
||
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments(); | ||
if (!typeArguments.isEmpty()) { | ||
// FIXME: this is super dodgy: we should check the type!! | ||
if (typeArguments.size() == 2) { | ||
final String mapKey = String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); | ||
type = typeArguments.get(1).toString(); | ||
configGroup = configGroups.get(type); | ||
name += mapKey; | ||
|
||
if (configGroup != null) { | ||
name += String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); | ||
List<ConfigDocItem> groupConfigItems = recordConfigItemsFromConfigGroup(configPhase, name, | ||
configGroup, configSection, true, sectionLevel); | ||
configDocItems.addAll(groupConfigItems); | ||
continue; | ||
} else { | ||
configDocKey.setWithinAMap(true); | ||
} | ||
} else { | ||
// FIXME: this is for Optional<T> and List<T> | ||
TypeMirror realTypeMirror = typeArguments.get(0); | ||
type = simpleTypeToString(realTypeMirror); | ||
|
||
if (isEnumType(realTypeMirror)) { | ||
acceptedValues = extractEnumValues(realTypeMirror); | ||
} | ||
} | ||
} else { | ||
type = simpleTypeToString(declaredType); | ||
if (isEnumType(declaredType)) { | ||
acceptedValues = extractEnumValues(declaredType); | ||
} | ||
} | ||
} | ||
|
||
final String configDescription = javaDocParser.parseConfigDescription(rawJavaDoc); | ||
|
||
configDocKey.setKey(name); | ||
configDocKey.setType(type); | ||
configDocKey.setConfigPhase(configPhase); | ||
configDocKey.setDefaultValue(defaultValue); | ||
configDocKey.setOptional(optional); | ||
configDocKey.setList(list); | ||
configDocKey.setConfigDoc(configDescription); | ||
configDocKey.setAcceptedValues(acceptedValues); | ||
configDocKey.setJavaDocSiteLink(getJavaDocSiteLink(type)); | ||
ConfigDocItem configDocItem = new ConfigDocItem(); | ||
configDocItem.setConfigDocKey(configDocKey); | ||
configDocItems.add(configDocItem); | ||
} | ||
} | ||
|
||
return configDocItems; | ||
} | ||
|
||
private List<ConfigDocItem> recordConfigItemsFromConfigGroup(ConfigPhase configPhase, String name, Element configGroup, | ||
ConfigDocSection configSection, boolean withinAMap, int sectionLevel) { | ||
final List<ConfigDocItem> groupConfigItems; | ||
final List<ConfigDocItem> configDocItems = new ArrayList<>(); | ||
|
||
if (configSection != null) { | ||
final ConfigDocItem configDocItem = new ConfigDocItem(); | ||
configDocItem.setConfigDocSection(configSection); | ||
configDocItems.add(configDocItem); | ||
groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap, | ||
sectionLevel + 1); | ||
configSection.addConfigDocItems(groupConfigItems); | ||
} else { | ||
groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap, sectionLevel); | ||
configDocItems.addAll(groupConfigItems); | ||
} | ||
|
||
String configGroupName = configGroup.asType().toString(); | ||
List<ConfigDocItem> previousConfigGroupConfigItems = holder.getConfigGroupConfigItems().get(configGroupName); | ||
if (previousConfigGroupConfigItems == null) { | ||
holder.addConfigGroupItems(configGroupName, groupConfigItems); | ||
} else { | ||
previousConfigGroupConfigItems.addAll(configDocItems); | ||
} | ||
|
||
return configDocItems; | ||
} | ||
|
||
private String simpleTypeToString(TypeMirror typeMirror) { | ||
if (typeMirror.getKind().isPrimitive()) { | ||
return typeMirror.toString(); | ||
} | ||
|
||
final String knownGenericType = getKnownGenericType((DeclaredType) typeMirror); | ||
return knownGenericType != null ? knownGenericType : typeMirror.toString(); | ||
} | ||
|
||
private List<String> extractEnumValues(TypeMirror realTypeMirror) { | ||
Element declaredTypeElement = ((DeclaredType) realTypeMirror).asElement(); | ||
List<String> acceptedValues = new ArrayList<>(); | ||
|
||
for (Element field : declaredTypeElement.getEnclosedElements()) { | ||
if (field.getKind() == ElementKind.ENUM_CONSTANT) { | ||
acceptedValues.add(DocGeneratorUtil.hyphenateEnumValue(field.getSimpleName().toString())); | ||
} | ||
} | ||
|
||
return acceptedValues; | ||
} | ||
|
||
private boolean isEnumType(TypeMirror realTypeMirror) { | ||
return realTypeMirror instanceof DeclaredType | ||
&& ((DeclaredType) realTypeMirror).asElement().getKind() == ElementKind.ENUM; | ||
} | ||
} |
Oops, something went wrong.