Skip to content

Commit

Permalink
docs: generate a specific documentation file for each config group
Browse files Browse the repository at this point in the history
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
machi1990 committed Nov 1, 2019
1 parent 2425ec2 commit 0849187
Show file tree
Hide file tree
Showing 11 changed files with 496 additions and 332 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
Expand Down Expand Up @@ -66,9 +65,9 @@
import org.jboss.jdeparser.JType;
import org.jboss.jdeparser.JTypes;

import io.quarkus.annotation.processor.generate_doc.ConfigDocItem;
import io.quarkus.annotation.processor.generate_doc.ConfigDocItemScanner;
import io.quarkus.annotation.processor.generate_doc.ConfigDocWriter;
import io.quarkus.annotation.processor.generate_doc.ScannedConfigDocsItemHolder;

public class ExtensionAnnotationProcessor extends AbstractProcessor {

Expand Down Expand Up @@ -236,9 +235,12 @@ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)
}

try {
final Map<String, List<ConfigDocItem>> extensionConfigurationItems = configDocItemScanner
final ScannedConfigDocsItemHolder scannedConfigDocsItemHolder = configDocItemScanner
.scanExtensionsConfigurationItems(javaDocProperties);
configDocWriter.writeExtensionConfigDocumentation(extensionConfigurationItems);

configDocWriter.writeExtensionConfigDocumentation(scannedConfigDocsItemHolder.getAllConfigItemsPerExtension(),
true); // generate extension doc with search engine activate
configDocWriter.writeExtensionConfigDocumentation(scannedConfigDocsItemHolder.getConfigGroupConfigItems(), false); // generate config group docs with search engine deactivated
} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to generate extension doc: " + e);
return;
Expand Down
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;
}
}
Loading

0 comments on commit 0849187

Please sign in to comment.