Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various improvements for config doc generation #42455

Merged
merged 10 commits into from
Aug 11, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@
public final class DiscoveryConfigRoot extends DiscoveryRootElement {

private final String prefix;
private final String overriddenDocPrefix;
private final ConfigPhase phase;
private final String overriddenDocFileName;

public DiscoveryConfigRoot(Extension extension, String prefix, String binaryName, String qualifiedName,
public DiscoveryConfigRoot(Extension extension, String prefix, String overriddenDocPrefix,
String binaryName, String qualifiedName,
ConfigPhase configPhase, String overriddenDocFileName, boolean configMapping) {
super(extension, binaryName, qualifiedName, configMapping);

this.prefix = prefix;
this.overriddenDocPrefix = overriddenDocPrefix;
this.phase = configPhase;
this.overriddenDocFileName = overriddenDocFileName;
}
Expand All @@ -27,6 +30,10 @@ public String getPrefix() {
return prefix;
}

public String getOverriddenDocPrefix() {
return overriddenDocPrefix;
}

public ConfigPhase getPhase() {
return phase;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.quarkus.annotation.processor.documentation.config.merger;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.quarkus.annotation.processor.Outputs;
import io.quarkus.annotation.processor.documentation.config.model.JavadocElements;
import io.quarkus.annotation.processor.documentation.config.model.JavadocElements.JavadocElement;
import io.quarkus.annotation.processor.documentation.config.util.JacksonMappers;

public final class JavadocMerger {

private JavadocMerger() {
}

public static JavadocRepository mergeJavadocElements(List<Path> buildOutputDirectories) {
Map<String, JavadocElement> javadocElementsMap = new HashMap<>();

for (Path buildOutputDirectory : buildOutputDirectories) {
Path javadocPath = buildOutputDirectory.resolve(Outputs.QUARKUS_CONFIG_DOC_JAVADOC);
if (!Files.isReadable(javadocPath)) {
continue;
}

try {
JavadocElements javadocElements = JacksonMappers.yamlObjectReader().readValue(javadocPath.toFile(),
JavadocElements.class);

if (javadocElements.elements() == null || javadocElements.elements().isEmpty()) {
continue;
}

javadocElementsMap.putAll(javadocElements.elements());
} catch (IOException e) {
throw new IllegalStateException("Unable to parse: " + javadocPath, e);
}
}

return new JavadocRepository(javadocElementsMap);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package io.quarkus.maven.config.doc;
package io.quarkus.annotation.processor.documentation.config.merger;

import java.util.Map;
import java.util.Optional;

import io.quarkus.annotation.processor.documentation.config.model.JavadocElements.JavadocElement;
import io.quarkus.annotation.processor.documentation.config.util.Markers;

final class JavadocRepository {
public final class JavadocRepository {

private final Map<String, JavadocElement> javadocElementsMap;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.maven.config.doc;
package io.quarkus.annotation.processor.documentation.config.merger;

import java.util.Collections;
import java.util.List;
Expand All @@ -8,6 +8,9 @@
import io.quarkus.annotation.processor.documentation.config.model.ConfigSection;
import io.quarkus.annotation.processor.documentation.config.model.Extension;

/**
* The merged model we obtain after merging all the ResolvedModels from the current project.
*/
public class MergedModel {

private final Map<Extension, Map<String, ConfigRoot>> configRoots;
Expand All @@ -16,7 +19,7 @@ public class MergedModel {

private final Map<Extension, List<ConfigSection>> generatedConfigSections;

public MergedModel(Map<Extension, Map<String, ConfigRoot>> configRoots,
MergedModel(Map<Extension, Map<String, ConfigRoot>> configRoots,
Map<String, ConfigRoot> configRootsInSpecificFile,
Map<Extension, List<ConfigSection>> configSections) {
this.configRoots = Collections.unmodifiableMap(configRoots);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package io.quarkus.annotation.processor.documentation.config.merger;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.stream.Collectors;

import io.quarkus.annotation.processor.Outputs;
import io.quarkus.annotation.processor.documentation.config.model.AbstractConfigItem;
import io.quarkus.annotation.processor.documentation.config.model.ConfigItemCollection;
import io.quarkus.annotation.processor.documentation.config.model.ConfigRoot;
import io.quarkus.annotation.processor.documentation.config.model.ConfigSection;
import io.quarkus.annotation.processor.documentation.config.model.Extension;
import io.quarkus.annotation.processor.documentation.config.model.Extension.NameSource;
import io.quarkus.annotation.processor.documentation.config.model.ResolvedModel;
import io.quarkus.annotation.processor.documentation.config.util.JacksonMappers;

public final class ModelMerger {

private ModelMerger() {
}

/**
* Merge all the resolved models obtained from a list of build output directories (e.g. in the case of Maven, the list of
* target/ directories found in the parent directory scanned).
*/
public static MergedModel mergeModel(List<Path> buildOutputDirectories) {
// keyed on extension and then top level prefix
Map<Extension, Map<String, ConfigRoot>> configRoots = new HashMap<>();
// keyed on file name
Map<String, ConfigRoot> configRootsInSpecificFile = new TreeMap<>();
// keyed on extension
Map<Extension, List<ConfigSection>> generatedConfigSections = new TreeMap<>();

for (Path buildOutputDirectory : buildOutputDirectories) {
Path resolvedModelPath = buildOutputDirectory.resolve(Outputs.QUARKUS_CONFIG_DOC_MODEL);
if (!Files.isReadable(resolvedModelPath)) {
continue;
}

try {
ResolvedModel resolvedModel = JacksonMappers.yamlObjectReader().readValue(resolvedModelPath.toFile(),
ResolvedModel.class);

if (resolvedModel.getConfigRoots() == null || resolvedModel.getConfigRoots().isEmpty()) {
continue;
}

for (ConfigRoot configRoot : resolvedModel.getConfigRoots()) {
if (configRoot.getOverriddenDocFileName() != null) {
ConfigRoot existingConfigRootInSpecificFile = configRootsInSpecificFile
.get(configRoot.getOverriddenDocFileName());

if (existingConfigRootInSpecificFile == null) {
configRootsInSpecificFile.put(configRoot.getOverriddenDocFileName(), configRoot);
} else {
if (!existingConfigRootInSpecificFile.getExtension().equals(configRoot.getExtension())
|| !existingConfigRootInSpecificFile.getPrefix().equals(configRoot.getPrefix())) {
throw new IllegalStateException(
"Two config roots with different extensions or prefixes cannot be merged in the same specific config file: "
+ configRoot.getOverriddenDocFileName());
}

existingConfigRootInSpecificFile.merge(configRoot);
}

continue;
}

Map<String, ConfigRoot> extensionConfigRoots = configRoots.computeIfAbsent(configRoot.getExtension(),
e -> new HashMap<>());

ConfigRoot existingConfigRoot = extensionConfigRoots.get(configRoot.getTopLevelPrefix());

if (existingConfigRoot == null) {
extensionConfigRoots.put(configRoot.getTopLevelPrefix(), configRoot);
} else {
existingConfigRoot.merge(configRoot);
}
}
} catch (IOException e) {
throw new IllegalStateException("Unable to parse: " + resolvedModelPath, e);
}
}

configRoots = retainBestExtensionKey(configRoots);

for (Entry<Extension, Map<String, ConfigRoot>> extensionConfigRootsEntry : configRoots.entrySet()) {
List<ConfigSection> extensionGeneratedConfigSections = generatedConfigSections
.computeIfAbsent(extensionConfigRootsEntry.getKey(), e -> new ArrayList<>());

for (ConfigRoot configRoot : extensionConfigRootsEntry.getValue().values()) {
collectGeneratedConfigSections(extensionGeneratedConfigSections, configRoot);
}
}

return new MergedModel(configRoots, configRootsInSpecificFile, generatedConfigSections);
}

private static Map<Extension, Map<String, ConfigRoot>> retainBestExtensionKey(
Map<Extension, Map<String, ConfigRoot>> configRoots) {
return configRoots.entrySet().stream().collect(Collectors.toMap(e -> {
Extension extension = e.getKey();

for (ConfigRoot configRoot : e.getValue().values()) {
if (configRoot.getExtension().nameSource().isBetterThan(extension.nameSource())) {
extension = configRoot.getExtension();
}
if (NameSource.EXTENSION_METADATA.equals(extension.nameSource())) {
// we won't find any better
break;
}
}

return extension;
}, e -> e.getValue(), (k1, k2) -> k1, TreeMap::new));
}

private static void collectGeneratedConfigSections(List<ConfigSection> extensionGeneratedConfigSections,
ConfigItemCollection configItemCollection) {
for (AbstractConfigItem configItem : configItemCollection.getItems()) {
if (!configItem.isSection()) {
continue;
}

ConfigSection configSection = (ConfigSection) configItem;
if (configSection.isGenerated()) {
extensionGeneratedConfigSections.add(configSection);
}

collectGeneratedConfigSections(extensionGeneratedConfigSections, configSection);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import io.quarkus.annotation.processor.documentation.config.util.Markers;

/**
* At this stage, a config root is actually a prefix: we merged all the config roots with the same prefix.
Expand All @@ -17,14 +20,19 @@ public class ConfigRoot implements ConfigItemCollection {

private final Extension extension;
private final String prefix;
// used by the doc generation to classify config roots
private final String topLevelPrefix;

private String overriddenDocFileName;
private final String overriddenDocFileName;
private final List<AbstractConfigItem> items = new ArrayList<>();
private final Set<String> qualifiedNames = new HashSet<>();

public ConfigRoot(Extension extension, String prefix) {
public ConfigRoot(Extension extension, String prefix, String overriddenDocPrefix, String overriddenDocFileName) {
this.extension = extension;
this.prefix = prefix;
this.overriddenDocFileName = overriddenDocFileName;
this.topLevelPrefix = overriddenDocPrefix != null ? buildTopLevelPrefix(overriddenDocPrefix)
: buildTopLevelPrefix(prefix);
}

public Extension getExtension() {
Expand All @@ -39,13 +47,6 @@ public String getOverriddenDocFileName() {
return overriddenDocFileName;
}

public void setOverriddenDocFileName(String overriddenDocFileName) {
if (this.overriddenDocFileName != null) {
return;
}
this.overriddenDocFileName = overriddenDocFileName;
}

public void addQualifiedName(String qualifiedName) {
qualifiedNames.add(qualifiedName);
}
Expand All @@ -64,6 +65,10 @@ public List<AbstractConfigItem> getItems() {
return Collections.unmodifiableList(items);
}

public String getTopLevelPrefix() {
return topLevelPrefix;
}

public void merge(ConfigRoot other) {
this.qualifiedNames.addAll(other.getQualifiedNames());

Expand Down Expand Up @@ -116,4 +121,14 @@ public boolean hasMemorySizeType() {
}
return false;
}

private static String buildTopLevelPrefix(String prefix) {
String[] prefixSegments = prefix.split(Pattern.quote(Markers.DOT));

if (prefixSegments.length == 1) {
return prefixSegments[0];
}

return prefixSegments[0] + Markers.DOT + prefixSegments[1];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ public final class ConfigSection extends AbstractConfigItem implements ConfigIte

private boolean generated;
private final List<AbstractConfigItem> items = new ArrayList<>();
private final int level;

public ConfigSection(String sourceClass, String sourceName, String path, String type, boolean generated,
boolean deprecated) {
public ConfigSection(String sourceClass, String sourceName, String path, String type, int level,
boolean generated, boolean deprecated) {
super(sourceClass, sourceName, path, type, deprecated);
this.generated = generated;
this.level = level;
}

@Override
Expand Down Expand Up @@ -43,6 +45,10 @@ public boolean isGenerated() {
return generated;
}

public int getLevel() {
return level;
}

/**
* This is used when we merge ConfigSection at the ConfigRoot level.
* It can happen when for instance a path is both used at a given level and in an unnamed map.
Expand Down
Loading
Loading