deprecated = javadoc.getBlockTags().stream()
- .filter(t -> t.getType() == Type.DEPRECATED)
- .map(JavadocBlockTag::getContent)
- .map(JavadocDescription::toText)
- .findFirst();
-
- String asciidoc;
- if (isAsciidoc(javadoc)) {
- asciidoc = handleEolInAsciidoc(javadoc);
- } else {
- asciidoc = htmlJavadocToAsciidoc(javadoc.getDescription());
- }
-
- if (asciidoc == null || asciidoc.isBlank()) {
- return ParsedJavadocSection.empty();
- }
-
- final int newLineIndex = asciidoc.indexOf(NEW_LINE);
- final int dotIndex = asciidoc.indexOf(DOT);
-
- final int endOfTitleIndex;
- if (newLineIndex > 0 && newLineIndex < dotIndex) {
- endOfTitleIndex = newLineIndex;
- } else {
- endOfTitleIndex = dotIndex;
- }
-
- if (endOfTitleIndex == -1) {
- final String title = asciidoc.replaceAll("^([^\\w])+", "").trim();
-
- return new ParsedJavadocSection(title, null, deprecated.orElse(null));
- } else {
- final String title = asciidoc.substring(0, endOfTitleIndex).replaceAll("^([^\\w])+", "").trim();
- final String details = asciidoc.substring(endOfTitleIndex + 1).trim();
-
- return new ParsedJavadocSection(title, details.isBlank() ? null : details, deprecated.orElse(null));
- }
- }
-
- private String handleEolInAsciidoc(Javadoc javadoc) {
- // it's Asciidoc, so we just pass through
- // it also uses platform specific EOL, so we need to convert them back to \n
- String asciidoc = javadoc.getDescription().toText();
- asciidoc = REPLACE_WINDOWS_EOL.matcher(asciidoc).replaceAll("\n");
- asciidoc = REPLACE_MACOS_EOL.matcher(asciidoc).replaceAll("\n");
- return asciidoc;
- }
-
- private boolean isAsciidoc(Javadoc javadoc) {
- for (JavadocBlockTag blockTag : javadoc.getBlockTags()) {
- if ("asciidoclet".equals(blockTag.getTagName())) {
- return true;
- }
- }
- return false;
- }
-
- private String htmlJavadocToAsciidoc(JavadocDescription javadocDescription) {
StringBuilder sb = new StringBuilder();
- for (JavadocDescriptionElement javadocDescriptionElement : javadocDescription.getElements()) {
+ for (JavadocDescriptionElement javadocDescriptionElement : parsedJavadoc.getDescription().getElements()) {
if (javadocDescriptionElement instanceof JavadocInlineTag) {
JavadocInlineTag inlineTag = (JavadocInlineTag) javadocDescriptionElement;
String content = inlineTag.getContent().trim();
@@ -204,7 +96,7 @@ private String htmlJavadocToAsciidoc(JavadocDescription javadocDescription) {
case LITERAL:
case SYSTEM_PROPERTY:
sb.append('`');
- appendEscapedAsciiDoc(sb, content);
+ appendEscapedAsciiDoc(sb, content, inlineMacroMode);
sb.append('`');
break;
case LINK:
@@ -213,7 +105,7 @@ private String htmlJavadocToAsciidoc(JavadocDescription javadocDescription) {
content = ConfigNamingUtil.hyphenate(content.substring(1));
}
sb.append('`');
- appendEscapedAsciiDoc(sb, content);
+ appendEscapedAsciiDoc(sb, content, inlineMacroMode);
sb.append('`');
break;
default:
@@ -221,20 +113,22 @@ private String htmlJavadocToAsciidoc(JavadocDescription javadocDescription) {
break;
}
} else {
- appendHtml(sb, Jsoup.parseBodyFragment(javadocDescriptionElement.toText()));
+ appendHtml(sb, Jsoup.parseBodyFragment(javadocDescriptionElement.toText()), inlineMacroMode);
}
}
- return trim(sb);
+ String asciidoc = trim(sb);
+
+ return asciidoc.isBlank() ? null : asciidoc;
}
- private void appendHtml(StringBuilder sb, Node node) {
+ private static void appendHtml(StringBuilder sb, Node node, boolean inlineMacroMode) {
for (Node childNode : node.childNodes()) {
switch (childNode.nodeName()) {
case PARAGRAPH_NODE:
newLine(sb);
newLine(sb);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
break;
case PREFORMATED_NODE:
newLine(sb);
@@ -254,7 +148,7 @@ private void appendHtml(StringBuilder sb, Node node) {
newLine(sb);
sb.append(BLOCKQUOTE_BLOCK_ASCIDOC_STYLE);
newLine(sb);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
newLineIfNeeded(sb);
sb.append(BLOCKQUOTE_BLOCK_ASCIDOC_STYLE_END);
newLine(sb);
@@ -263,7 +157,7 @@ private void appendHtml(StringBuilder sb, Node node) {
case ORDERED_LIST_NODE:
case UN_ORDERED_LIST_NODE:
newLine(sb);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
break;
case LIST_ITEM_NODE:
final String marker = childNode.parent().nodeName().equals(ORDERED_LIST_NODE)
@@ -271,59 +165,59 @@ private void appendHtml(StringBuilder sb, Node node) {
: UNORDERED_LIST_ITEM_ASCIDOC_STYLE;
newLine(sb);
sb.append(marker);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
break;
case LINK_NODE:
final String link = childNode.attr(HREF_ATTRIBUTE);
sb.append("link:");
sb.append(link);
final StringBuilder caption = new StringBuilder();
- appendHtml(caption, childNode);
+ appendHtml(caption, childNode, inlineMacroMode);
sb.append(String.format(LINK_ATTRIBUTE_FORMAT, trim(caption)));
break;
case CODE_NODE:
sb.append(BACKTICK);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
sb.append(BACKTICK);
break;
case BOLD_NODE:
case STRONG_NODE:
sb.append(STAR);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
sb.append(STAR);
break;
case EMPHASIS_NODE:
case ITALICS_NODE:
sb.append(UNDERSCORE);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
sb.append(UNDERSCORE);
break;
case UNDERLINE_NODE:
sb.append(UNDERLINE_ASCIDOC_STYLE);
sb.append(HASH);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
sb.append(HASH);
break;
case SMALL_NODE:
sb.append(SMALL_ASCIDOC_STYLE);
sb.append(HASH);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
sb.append(HASH);
break;
case BIG_NODE:
sb.append(BIG_ASCIDOC_STYLE);
sb.append(HASH);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
sb.append(HASH);
break;
case SUB_SCRIPT_NODE:
sb.append(SUB_SCRIPT_ASCIDOC_STYLE);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
sb.append(SUB_SCRIPT_ASCIDOC_STYLE);
break;
case SUPER_SCRIPT_NODE:
sb.append(SUPER_SCRIPT_ASCIDOC_STYLE);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
sb.append(SUPER_SCRIPT_ASCIDOC_STYLE);
break;
case DEL_NODE:
@@ -331,7 +225,7 @@ private void appendHtml(StringBuilder sb, Node node) {
case STRIKE_NODE:
sb.append(LINE_THROUGH_ASCIDOC_STYLE);
sb.append(HASH);
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
sb.append(HASH);
break;
case NEW_LINE_NODE:
@@ -352,10 +246,10 @@ private void appendHtml(StringBuilder sb, Node node) {
text = startingSpaceMatcher.replaceFirst("");
}
- appendEscapedAsciiDoc(sb, text);
+ appendEscapedAsciiDoc(sb, text, inlineMacroMode);
break;
default:
- appendHtml(sb, childNode);
+ appendHtml(sb, childNode, inlineMacroMode);
break;
}
}
@@ -431,7 +325,7 @@ private static StringBuilder trimText(StringBuilder sb, String charsToTrim) {
return sb;
}
- private StringBuilder unescapeHtmlEntities(StringBuilder sb, String text) {
+ private static StringBuilder unescapeHtmlEntities(StringBuilder sb, String text) {
int i = 0;
/* trim leading whitespace */
LOOP: while (i < text.length()) {
@@ -497,7 +391,7 @@ private StringBuilder unescapeHtmlEntities(StringBuilder sb, String text) {
return sb;
}
- private StringBuilder appendEscapedAsciiDoc(StringBuilder sb, String text) {
+ private static StringBuilder appendEscapedAsciiDoc(StringBuilder sb, String text, boolean inlineMacroMode) {
boolean escaping = false;
for (int i = 0; i < text.length(); i++) {
final char ch = text.charAt(i);
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToMarkdownTransformer.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToMarkdownTransformer.java
new file mode 100644
index 0000000000000..fd5796ed7b8b0
--- /dev/null
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToMarkdownTransformer.java
@@ -0,0 +1,87 @@
+package io.quarkus.annotation.processor.documentation.config.formatter;
+
+import java.util.regex.Pattern;
+
+import com.github.javaparser.StaticJavaParser;
+import com.github.javaparser.javadoc.Javadoc;
+import com.github.javaparser.javadoc.description.JavadocDescription;
+import com.github.javaparser.javadoc.description.JavadocDescriptionElement;
+import com.github.javaparser.javadoc.description.JavadocInlineTag;
+
+import io.quarkus.annotation.processor.documentation.config.model.JavadocFormat;
+
+public class JavadocToMarkdownTransformer {
+
+ private static final Pattern START_OF_LINE = Pattern.compile("^", Pattern.MULTILINE);
+
+ public static String toMarkdown(String javadoc, JavadocFormat format) {
+ if (javadoc == null || javadoc.isBlank()) {
+ return null;
+ }
+
+ if (format == JavadocFormat.MARKDOWN) {
+ return javadoc;
+ } else if (format == JavadocFormat.JAVADOC) {
+ // the parser expects all the lines to start with "* "
+ // we add it as it has been previously removed
+ Javadoc parsedJavadoc = StaticJavaParser.parseJavadoc(START_OF_LINE.matcher(javadoc).replaceAll("* "));
+
+ // HTML is valid Javadoc but we need to drop the Javadoc tags e.g. {@link ...}
+ return simplifyJavadoc(parsedJavadoc.getDescription());
+ }
+
+ // it's Asciidoc, the fun begins...
+ return "";
+ }
+
+ /**
+ * This is not definitely not perfect but it can be used to filter the Javadoc included in Markdown.
+ *
+ * We will need to discuss further how to handle passing the Javadoc to the IDE.
+ * In Quarkus, we have Asciidoc, standard Javadoc and soon we might have Markdown javadoc.
+ */
+ private static String simplifyJavadoc(JavadocDescription javadocDescription) {
+ StringBuilder sb = new StringBuilder();
+
+ for (JavadocDescriptionElement javadocDescriptionElement : javadocDescription.getElements()) {
+ if (javadocDescriptionElement instanceof JavadocInlineTag) {
+ JavadocInlineTag inlineTag = (JavadocInlineTag) javadocDescriptionElement;
+ String content = inlineTag.getContent().trim();
+ switch (inlineTag.getType()) {
+ case CODE:
+ case VALUE:
+ case LITERAL:
+ case SYSTEM_PROPERTY:
+ case LINK:
+ case LINKPLAIN:
+ sb.append("");
+ sb.append(escapeHtml(content));
+ sb.append("
");
+ break;
+ default:
+ sb.append(content);
+ break;
+ }
+ } else {
+ sb.append(javadocDescriptionElement.toText());
+ }
+ }
+
+ return sb.toString().trim();
+ }
+
+ private static String escapeHtml(String s) {
+ StringBuilder out = new StringBuilder(Math.max(16, s.length()));
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c > 127 || c == '"' || c == '\'' || c == '<' || c == '>' || c == '&') {
+ out.append("");
+ out.append((int) c);
+ out.append(';');
+ } else {
+ out.append(c);
+ }
+ }
+ return out.toString();
+ }
+}
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocTransformer.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocTransformer.java
new file mode 100644
index 0000000000000..59da647afdfa3
--- /dev/null
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocTransformer.java
@@ -0,0 +1,20 @@
+package io.quarkus.annotation.processor.documentation.config.formatter;
+
+import io.quarkus.annotation.processor.documentation.config.model.JavadocFormat;
+
+public final class JavadocTransformer {
+
+ private JavadocTransformer() {
+ }
+
+ public static String transform(String javadoc, JavadocFormat fromFormat, JavadocFormat toFormat) {
+ switch (toFormat) {
+ case ASCIIDOC:
+ return JavadocToAsciidocTransformer.toAsciidoc(javadoc, fromFormat);
+ case MARKDOWN:
+ return JavadocToMarkdownTransformer.toMarkdown(javadoc, fromFormat);
+ default:
+ throw new IllegalArgumentException("Converting to " + toFormat + " is not supported");
+ }
+ }
+}
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigItemCollection.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigItemCollection.java
index 01527fbbc4125..77cd8dd3f1e53 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigItemCollection.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigItemCollection.java
@@ -17,6 +17,20 @@ default List getNonDeprecatedItems() {
.toList();
}
+ @JsonIgnore
+ default List getNonDeprecatedProperties() {
+ return getItems().stream()
+ .filter(i -> i instanceof ConfigProperty && !i.isDeprecated())
+ .toList();
+ }
+
+ @JsonIgnore
+ default List getNonDeprecatedSections() {
+ return getItems().stream()
+ .filter(i -> i instanceof ConfigSection && !i.isDeprecated())
+ .toList();
+ }
+
void addItem(AbstractConfigItem item);
boolean hasDurationType();
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocElements.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocElements.java
index 9ef41190a3268..8a3db353d4c9f 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocElements.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocElements.java
@@ -2,11 +2,9 @@
import java.util.Map;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-
public record JavadocElements(Extension extension, Map elements) {
- public record JavadocElement(String description, String since, String deprecated, @JsonIgnore String rawJavadoc) {
+ public record JavadocElement(String description, JavadocFormat format, String since, String deprecated) {
}
public boolean isEmpty() {
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocFormat.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocFormat.java
new file mode 100644
index 0000000000000..b44020aa4ebb1
--- /dev/null
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocFormat.java
@@ -0,0 +1,8 @@
+package io.quarkus.annotation.processor.documentation.config.model;
+
+public enum JavadocFormat {
+
+ ASCIIDOC,
+ MARKDOWN,
+ JAVADOC;
+}
\ No newline at end of file
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractJavadocConfigListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractJavadocConfigListener.java
index 490866047cf7d..a2fe0e875223a 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractJavadocConfigListener.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractJavadocConfigListener.java
@@ -9,8 +9,8 @@
import io.quarkus.annotation.processor.documentation.config.discovery.DiscoveryConfigRoot;
import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadoc;
import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadocSection;
-import io.quarkus.annotation.processor.documentation.config.formatter.JavadocToAsciidocTransformer;
import io.quarkus.annotation.processor.documentation.config.model.JavadocElements.JavadocElement;
+import io.quarkus.annotation.processor.documentation.config.util.JavadocUtil;
import io.quarkus.annotation.processor.documentation.config.util.Markers;
import io.quarkus.annotation.processor.util.Config;
import io.quarkus.annotation.processor.util.Utils;
@@ -41,13 +41,15 @@ public Optional onConfigRoot(TypeElement configRoot) {
return Optional.empty();
}
- ParsedJavadocSection parsedJavadocSection = JavadocToAsciidocTransformer.INSTANCE
- .parseConfigSectionJavadoc(rawJavadoc.get());
+ ParsedJavadocSection parsedJavadocSection = JavadocUtil.parseConfigSectionJavadoc(rawJavadoc.get());
+ if (parsedJavadocSection.title() == null) {
+ return Optional.empty();
+ }
configCollector.addJavadocElement(
configRoot.getQualifiedName().toString(),
- new JavadocElement(parsedJavadocSection.title(), null, parsedJavadocSection.deprecated(),
- rawJavadoc.get()));
+ new JavadocElement(parsedJavadocSection.title(), parsedJavadocSection.format(), null,
+ parsedJavadocSection.deprecated()));
return Optional.empty();
}
@@ -69,13 +71,17 @@ public void onResolvedEnum(TypeElement enumTypeElement) {
continue;
}
- ParsedJavadoc parsedJavadoc = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(rawJavadoc.get());
+ ParsedJavadoc parsedJavadoc = JavadocUtil.parseConfigItemJavadoc(rawJavadoc.get());
+
+ if (parsedJavadoc.description() == null) {
+ continue;
+ }
configCollector.addJavadocElement(
enumTypeElement.getQualifiedName().toString() + Markers.DOT + enumElement.getSimpleName()
.toString(),
- new JavadocElement(parsedJavadoc.description(), parsedJavadoc.since(), parsedJavadoc.deprecated(),
- rawJavadoc.get()));
+ new JavadocElement(parsedJavadoc.description(), parsedJavadoc.format(), parsedJavadoc.since(),
+ parsedJavadoc.deprecated()));
}
}
}
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocConfigMappingListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocConfigMappingListener.java
index 56ec7c8dc11f4..3a978fd690ea9 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocConfigMappingListener.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocConfigMappingListener.java
@@ -1,7 +1,5 @@
package io.quarkus.annotation.processor.documentation.config.scanner;
-import java.util.Optional;
-
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
@@ -9,8 +7,8 @@
import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadoc;
import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadocSection;
import io.quarkus.annotation.processor.documentation.config.discovery.ResolvedType;
-import io.quarkus.annotation.processor.documentation.config.formatter.JavadocToAsciidocTransformer;
import io.quarkus.annotation.processor.documentation.config.model.JavadocElements.JavadocElement;
+import io.quarkus.annotation.processor.documentation.config.util.JavadocUtil;
import io.quarkus.annotation.processor.documentation.config.util.Markers;
import io.quarkus.annotation.processor.documentation.config.util.Types;
import io.quarkus.annotation.processor.util.Config;
@@ -38,33 +36,36 @@ public void onEnclosedMethod(DiscoveryRootElement discoveryRootElement, TypeElem
return;
}
- Optional rawJavadoc = utils.element().getJavadoc(method);
+ String rawJavadoc = utils.element().getJavadoc(method).orElse("");
boolean isSection = utils.element().isAnnotationPresent(method, Types.ANNOTATION_CONFIG_DOC_SECTION);
- if (rawJavadoc.isEmpty()) {
- // We require a Javadoc for config items that are not config groups except if they are a section
- if (!resolvedType.isConfigGroup() || isSection) {
- utils.element().addMissingJavadocError(method);
- }
- return;
- }
-
if (isSection) {
// for sections, we only keep the title
- ParsedJavadocSection parsedJavadocSection = JavadocToAsciidocTransformer.INSTANCE
- .parseConfigSectionJavadoc(rawJavadoc.get());
+ ParsedJavadocSection parsedJavadocSection = JavadocUtil.parseConfigSectionJavadoc(rawJavadoc);
+
+ if (parsedJavadocSection.title() == null) {
+ return;
+ }
configCollector.addJavadocElement(
clazz.getQualifiedName().toString() + Markers.DOT + method.getSimpleName().toString(),
- new JavadocElement(parsedJavadocSection.title(), null, parsedJavadocSection.deprecated(),
- rawJavadoc.get()));
+ new JavadocElement(parsedJavadocSection.title(), parsedJavadocSection.format(), null,
+ parsedJavadocSection.deprecated()));
} else {
- ParsedJavadoc parsedJavadoc = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(rawJavadoc.get());
+ ParsedJavadoc parsedJavadoc = JavadocUtil.parseConfigItemJavadoc(rawJavadoc);
+
+ // We require a Javadoc for config items that are not config groups except if they are a section
+ if (parsedJavadoc.description() == null) {
+ if (parsedJavadoc.deprecated() == null && !resolvedType.isConfigGroup()) {
+ utils.element().addMissingJavadocError(method);
+ }
+ return;
+ }
configCollector.addJavadocElement(
clazz.getQualifiedName().toString() + Markers.DOT + method.getSimpleName().toString(),
- new JavadocElement(parsedJavadoc.description(), parsedJavadoc.since(), parsedJavadoc.deprecated(),
- rawJavadoc.get()));
+ new JavadocElement(parsedJavadoc.description(), parsedJavadoc.format(), parsedJavadoc.since(),
+ parsedJavadoc.deprecated()));
}
}
}
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocLegacyConfigRootListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocLegacyConfigRootListener.java
index bfbc991d0ff28..638a7bfb4ed4b 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocLegacyConfigRootListener.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocLegacyConfigRootListener.java
@@ -1,7 +1,5 @@
package io.quarkus.annotation.processor.documentation.config.scanner;
-import java.util.Optional;
-
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
@@ -9,8 +7,8 @@
import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadoc;
import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadocSection;
import io.quarkus.annotation.processor.documentation.config.discovery.ResolvedType;
-import io.quarkus.annotation.processor.documentation.config.formatter.JavadocToAsciidocTransformer;
import io.quarkus.annotation.processor.documentation.config.model.JavadocElements.JavadocElement;
+import io.quarkus.annotation.processor.documentation.config.util.JavadocUtil;
import io.quarkus.annotation.processor.documentation.config.util.Markers;
import io.quarkus.annotation.processor.documentation.config.util.Types;
import io.quarkus.annotation.processor.util.Config;
@@ -38,33 +36,36 @@ public void onEnclosedField(DiscoveryRootElement discoveryRootElement, TypeEleme
return;
}
- Optional rawJavadoc = utils.element().getJavadoc(field);
+ String rawJavadoc = utils.element().getJavadoc(field).orElse("");
boolean isSection = utils.element().isAnnotationPresent(field, Types.ANNOTATION_CONFIG_DOC_SECTION);
- if (rawJavadoc.isEmpty()) {
- // We require a Javadoc for config items that are not config groups except if they are a section
- if (!resolvedType.isConfigGroup() || isSection) {
- utils.element().addMissingJavadocError(field);
- }
- return;
- }
-
if (isSection) {
// for sections, we only keep the title
- ParsedJavadocSection parsedJavadocSection = JavadocToAsciidocTransformer.INSTANCE
- .parseConfigSectionJavadoc(rawJavadoc.get());
+ ParsedJavadocSection parsedJavadocSection = JavadocUtil.parseConfigSectionJavadoc(rawJavadoc);
+
+ if (parsedJavadocSection.title() == null) {
+ return;
+ }
configCollector.addJavadocElement(
clazz.getQualifiedName().toString() + Markers.DOT + field.getSimpleName().toString(),
- new JavadocElement(parsedJavadocSection.title(), null, parsedJavadocSection.deprecated(),
- rawJavadoc.get()));
+ new JavadocElement(parsedJavadocSection.title(), parsedJavadocSection.format(), null,
+ parsedJavadocSection.deprecated()));
} else {
- ParsedJavadoc parsedJavadoc = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(rawJavadoc.get());
+ ParsedJavadoc parsedJavadoc = JavadocUtil.parseConfigItemJavadoc(rawJavadoc);
+
+ // We require a Javadoc for config items that are not config groups except if they are a section
+ if (parsedJavadoc.description() == null) {
+ if (parsedJavadoc.deprecated() == null && !resolvedType.isConfigGroup()) {
+ utils.element().addMissingJavadocError(field);
+ }
+ return;
+ }
configCollector.addJavadocElement(
clazz.getQualifiedName().toString() + Markers.DOT + field.getSimpleName().toString(),
- new JavadocElement(parsedJavadoc.description(), parsedJavadoc.since(), parsedJavadoc.deprecated(),
- rawJavadoc.get()));
+ new JavadocElement(parsedJavadoc.description(), parsedJavadoc.format(), parsedJavadoc.since(),
+ parsedJavadoc.deprecated()));
}
}
}
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtil.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtil.java
index 8ca9e307b0ca0..2bed8914e4cb0 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtil.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtil.java
@@ -2,12 +2,31 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.Optional;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.jsoup.Jsoup;
+
+import com.github.javaparser.StaticJavaParser;
+import com.github.javaparser.javadoc.Javadoc;
+import com.github.javaparser.javadoc.JavadocBlockTag;
+import com.github.javaparser.javadoc.JavadocBlockTag.Type;
+import com.github.javaparser.javadoc.description.JavadocDescription;
+
+import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadoc;
+import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadocSection;
+import io.quarkus.annotation.processor.documentation.config.model.JavadocFormat;
+
public final class JavadocUtil {
+ private static final Pattern START_OF_LINE = Pattern.compile("^", Pattern.MULTILINE);
+ private static final Pattern REPLACE_WINDOWS_EOL = Pattern.compile("\r\n");
+ private static final Pattern REPLACE_MACOS_EOL = Pattern.compile("\r");
+ private static final String DOT = ".";
+ private static final String NEW_LINE = "\n";
+
static final String VERTX_JAVA_DOC_SITE = "https://vertx.io/docs/apidocs/";
static final String OFFICIAL_JAVA_DOC_BASE_LINK = "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/";
static final String AGROAL_API_JAVA_DOC_SITE = "https://javadoc.io/doc/io.agroal/agroal-api/latest/";
@@ -25,6 +44,118 @@ public final class JavadocUtil {
private JavadocUtil() {
}
+ public static ParsedJavadoc parseConfigItemJavadoc(String rawJavadoc) {
+ if (rawJavadoc == null || rawJavadoc.isBlank()) {
+ return ParsedJavadoc.empty();
+ }
+
+ // the parser expects all the lines to start with "* "
+ // we add it as it has been previously removed
+ Javadoc javadoc = StaticJavaParser.parseJavadoc(START_OF_LINE.matcher(rawJavadoc).replaceAll("* "));
+
+ String description;
+ JavadocFormat format;
+
+ if (isAsciidoc(javadoc)) {
+ description = normalizeEol(javadoc.getDescription().toText());
+ format = JavadocFormat.ASCIIDOC;
+ } else if (isMarkdown(javadoc)) {
+ // this is to prepare the Markdown Javadoc that will come up soon enough
+ // I don't know exactly how the parser will deal with them though
+ description = normalizeEol(javadoc.getDescription().toText());
+ format = JavadocFormat.MARKDOWN;
+ } else {
+ description = normalizeEol(javadoc.getDescription().toText());
+ format = JavadocFormat.JAVADOC;
+ }
+
+ Optional since = javadoc.getBlockTags().stream()
+ .filter(t -> t.getType() == Type.SINCE)
+ .map(JavadocBlockTag::getContent)
+ .map(JavadocDescription::toText)
+ .findFirst();
+
+ Optional deprecated = javadoc.getBlockTags().stream()
+ .filter(t -> t.getType() == Type.DEPRECATED)
+ .map(JavadocBlockTag::getContent)
+ .map(JavadocDescription::toText)
+ .findFirst();
+
+ if (description != null && description.isBlank()) {
+ description = null;
+ }
+
+ return new ParsedJavadoc(description, format, since.orElse(null), deprecated.orElse(null));
+ }
+
+ public static ParsedJavadocSection parseConfigSectionJavadoc(String javadocComment) {
+ if (javadocComment == null || javadocComment.trim().isEmpty()) {
+ return ParsedJavadocSection.empty();
+ }
+
+ // the parser expects all the lines to start with "* "
+ // we add it as it has been previously removed
+ javadocComment = START_OF_LINE.matcher(javadocComment).replaceAll("* ");
+ Javadoc javadoc = StaticJavaParser.parseJavadoc(javadocComment);
+
+ Optional deprecated = javadoc.getBlockTags().stream()
+ .filter(t -> t.getType() == Type.DEPRECATED)
+ .map(JavadocBlockTag::getContent)
+ .map(JavadocDescription::toText)
+ .findFirst();
+
+ String description;
+ JavadocFormat format;
+
+ if (isAsciidoc(javadoc)) {
+ description = normalizeEol(javadoc.getDescription().toText());
+ format = JavadocFormat.ASCIIDOC;
+ } else if (isMarkdown(javadoc)) {
+ // this is to prepare the Markdown Javadoc that will come up soon enough
+ // I don't know exactly how the parser will deal with them though
+ description = normalizeEol(javadoc.getDescription().toText());
+ format = JavadocFormat.MARKDOWN;
+ } else {
+ description = normalizeEol(javadoc.getDescription().toText());
+ format = JavadocFormat.JAVADOC;
+ }
+
+ if (description == null || description.isBlank()) {
+ return ParsedJavadocSection.empty();
+ }
+
+ final int newLineIndex = description.indexOf(NEW_LINE);
+ final int dotIndex = description.indexOf(DOT);
+
+ final int endOfTitleIndex;
+ if (newLineIndex > 0 && newLineIndex < dotIndex) {
+ endOfTitleIndex = newLineIndex;
+ } else {
+ endOfTitleIndex = dotIndex;
+ }
+
+ String title;
+ String details;
+
+ if (endOfTitleIndex == -1) {
+ title = description.trim();
+ details = null;
+ } else {
+ title = description.substring(0, endOfTitleIndex).trim();
+ details = description.substring(endOfTitleIndex + 1).trim();
+ }
+
+ if (title.contains("<")) {
+ title = Jsoup.parse(title).text();
+ }
+
+ title = title.replaceAll("^([^\\w])+", "");
+
+ return new ParsedJavadocSection(title == null || title.isBlank() ? null : title,
+ details == null || details.isBlank() ? null : details, format,
+ deprecated.orElse(null));
+ }
+
/**
* Get javadoc link of a given type value
*/
@@ -59,6 +190,33 @@ public static String getJavadocSiteLink(String binaryName) {
return null;
}
+ private static String normalizeEol(String javadoc) {
+ // it's Asciidoc, so we just pass through
+ // it also uses platform specific EOL, so we need to convert them back to \n
+ String normalizedJavadoc = javadoc;
+ normalizedJavadoc = REPLACE_WINDOWS_EOL.matcher(normalizedJavadoc).replaceAll("\n");
+ normalizedJavadoc = REPLACE_MACOS_EOL.matcher(normalizedJavadoc).replaceAll("\n");
+ return normalizedJavadoc;
+ }
+
+ private static boolean isAsciidoc(Javadoc javadoc) {
+ for (JavadocBlockTag blockTag : javadoc.getBlockTags()) {
+ if ("asciidoclet".equals(blockTag.getTagName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isMarkdown(Javadoc javadoc) {
+ for (JavadocBlockTag blockTag : javadoc.getBlockTags()) {
+ if ("markdown".equals(blockTag.getTagName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static String getJavaDocLinkForType(String type) {
int beginOfWrappedTypeIndex = type.indexOf("<");
if (beginOfWrappedTypeIndex != -1) {
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/util/ElementUtil.java b/core/processor/src/main/java/io/quarkus/annotation/processor/util/ElementUtil.java
index eaf41d1ec52a5..86d07a9229ef1 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/util/ElementUtil.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/util/ElementUtil.java
@@ -172,9 +172,10 @@ public Optional getJavadoc(Element e) {
}
public void addMissingJavadocError(Element e) {
- processingEnv.getMessager()
- .printMessage(Diagnostic.Kind.ERROR,
- "Unable to find javadoc for config item " + e.getEnclosingElement() + " " + e, e);
+ String error = "Unable to find javadoc for config item " + e.getEnclosingElement() + " " + e;
+
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, error, e);
+ throw new IllegalStateException(error);
}
public boolean isJdkClass(TypeElement e) {
diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformerConfigItemTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformerConfigItemTest.java
index 3bab12d00c215..546e8cc05b11f 100644
--- a/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformerConfigItemTest.java
+++ b/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformerConfigItemTest.java
@@ -11,51 +11,42 @@
import org.junit.jupiter.params.provider.ValueSource;
import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadoc;
+import io.quarkus.annotation.processor.documentation.config.util.JavadocUtil;
public class JavadocToAsciidocTransformerConfigItemTest {
- @Test
- public void parseNullJavaDoc() {
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(null);
- assertNull(parsed.description());
- }
-
@Test
public void removeParagraphIndentation() {
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE
- .parseConfigItemJavadoc("First paragraph Second Paragraph");
- assertEquals("First paragraph +\n +\nSecond Paragraph", parsed.description());
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc("First paragraph Second Paragraph");
+ assertEquals("First paragraph +\n +\nSecond Paragraph",
+ JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format()));
}
@Test
public void parseUntrimmedJavaDoc() {
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(" ");
- assertNull(parsed.description());
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(" ");
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(" ");
assertNull(parsed.description());
- }
-
- @Test
- public void parseSimpleJavaDoc() {
- String javaDoc = "hello world";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
- assertEquals(javaDoc, parsed.description());
+ parsed = JavadocUtil.parseConfigItemJavadoc(" ");
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
+ assertNull(description);
}
@Test
public void parseJavaDocWithParagraph() {
String javaDoc = "helloworld
";
String expectedOutput = "hello\n\nworld";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
javaDoc = "hello worldbonjour
le monde
";
expectedOutput = "hello world\n\nbonjour\n\nle monde";
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
}
@Test
@@ -63,55 +54,64 @@ public void parseJavaDocWithStyles() {
// Bold
String javaDoc = "hello world ";
String expectedOutput = "hello *world*";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
- assertEquals(expectedOutput, parsed.description());
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
+ assertEquals(expectedOutput, description);
javaDoc = "hello world ";
expectedOutput = "hello *world*";
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
- assertEquals(expectedOutput, parsed.description());
+ parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
+ assertEquals(expectedOutput, description);
// Emphasized
javaDoc = "hello world ";
expectedOutput = "_hello world_";
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
- assertEquals(expectedOutput, parsed.description());
+ parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
+ assertEquals(expectedOutput, description);
// Italics
javaDoc = "hello world ";
expectedOutput = "_hello world_";
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
- assertEquals(expectedOutput, parsed.description());
+ parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
+ assertEquals(expectedOutput, description);
// Underline
javaDoc = "hello world ";
expectedOutput = "[.underline]#hello world#";
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
- assertEquals(expectedOutput, parsed.description());
+ parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
+ assertEquals(expectedOutput, description);
// small
javaDoc = "quarkus subatomic ";
expectedOutput = "[.small]#quarkus subatomic#";
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
- assertEquals(expectedOutput, parsed.description());
+ parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
+ assertEquals(expectedOutput, description);
// big
javaDoc = "hello world ";
expectedOutput = "[.big]#hello world#";
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
- assertEquals(expectedOutput, parsed.description());
+ parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
+ assertEquals(expectedOutput, description);
// line through
javaDoc = "hello monolith world ";
expectedOutput = "[.line-through]#hello #[.line-through]#monolith #[.line-through]#world#";
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
- assertEquals(expectedOutput, parsed.description());
+ parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
+ assertEquals(expectedOutput, description);
// superscript and subscript
javaDoc = "cloud in-premise ";
expectedOutput = "^cloud ^~in-premise~";
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
- assertEquals(expectedOutput, parsed.description());
+ parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
+ assertEquals(expectedOutput, description);
}
@Test
@@ -123,9 +123,10 @@ public void parseJavaDocWithLiTagsInsideUlTag() {
"" +
"";
String expectedOutput = "List:\n\n - 1\n - 2";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
}
@Test
@@ -137,76 +138,93 @@ public void parseJavaDocWithLiTagsInsideOlTag() {
"" +
"";
String expectedOutput = "List:\n\n . 1\n . 2";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
}
@Test
public void parseJavaDocWithLinkInlineSnippet() {
String javaDoc = "{@link firstlink} {@link #secondlink} \n {@linkplain #third.link}";
String expectedOutput = "`firstlink` `secondlink` `third.link`";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
}
@Test
public void parseJavaDocWithLinkTag() {
String javaDoc = "this is a hello link";
String expectedOutput = "this is a link:http://link.com[hello] link";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
}
@Test
public void parseJavaDocWithCodeInlineSnippet() {
String javaDoc = "{@code true} {@code false}";
String expectedOutput = "`true` `false`";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
}
@Test
public void parseJavaDocWithLiteralInlineSnippet() {
String javaDoc = "{@literal java.util.Boolean}";
String expectedOutput = "`java.util.Boolean`";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
}
@Test
public void parseJavaDocWithValueInlineSnippet() {
String javaDoc = "{@value 10s}";
String expectedOutput = "`10s`";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
}
@Test
public void parseJavaDocWithUnknownInlineSnippet() {
String javaDoc = "{@see java.util.Boolean}";
String expectedOutput = "java.util.Boolean";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
}
@Test
public void parseJavaDocWithUnknownNode() {
String javaDoc = "hello ";
String expectedOutput = "hello";
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc);
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ String description = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
- assertEquals(expectedOutput, parsed.description());
+ assertEquals(expectedOutput, description);
}
@Test
public void parseJavaDocWithBlockquoteBlock() {
+ ParsedJavadoc parsed = JavadocUtil
+ .parseConfigItemJavadoc("See Section 4.5.5 of the JSR 380 specification, specifically\n"
+ + "\n"
+ + "\n"
+ + "In sub types (be it sub classes/interfaces or interface implementations), no parameter constraints may\n"
+ + "be declared on overridden or implemented methods, nor may parameters be marked for cascaded validation.\n"
+ + "This would pose a strengthening of preconditions to be fulfilled by the caller.\n"
+ + " \nThat was interesting, wasn't it?");
+
assertEquals("See Section 4.5.5 of the JSR 380 specification, specifically\n"
+ "\n"
+ "[quote]\n"
@@ -215,49 +233,37 @@ public void parseJavaDocWithBlockquoteBlock() {
+ "____\n"
+ "\n"
+ "That was interesting, wasn't it?",
- JavadocToAsciidocTransformer.INSTANCE
- .parseConfigItemJavadoc("See Section 4.5.5 of the JSR 380 specification, specifically\n"
- + "\n"
- + "\n"
- + "In sub types (be it sub classes/interfaces or interface implementations), no parameter constraints may\n"
- + "be declared on overridden or implemented methods, nor may parameters be marked for cascaded validation.\n"
- + "This would pose a strengthening of preconditions to be fulfilled by the caller.\n"
- + " \nThat was interesting, wasn't it?")
- .description());
+ JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format()));
+
+ parsed = JavadocUtil.parseConfigItemJavadoc(
+ "Some HTML entities & special characters:\n\n<os>|<arch>[/variant]|<os>/<arch>[/variant]\n \n\nbaz");
assertEquals(
"Some HTML entities & special characters:\n\n```\n|[/variant]|/[/variant]\n```\n\nbaz",
- JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(
- "Some HTML entities & special characters:\n\n<os>|<arch>[/variant]|<os>/<arch>[/variant]\n \n\nbaz")
- .description());
+ JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format()));
// TODO
// assertEquals("Example:\n\n```\nfoo\nbar\n```",
- // JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc("Example:\n\n{@code\nfoo\nbar\n} "));
+ // JavadocUtil.parseConfigItemJavadoc("Example:\n\n{@code\nfoo\nbar\n} "));
}
@Test
public void parseJavaDocWithCodeBlock() {
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc("Example:\n\n\nfoo\nbar\n \n\nbaz");
+
assertEquals("Example:\n\n```\nfoo\nbar\n```\n\nbaz",
- JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc("Example:\n\n\nfoo\nbar\n \n\nbaz")
- .description());
+ JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format()));
+
+ parsed = JavadocUtil.parseConfigItemJavadoc(
+ "Some HTML entities & special characters:\n\n<os>|<arch>[/variant]|<os>/<arch>[/variant]\n \n\nbaz");
assertEquals(
"Some HTML entities & special characters:\n\n```\n|[/variant]|/[/variant]\n```\n\nbaz",
- JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(
- "Some HTML entities & special characters:\n\n<os>|<arch>[/variant]|<os>/<arch>[/variant]\n \n\nbaz")
- .description());
+ JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format()));
// TODO
// assertEquals("Example:\n\n```\nfoo\nbar\n```",
- // JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc("Example:\n\n{@code\nfoo\nbar\n} "));
- }
-
- @Test
- public void since() {
- ParsedJavadoc parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc("Javadoc text\n\n@since 1.2.3");
- assertEquals("Javadoc text", parsed.description());
- assertEquals("1.2.3", parsed.since());
+ // JavadocUtil.parseConfigItemJavadoc("Example:\n\n{@code\nfoo\nbar\n} "));
}
@Test
@@ -276,8 +282,9 @@ public void asciidoc() {
"And some code\n" +
"----";
- assertEquals(asciidoc,
- JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(asciidoc + "\n" + "@asciidoclet").description());
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(asciidoc + "\n" + "@asciidoclet");
+
+ assertEquals(asciidoc, JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format()));
}
@Test
@@ -289,8 +296,9 @@ public void asciidocLists() {
" * 1.2\n" +
"* 2";
- assertEquals(asciidoc,
- JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(asciidoc + "\n" + "@asciidoclet").description());
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(asciidoc + "\n" + "@asciidoclet");
+
+ assertEquals(asciidoc, JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format()));
}
@ParameterizedTest
@@ -299,7 +307,8 @@ public void escape(String ch) {
final String javaDoc = "Inline " + ch + " " + ch + ch + ", HTML tag glob " + ch + " " + ch + ch
+ "
, {@code JavaDoc tag " + ch + " " + ch + ch + "}";
- final String asciiDoc = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc).description();
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ final String asciiDoc = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
final String actual = Factory.create().convert(asciiDoc, Collections.emptyMap());
final String expected = "\n
Inline " + ch + " " + ch + ch + ", HTML tag glob " + ch
+ " " + ch + ch + "
, JavaDoc tag " + ch + " " + ch + ch + "
\n
";
@@ -312,8 +321,8 @@ public void escapeInsideInlineElement(String ch) {
final String javaDoc = "Inline " + ch + " " + ch + ch + ", HTML tag glob " + ch + " " + ch + ch
+ "
, {@code JavaDoc tag " + ch + " " + ch + ch + "}";
- final String asciiDoc = JavadocToAsciidocTransformer.INLINE_MACRO_INSTANCE.parseConfigItemJavadoc(javaDoc)
- .description();
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ final String asciiDoc = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format(), true);
final String actual = Factory.create().convert(asciiDoc, Collections.emptyMap());
if (ch.equals("]")) {
@@ -329,7 +338,8 @@ public void escapePlus() {
final String javaDoc = "Inline + ++, HTML tag glob + ++
, {@code JavaDoc tag + ++}";
final String expected = "\n
Inline + ++, HTML tag glob + ++
, JavaDoc tag + ++
\n
";
- final String asciiDoc = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc).description();
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ final String asciiDoc = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
final String actual = Factory.create().convert(asciiDoc, Collections.emptyMap());
assertEquals(expected, actual);
}
@@ -342,7 +352,8 @@ public void escapeBrackets(String ch) {
final String expected = "\n
Inline " + ch + " " + ch + ch + ", HTML tag glob " + ch
+ " " + ch + ch + "
\n
";
- final String asciiDoc = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(javaDoc).description();
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+ final String asciiDoc = JavadocToAsciidocTransformer.toAsciidoc(parsed.description(), parsed.format());
final String actual = Factory.create().convert(asciiDoc, Collections.emptyMap());
assertEquals(expected, actual);
}
diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformerConfigSectionTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformerConfigSectionTest.java
index 190b32c9bba84..9ab4290efa591 100644
--- a/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformerConfigSectionTest.java
+++ b/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformerConfigSectionTest.java
@@ -5,25 +5,19 @@
import org.junit.jupiter.api.Test;
import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadocSection;
+import io.quarkus.annotation.processor.documentation.config.util.JavadocUtil;
public class JavadocToAsciidocTransformerConfigSectionTest {
- @Test
- public void parseNullSection() {
- ParsedJavadocSection parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(null);
- assertEquals(null, parsed.details());
- assertEquals(null, parsed.title());
- }
-
@Test
public void parseUntrimmedJavaDoc() {
- ParsedJavadocSection parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(" ");
- assertEquals(null, parsed.title());
- assertEquals(null, parsed.details());
+ ParsedJavadocSection parsed = JavadocUtil.parseConfigSectionJavadoc(" ");
+ assertEquals(null, JavadocToAsciidocTransformer.toAsciidoc(parsed.title(), parsed.format()));
+ assertEquals(null, JavadocToAsciidocTransformer.toAsciidoc(parsed.details(), parsed.format()));
- parsed = JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(" ");
- assertEquals(null, parsed.title());
- assertEquals(null, parsed.details());
+ parsed = JavadocUtil.parseConfigSectionJavadoc(" ");
+ assertEquals(null, JavadocToAsciidocTransformer.toAsciidoc(parsed.title(), parsed.format()));
+ assertEquals(null, JavadocToAsciidocTransformer.toAsciidoc(parsed.details(), parsed.format()));
}
@Test
@@ -43,10 +37,9 @@ public void passThroughAConfigSectionInAsciiDoc() {
String asciidoc = "=== " + title + "\n\n" + details;
- ParsedJavadocSection sectionHolder = JavadocToAsciidocTransformer.INSTANCE
- .parseConfigSectionJavadoc(asciidoc + "\n" + "@asciidoclet");
- assertEquals(title, sectionHolder.title());
- assertEquals(details, sectionHolder.details());
+ ParsedJavadocSection sectionHolder = JavadocUtil.parseConfigSectionJavadoc(asciidoc + "\n" + "@asciidoclet");
+ assertEquals(title, JavadocToAsciidocTransformer.toAsciidoc(sectionHolder.title(), sectionHolder.format()));
+ assertEquals(details, JavadocToAsciidocTransformer.toAsciidoc(sectionHolder.details(), sectionHolder.format()));
asciidoc = "Asciidoc title. \n" +
"\n" +
@@ -62,66 +55,7 @@ public void passThroughAConfigSectionInAsciiDoc() {
"And some code\n" +
"----";
- sectionHolder = JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(asciidoc + "\n" + "@asciidoclet");
- assertEquals("Asciidoc title", sectionHolder.title());
- }
-
- @Test
- public void parseSectionWithoutIntroduction() {
- /**
- * Simple javadoc
- */
- String javaDoc = "Config Section";
- String expectedTitle = "Config Section";
- String expectedDetails = null;
- ParsedJavadocSection sectionHolder = JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(javaDoc);
- assertEquals(expectedDetails, sectionHolder.details());
- assertEquals(expectedTitle, sectionHolder.title());
-
- javaDoc = "Config Section.";
- expectedTitle = "Config Section";
- expectedDetails = null;
- sectionHolder = JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(javaDoc);
- assertEquals(expectedDetails, sectionHolder.details());
- assertEquals(expectedTitle, sectionHolder.title());
-
- /**
- * html javadoc
- */
- javaDoc = "Config Section
";
- expectedTitle = "Config Section";
- expectedDetails = null;
- sectionHolder = JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(javaDoc);
- assertEquals(expectedDetails, sectionHolder.details());
- assertEquals(expectedTitle, sectionHolder.title());
- }
-
- @Test
- public void parseSectionWithIntroduction() {
- /**
- * Simple javadoc
- */
- String javaDoc = "Config Section .Introduction";
- String expectedDetails = "Introduction";
- String expectedTitle = "Config Section";
- assertEquals(expectedTitle, JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(javaDoc).title());
- assertEquals(expectedDetails, JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(javaDoc).details());
-
- /**
- * html javadoc
- */
- javaDoc = "Config Section
. Introduction";
- expectedDetails = "Introduction";
- assertEquals(expectedDetails, JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(javaDoc).details());
- assertEquals(expectedTitle, JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(javaDoc).title());
- }
-
- @Test
- public void properlyParseConfigSectionWrittenInHtml() {
- String javaDoc = "Config Section.
This is section introduction";
- String expectedDetails = "This is section introduction";
- String title = "Config Section";
- assertEquals(expectedDetails, JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(javaDoc).details());
- assertEquals(title, JavadocToAsciidocTransformer.INSTANCE.parseConfigSectionJavadoc(javaDoc).title());
+ sectionHolder = JavadocUtil.parseConfigSectionJavadoc(asciidoc + "\n" + "@asciidoclet");
+ assertEquals("Asciidoc title", JavadocToAsciidocTransformer.toAsciidoc(sectionHolder.title(), sectionHolder.format()));
}
}
diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtilTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtilTest.java
index 9fab0e55e0e52..1b16410465b84 100644
--- a/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtilTest.java
+++ b/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtilTest.java
@@ -11,8 +11,118 @@
import org.junit.jupiter.api.Test;
+import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadoc;
+import io.quarkus.annotation.processor.documentation.config.discovery.ParsedJavadocSection;
+
public class JavadocUtilTest {
+ @Test
+ public void parseNullJavaDoc() {
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(null);
+ assertNull(parsed.description());
+ }
+
+ @Test
+ public void parseSimpleJavaDoc() {
+ String javaDoc = "hello world";
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc(javaDoc);
+
+ assertEquals(javaDoc, parsed.description());
+ }
+
+ @Test
+ public void since() {
+ ParsedJavadoc parsed = JavadocUtil.parseConfigItemJavadoc("Javadoc text\n\n@since 1.2.3");
+ assertEquals("Javadoc text", parsed.description());
+ assertEquals("1.2.3", parsed.since());
+ }
+
+ @Test
+ public void deprecated() {
+ ParsedJavadoc parsed = JavadocUtil
+ .parseConfigItemJavadoc("@deprecated JNI is always enabled starting from GraalVM 19.3.1.");
+ assertEquals(null, parsed.description());
+ }
+
+ @Test
+ public void parseNullSection() {
+ ParsedJavadocSection parsed = JavadocUtil.parseConfigSectionJavadoc(null);
+ assertEquals(null, parsed.details());
+ assertEquals(null, parsed.title());
+ }
+
+ @Test
+ public void parseSimpleSection() {
+ ParsedJavadocSection parsed = JavadocUtil.parseConfigSectionJavadoc("title");
+ assertEquals("title", parsed.title());
+ assertEquals(null, parsed.details());
+ }
+
+ @Test
+ public void parseSectionWithIntroduction() {
+ /**
+ * Simple javadoc
+ */
+ String javaDoc = "Config Section .Introduction";
+ String expectedDetails = "Introduction";
+ String expectedTitle = "Config Section";
+ assertEquals(expectedTitle, JavadocUtil.parseConfigSectionJavadoc(javaDoc).title());
+ assertEquals(expectedDetails, JavadocUtil.parseConfigSectionJavadoc(javaDoc).details());
+
+ /**
+ * html javadoc
+ */
+ javaDoc = "Config Section
. Introduction";
+ expectedDetails = "Introduction";
+ assertEquals(expectedDetails, JavadocUtil.parseConfigSectionJavadoc(javaDoc).details());
+ assertEquals(expectedTitle, JavadocUtil.parseConfigSectionJavadoc(javaDoc).title());
+ }
+
+ @Test
+ public void parseSectionWithParagraph() {
+ String javaDoc = "Dev Services\n\nDev Services allows Quarkus to automatically start Elasticsearch in dev and test mode.";
+ assertEquals("Dev Services", JavadocUtil.parseConfigSectionJavadoc(javaDoc).title());
+ }
+
+ @Test
+ public void properlyParseConfigSectionWrittenInHtml() {
+ String javaDoc = "Config Section.
This is section introduction";
+ String expectedDetails = "
This is section introduction";
+ String title = "Config Section";
+ assertEquals(expectedDetails, JavadocUtil.parseConfigSectionJavadoc(javaDoc).details());
+ assertEquals(title, JavadocUtil.parseConfigSectionJavadoc(javaDoc).title());
+ }
+
+ @Test
+ public void parseSectionWithoutIntroduction() {
+ /**
+ * Simple javadoc
+ */
+ String javaDoc = "Config Section";
+ String expectedTitle = "Config Section";
+ String expectedDetails = null;
+ ParsedJavadocSection sectionHolder = JavadocUtil.parseConfigSectionJavadoc(javaDoc);
+ assertEquals(expectedDetails, sectionHolder.details());
+ assertEquals(expectedTitle, sectionHolder.title());
+
+ javaDoc = "Config Section.";
+ expectedTitle = "Config Section";
+ expectedDetails = null;
+ sectionHolder = JavadocUtil.parseConfigSectionJavadoc(javaDoc);
+ assertEquals(expectedDetails, sectionHolder.details());
+ assertEquals(expectedTitle, sectionHolder.title());
+
+ /**
+ * html javadoc
+ */
+ javaDoc = "
Config Section
";
+ expectedTitle = "Config Section";
+ expectedDetails = null;
+ sectionHolder = JavadocUtil.parseConfigSectionJavadoc(javaDoc);
+ assertEquals(expectedDetails, sectionHolder.details());
+ assertEquals(expectedTitle, sectionHolder.title());
+ }
+
@Test
public void shouldReturnEmptyListForPrimitiveValue() {
String value = JavadocUtil.getJavadocSiteLink("int");
diff --git a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateAsciidocMojo.java b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateAsciidocMojo.java
index c6c6386052cc8..a1ed3bf1bae0e 100644
--- a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateAsciidocMojo.java
+++ b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateAsciidocMojo.java
@@ -1,406 +1,13 @@
package io.quarkus.maven.config.doc;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UncheckedIOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.apache.maven.execution.MavenSession;
-import org.apache.maven.plugin.AbstractMojo;
-import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
-import org.apache.maven.plugins.annotations.Parameter;
-
-import io.quarkus.annotation.processor.documentation.config.merger.JavadocMerger;
-import io.quarkus.annotation.processor.documentation.config.merger.JavadocRepository;
-import io.quarkus.annotation.processor.documentation.config.merger.MergedModel;
-import io.quarkus.annotation.processor.documentation.config.merger.MergedModel.ConfigRootKey;
-import io.quarkus.annotation.processor.documentation.config.merger.ModelMerger;
-import io.quarkus.annotation.processor.documentation.config.model.ConfigItemCollection;
-import io.quarkus.annotation.processor.documentation.config.model.ConfigProperty;
-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.qute.Engine;
-import io.quarkus.qute.ReflectionValueResolver;
-import io.quarkus.qute.UserTagSectionHelper;
-import io.quarkus.qute.ValueResolver;
+/**
+ * We will recommend using generate-config-doc that is more flexible.
+ *
+ * The GenerateConfigDocMojo defaults have to stay consistent with this.
+ */
@Mojo(name = "generate-asciidoc", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true)
-public class GenerateAsciidocMojo extends AbstractMojo {
-
- private static final String TARGET = "target";
-
- private static final String ADOC_SUFFIX = ".adoc";
- private static final String CONFIG_ROOT_FILE_FORMAT = "%s_%s.adoc";
- private static final String EXTENSION_FILE_FORMAT = "%s.adoc";
- private static final String ALL_CONFIG_FILE_NAME = "quarkus-all-config.adoc";
-
- @Parameter(defaultValue = "${session}", readonly = true)
- private MavenSession mavenSession;
-
- @Parameter
- private File scanDirectory;
-
- @Parameter(defaultValue = "${project.build.directory}/quarkus-generated-doc/config", required = true)
- private File targetDirectory;
-
- @Parameter(defaultValue = "false")
- private boolean generateAllConfig;
-
- @Parameter(defaultValue = "false")
- private boolean enableEnumTooltips;
-
- @Parameter(defaultValue = "false")
- private boolean skip;
-
- @Override
- public void execute() throws MojoExecutionException, MojoFailureException {
- if (skip) {
- return;
- }
-
- // I was unable to find an easy way to get the root directory of the project
- Path resolvedScanDirectory = scanDirectory != null ? scanDirectory.toPath()
- : mavenSession.getCurrentProject().getBasedir().toPath().getParent();
- Path resolvedTargetDirectory = targetDirectory.toPath();
- initTargetDirectory(resolvedTargetDirectory);
-
- List targetDirectories = findTargetDirectories(resolvedScanDirectory);
-
- JavadocRepository javadocRepository = JavadocMerger.mergeJavadocElements(targetDirectories);
- MergedModel mergedModel = ModelMerger.mergeModel(javadocRepository, targetDirectories);
-
- AsciidocFormatter asciidocFormatter = new AsciidocFormatter(javadocRepository, enableEnumTooltips);
- Engine quteEngine = initializeQuteEngine(asciidocFormatter);
-
- // we generate a file per extension + top level prefix
- for (Entry> extensionConfigRootsEntry : mergedModel.getConfigRoots()
- .entrySet()) {
- Extension extension = extensionConfigRootsEntry.getKey();
-
- Path configRootAdocPath = null;
-
- for (Entry configRootEntry : extensionConfigRootsEntry.getValue().entrySet()) {
- String topLevelPrefix = configRootEntry.getKey().topLevelPrefix();
- ConfigRoot configRoot = configRootEntry.getValue();
-
- if (configRoot.getNonDeprecatedItems().isEmpty()) {
- continue;
- }
-
- configRootAdocPath = resolvedTargetDirectory.resolve(String.format(CONFIG_ROOT_FILE_FORMAT,
- extension.artifactId(), topLevelPrefix));
- String summaryTableId = asciidocFormatter
- .toAnchor(extension.artifactId() + "_" + topLevelPrefix);
-
- try {
- Files.writeString(configRootAdocPath,
- generateConfigReference(quteEngine, summaryTableId, extension, configRoot, "", true));
- } catch (Exception e) {
- throw new MojoExecutionException("Unable to render config roots for top level prefix: " + topLevelPrefix
- + " in extension: " + extension, e);
- }
- }
-
- // if we have only one top level prefix, we copy the generated file to a file named after the extension
- // for simplicity's sake
- if (extensionConfigRootsEntry.getValue().size() == 1 && configRootAdocPath != null) {
- Path extensionAdocPath = resolvedTargetDirectory.resolve(String.format(EXTENSION_FILE_FORMAT,
- extension.artifactId()));
-
- try {
- Files.copy(configRootAdocPath, extensionAdocPath, StandardCopyOption.REPLACE_EXISTING);
- } catch (Exception e) {
- throw new MojoExecutionException("Unable to copy extension file for: " + extension, e);
- }
- }
- }
-
- // we generate the config roots that are saved in a specific file
- for (Entry specificFileConfigRootEntry : mergedModel.getConfigRootsInSpecificFile().entrySet()) {
- String fileName = specificFileConfigRootEntry.getKey();
- ConfigRoot configRoot = specificFileConfigRootEntry.getValue();
-
- if (configRoot.getNonDeprecatedItems().isEmpty()) {
- continue;
- }
-
- Extension extension = configRoot.getExtension();
-
- if (!fileName.endsWith(".adoc")) {
- fileName += ".adoc";
- }
-
- Path configRootAdocPath = resolvedTargetDirectory.resolve(fileName);
- String summaryTableId = asciidocFormatter.toAnchor(stripAdocSuffix(fileName));
-
- try {
- Files.writeString(configRootAdocPath,
- generateConfigReference(quteEngine, summaryTableId, extension, configRoot, "", true));
- } catch (Exception e) {
- throw new MojoExecutionException("Unable to render config roots for specific file: " + fileName
- + " in extension: " + extension, e);
- }
- }
-
- // we generate files for generated sections
- for (Entry> extensionConfigSectionsEntry : mergedModel.getGeneratedConfigSections()
- .entrySet()) {
- Extension extension = extensionConfigSectionsEntry.getKey();
-
- for (ConfigSection generatedConfigSection : extensionConfigSectionsEntry.getValue()) {
- if (generatedConfigSection.getNonDeprecatedItems().isEmpty()) {
- continue;
- }
-
- Path configSectionAdocPath = resolvedTargetDirectory.resolve(String.format(CONFIG_ROOT_FILE_FORMAT,
- extension.artifactId(), cleanSectionPath(generatedConfigSection.getPath().property())));
- String summaryTableId = asciidocFormatter
- .toAnchor(extension.artifactId() + "_" + generatedConfigSection.getPath().property());
-
- try {
- Files.writeString(configSectionAdocPath,
- generateConfigReference(quteEngine, summaryTableId, extension, generatedConfigSection,
- "_" + generatedConfigSection.getPath().property(), false));
- } catch (Exception e) {
- throw new MojoExecutionException(
- "Unable to render config section for section: " + generatedConfigSection.getPath().property()
- + " in extension: " + extension,
- e);
- }
- }
- }
-
- if (generateAllConfig) {
- // we generate the file centralizing all the config properties
- try {
- Path allConfigAdocPath = resolvedTargetDirectory.resolve(ALL_CONFIG_FILE_NAME);
-
- Files.writeString(allConfigAdocPath, generateAllConfig(quteEngine, mergedModel.getConfigRoots()));
- } catch (Exception e) {
- throw new MojoExecutionException("Unable to render all config", e);
- }
- }
- }
-
- private static String generateConfigReference(Engine quteEngine, String summaryTableId, Extension extension,
- ConfigItemCollection configItemCollection, String additionalAnchorPrefix, boolean searchable) {
- return quteEngine.getTemplate("configReference.qute.adoc")
- .data("extension", extension)
- .data("configItemCollection", configItemCollection)
- .data("searchable", searchable)
- .data("summaryTableId", summaryTableId)
- .data("additionalAnchorPrefix", additionalAnchorPrefix)
- .data("includeDurationNote", configItemCollection.hasDurationType())
- .data("includeMemorySizeNote", configItemCollection.hasMemorySizeType())
- .render();
- }
-
- private static String generateAllConfig(Engine quteEngine,
- Map> configRootsByExtensions) {
- return quteEngine.getTemplate("allConfig.qute.adoc")
- .data("configRootsByExtensions", configRootsByExtensions)
- .data("searchable", true)
- .data("summaryTableId", "all-config")
- .data("additionalAnchorPrefix", "")
- .data("includeDurationNote", true)
- .data("includeMemorySizeNote", true)
- .render();
- }
-
- private static void initTargetDirectory(Path resolvedTargetDirectory) throws MojoExecutionException {
- try {
- Files.createDirectories(resolvedTargetDirectory);
- } catch (IOException e) {
- throw new MojoExecutionException("Unable to create directory: " + resolvedTargetDirectory, e);
- }
- }
-
- private static List findTargetDirectories(Path scanDirectory) throws MojoExecutionException {
- try {
- List targets = new ArrayList<>();
-
- Files.walkFileTree(scanDirectory, new SimpleFileVisitor<>() {
-
- @Override
- public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
- if (dir.endsWith(TARGET)) {
- targets.add(dir);
-
- // a target directory can contain target directories for test projects
- // so let's make sure we ignore whatever is nested in a target
- return FileVisitResult.SKIP_SUBTREE;
- }
-
- return FileVisitResult.CONTINUE;
- }
- });
-
- // Make sure we are deterministic
- Collections.sort(targets);
-
- return targets;
- } catch (IOException e) {
- throw new MojoExecutionException("Unable to collect the target directories", e);
- }
- }
-
- private static Engine initializeQuteEngine(AsciidocFormatter asciidocFormatter) {
- Engine engine = Engine.builder()
- .addDefaults()
- .addSectionHelper(new UserTagSectionHelper.Factory("configProperty", "configProperty.qute.adoc"))
- .addSectionHelper(new UserTagSectionHelper.Factory("configSection", "configSection.qute.adoc"))
- .addSectionHelper(new UserTagSectionHelper.Factory("envVar", "envVar.qute.adoc"))
- .addSectionHelper(new UserTagSectionHelper.Factory("durationNote", "durationNote.qute.adoc"))
- .addSectionHelper(new UserTagSectionHelper.Factory("memorySizeNote", "memorySizeNote.qute.adoc"))
- .addValueResolver(new ReflectionValueResolver())
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(String.class)
- .applyToName("escapeCellContent")
- .applyToNoParameters()
- .resolveSync(ctx -> asciidocFormatter.escapeCellContent((String) ctx.getBase()))
- .build())
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(String.class)
- .applyToName("toAnchor")
- .applyToNoParameters()
- .resolveSync(ctx -> asciidocFormatter.toAnchor((String) ctx.getBase()))
- .build())
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(ConfigRootKey.class)
- .applyToName("displayConfigRootDescription")
- .applyToParameters(1)
- .resolveSync(ctx -> asciidocFormatter
- .displayConfigRootDescription((ConfigRootKey) ctx.getBase(),
- (int) ctx.evaluate(ctx.getParams().get(0)).toCompletableFuture().join()))
- .build())
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(ConfigProperty.class)
- .applyToName("toAnchor")
- .applyToParameters(2)
- .resolveSync(ctx -> asciidocFormatter
- .toAnchor(((Extension) ctx.evaluate(ctx.getParams().get(0)).toCompletableFuture().join())
- .artifactId() +
- // the additional suffix
- ctx.evaluate(ctx.getParams().get(1)).toCompletableFuture().join() +
- "_" + ((ConfigProperty) ctx.getBase()).getPath().property()))
- .build())
- // we need a different anchor for sections as otherwise we can have a conflict
- // (typically when you have an `enabled` property with parent name just under the section level)
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(ConfigSection.class)
- .applyToName("toAnchor")
- .applyToParameters(2)
- .resolveSync(ctx -> asciidocFormatter
- .toAnchor(((Extension) ctx.evaluate(ctx.getParams().get(0)).toCompletableFuture().join())
- .artifactId() +
- // the additional suffix
- ctx.evaluate(ctx.getParams().get(1)).toCompletableFuture().join() +
- "_section_" + ((ConfigSection) ctx.getBase()).getPath().property()))
- .build())
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(ConfigProperty.class)
- .applyToName("formatTypeDescription")
- .applyToNoParameters()
- .resolveSync(ctx -> asciidocFormatter.formatTypeDescription((ConfigProperty) ctx.getBase()))
- .build())
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(ConfigProperty.class)
- .applyToName("formatDescription")
- .applyToNoParameters()
- .resolveSync(ctx -> asciidocFormatter.formatDescription((ConfigProperty) ctx.getBase()))
- .build())
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(ConfigProperty.class)
- .applyToName("formatDefaultValue")
- .applyToNoParameters()
- .resolveSync(ctx -> asciidocFormatter.formatDefaultValue((ConfigProperty) ctx.getBase()))
- .build())
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(ConfigSection.class)
- .applyToName("formatTitle")
- .applyToNoParameters()
- .resolveSync(ctx -> asciidocFormatter.formatSectionTitle((ConfigSection) ctx.getBase()))
- .build())
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(ConfigSection.class)
- .applyToName("adjustedLevel")
- .applyToParameters(1)
- .resolveSync(ctx -> asciidocFormatter
- .adjustedLevel((ConfigSection) ctx.getBase(),
- (boolean) ctx.evaluate(ctx.getParams().get(0)).toCompletableFuture().join()))
- .build())
- .addValueResolver(ValueResolver.builder()
- .applyToBaseClass(Extension.class)
- .applyToName("formatName")
- .applyToNoParameters()
- .resolveSync(ctx -> asciidocFormatter.formatName((Extension) ctx.getBase()))
- .build())
- .build();
-
- engine.putTemplate("configReference.qute.adoc",
- engine.parse(getTemplate("templates/configReference.qute.adoc")));
- engine.putTemplate("allConfig.qute.adoc",
- engine.parse(getTemplate("templates/allConfig.qute.adoc")));
- engine.putTemplate("configProperty.qute.adoc",
- engine.parse(getTemplate("templates/tags/configProperty.qute.adoc")));
- engine.putTemplate("configSection.qute.adoc",
- engine.parse(getTemplate("templates/tags/configSection.qute.adoc")));
- engine.putTemplate("envVar.qute.adoc",
- engine.parse(getTemplate("templates/tags/envVar.qute.adoc")));
- engine.putTemplate("durationNote.qute.adoc",
- engine.parse(getTemplate("templates/tags/durationNote.qute.adoc")));
- engine.putTemplate("memorySizeNote.qute.adoc",
- engine.parse(getTemplate("templates/tags/memorySizeNote.qute.adoc")));
-
- return engine;
- }
-
- private static String getTemplate(String template) {
- InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(template);
- if (is == null) {
- throw new IllegalArgumentException("Template does not exist: " + template);
- }
-
- try {
- return new String(is.readAllBytes(), StandardCharsets.UTF_8);
- } catch (IOException e) {
- throw new UncheckedIOException("Unable to read the template: " + template, e);
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- throw new UncheckedIOException("Unable close InputStream for template: " + template, e);
- }
- }
- }
-
- /**
- * A section path can contain quotes when being inside a Map.
- *
- * While not very common, we sometimes have to generate a section inside a Map
- * e.g. for the XDS config of the gRPC client.
- */
- private static String cleanSectionPath(String sectionPath) {
- return sectionPath.replace('"', '-');
- }
-
- private String stripAdocSuffix(String fileName) {
- return fileName.substring(0, fileName.length() - ADOC_SUFFIX.length());
- }
+public class GenerateAsciidocMojo extends GenerateConfigDocMojo {
}
diff --git a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateConfigDocMojo.java b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateConfigDocMojo.java
new file mode 100644
index 0000000000000..ed5626b9f1bf1
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateConfigDocMojo.java
@@ -0,0 +1,433 @@
+package io.quarkus.maven.config.doc;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+import io.quarkus.annotation.processor.documentation.config.merger.JavadocMerger;
+import io.quarkus.annotation.processor.documentation.config.merger.JavadocRepository;
+import io.quarkus.annotation.processor.documentation.config.merger.MergedModel;
+import io.quarkus.annotation.processor.documentation.config.merger.MergedModel.ConfigRootKey;
+import io.quarkus.annotation.processor.documentation.config.merger.ModelMerger;
+import io.quarkus.annotation.processor.documentation.config.model.ConfigItemCollection;
+import io.quarkus.annotation.processor.documentation.config.model.ConfigProperty;
+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.maven.config.doc.generator.Format;
+import io.quarkus.maven.config.doc.generator.Formatter;
+import io.quarkus.qute.Engine;
+import io.quarkus.qute.ReflectionValueResolver;
+import io.quarkus.qute.UserTagSectionHelper;
+import io.quarkus.qute.ValueResolver;
+
+@Mojo(name = "generate-config-doc", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true)
+public class GenerateConfigDocMojo extends AbstractMojo {
+
+ private static final String TARGET = "target";
+
+ private static final String ADOC_SUFFIX = ".adoc";
+ private static final String CONFIG_ROOT_FILE_FORMAT = "%s_%s.%s";
+ private static final String EXTENSION_FILE_FORMAT = "%s.%s";
+ private static final String ALL_CONFIG_FILE_FORMAT = "quarkus-all-config.%s";
+
+ @Parameter(defaultValue = "${session}", readonly = true)
+ private MavenSession mavenSession;
+
+ // if this is switched to something else at some point, GenerateAsciidocMojo will need to be adapted
+ @Parameter(defaultValue = "asciidoc")
+ private String format;
+
+ @Parameter(defaultValue = Format.DEFAULT_THEME)
+ private String theme;
+
+ @Parameter
+ private File scanDirectory;
+
+ @Parameter(defaultValue = "${project.build.directory}/quarkus-generated-doc/config", required = true)
+ private File targetDirectory;
+
+ @Parameter(defaultValue = "false")
+ private boolean generateAllConfig;
+
+ @Parameter(defaultValue = "false")
+ private boolean enableEnumTooltips;
+
+ @Parameter(defaultValue = "false")
+ private boolean skip;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ if (skip) {
+ return;
+ }
+
+ // I was unable to find an easy way to get the root directory of the project
+ Path resolvedScanDirectory = scanDirectory != null ? scanDirectory.toPath()
+ : mavenSession.getCurrentProject().getBasedir().toPath().getParent();
+ Path resolvedTargetDirectory = targetDirectory.toPath();
+ initTargetDirectory(resolvedTargetDirectory);
+
+ List targetDirectories = findTargetDirectories(resolvedScanDirectory);
+
+ JavadocRepository javadocRepository = JavadocMerger.mergeJavadocElements(targetDirectories);
+ MergedModel mergedModel = ModelMerger.mergeModel(javadocRepository, targetDirectories);
+
+ Format normalizedFormat = Format.normalizeFormat(format);
+
+ String normalizedTheme = normalizedFormat.normalizeTheme(theme);
+ Formatter formatter = Formatter.getFormatter(javadocRepository, enableEnumTooltips, normalizedFormat);
+ Engine quteEngine = initializeQuteEngine(formatter, normalizedFormat, normalizedTheme);
+
+ // we generate a file per extension + top level prefix
+ for (Entry> extensionConfigRootsEntry : mergedModel.getConfigRoots()
+ .entrySet()) {
+ Extension extension = extensionConfigRootsEntry.getKey();
+
+ Path configRootPath = null;
+
+ for (Entry configRootEntry : extensionConfigRootsEntry.getValue().entrySet()) {
+ String topLevelPrefix = configRootEntry.getKey().topLevelPrefix();
+ ConfigRoot configRoot = configRootEntry.getValue();
+
+ // here we generate a file even if there are no items as it's used for the Reactive Oracle SQL client
+
+ configRootPath = resolvedTargetDirectory.resolve(String.format(CONFIG_ROOT_FILE_FORMAT,
+ extension.artifactId(), topLevelPrefix, normalizedFormat.getExtension()));
+ String summaryTableId = formatter
+ .toAnchor(extension.artifactId() + "_" + topLevelPrefix);
+
+ try {
+ Files.writeString(configRootPath,
+ generateConfigReference(quteEngine, summaryTableId, extension, configRoot, "", true));
+ } catch (Exception e) {
+ throw new MojoExecutionException("Unable to render config roots for top level prefix: " + topLevelPrefix
+ + " in extension: " + extension, e);
+ }
+ }
+
+ // if we have only one top level prefix, we copy the generated file to a file named after the extension
+ // for simplicity's sake
+ if (extensionConfigRootsEntry.getValue().size() == 1 && configRootPath != null) {
+ Path extensionPath = resolvedTargetDirectory.resolve(String.format(EXTENSION_FILE_FORMAT,
+ extension.artifactId(), normalizedFormat.getExtension()));
+
+ try {
+ Files.copy(configRootPath, extensionPath, StandardCopyOption.REPLACE_EXISTING);
+ } catch (Exception e) {
+ throw new MojoExecutionException("Unable to copy extension file for: " + extension, e);
+ }
+ }
+ }
+
+ // we generate the config roots that are saved in a specific file
+ for (Entry specificFileConfigRootEntry : mergedModel.getConfigRootsInSpecificFile().entrySet()) {
+ String annotationFileName = specificFileConfigRootEntry.getKey();
+ ConfigRoot configRoot = specificFileConfigRootEntry.getValue();
+ Extension extension = configRoot.getExtension();
+
+ if (configRoot.getNonDeprecatedItems().isEmpty()) {
+ continue;
+ }
+
+ String normalizedFileName = stripAdocSuffix(annotationFileName);
+ String fileName = normalizedFileName + "." + normalizedFormat.getExtension();
+
+ Path configRootPath = resolvedTargetDirectory.resolve(fileName);
+ String summaryTableId = formatter.toAnchor(normalizedFileName);
+
+ try {
+ Files.writeString(configRootPath,
+ generateConfigReference(quteEngine, summaryTableId, extension, configRoot, "", true));
+ } catch (Exception e) {
+ throw new MojoExecutionException("Unable to render config roots for specific file: " + fileName
+ + " in extension: " + extension, e);
+ }
+ }
+
+ // we generate files for generated sections
+ for (Entry> extensionConfigSectionsEntry : mergedModel.getGeneratedConfigSections()
+ .entrySet()) {
+ Extension extension = extensionConfigSectionsEntry.getKey();
+
+ for (ConfigSection generatedConfigSection : extensionConfigSectionsEntry.getValue()) {
+ if (generatedConfigSection.getNonDeprecatedItems().isEmpty()) {
+ continue;
+ }
+
+ Path configSectionPath = resolvedTargetDirectory.resolve(String.format(CONFIG_ROOT_FILE_FORMAT,
+ extension.artifactId(), cleanSectionPath(generatedConfigSection.getPath().property()),
+ normalizedFormat.getExtension()));
+ String summaryTableId = formatter
+ .toAnchor(extension.artifactId() + "_" + generatedConfigSection.getPath().property());
+
+ try {
+ Files.writeString(configSectionPath,
+ generateConfigReference(quteEngine, summaryTableId, extension, generatedConfigSection,
+ "_" + generatedConfigSection.getPath().property(), false));
+ } catch (Exception e) {
+ throw new MojoExecutionException(
+ "Unable to render config section for section: " + generatedConfigSection.getPath().property()
+ + " in extension: " + extension,
+ e);
+ }
+ }
+ }
+
+ if (generateAllConfig) {
+ // we generate the file centralizing all the config properties
+ try {
+ Path allConfigPath = resolvedTargetDirectory.resolve(String.format(ALL_CONFIG_FILE_FORMAT,
+ normalizedFormat.getExtension()));
+
+ Files.writeString(allConfigPath, generateAllConfig(quteEngine, mergedModel.getConfigRoots()));
+ } catch (Exception e) {
+ throw new MojoExecutionException("Unable to render all config", e);
+ }
+ }
+ }
+
+ private static String generateConfigReference(Engine quteEngine, String summaryTableId, Extension extension,
+ ConfigItemCollection configItemCollection, String additionalAnchorPrefix, boolean searchable) {
+ return quteEngine.getTemplate("configReference")
+ .data("extension", extension)
+ .data("configItemCollection", configItemCollection)
+ .data("searchable", searchable)
+ .data("summaryTableId", summaryTableId)
+ .data("additionalAnchorPrefix", additionalAnchorPrefix)
+ .data("includeDurationNote", configItemCollection.hasDurationType())
+ .data("includeMemorySizeNote", configItemCollection.hasMemorySizeType())
+ .render();
+ }
+
+ private static String generateAllConfig(Engine quteEngine,
+ Map> configRootsByExtensions) {
+ return quteEngine.getTemplate("allConfig")
+ .data("configRootsByExtensions", configRootsByExtensions)
+ .data("searchable", true)
+ .data("summaryTableId", "all-config")
+ .data("additionalAnchorPrefix", "")
+ .data("includeDurationNote", true)
+ .data("includeMemorySizeNote", true)
+ .render();
+ }
+
+ private static void initTargetDirectory(Path resolvedTargetDirectory) throws MojoExecutionException {
+ try {
+ Files.createDirectories(resolvedTargetDirectory);
+ } catch (IOException e) {
+ throw new MojoExecutionException("Unable to create directory: " + resolvedTargetDirectory, e);
+ }
+ }
+
+ private static List findTargetDirectories(Path scanDirectory) throws MojoExecutionException {
+ try {
+ List targets = new ArrayList<>();
+
+ Files.walkFileTree(scanDirectory, new SimpleFileVisitor<>() {
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+ if (dir.endsWith(TARGET)) {
+ targets.add(dir);
+
+ // a target directory can contain target directories for test projects
+ // so let's make sure we ignore whatever is nested in a target
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+
+ return FileVisitResult.CONTINUE;
+ }
+ });
+
+ // Make sure we are deterministic
+ Collections.sort(targets);
+
+ return targets;
+ } catch (IOException e) {
+ throw new MojoExecutionException("Unable to collect the target directories", e);
+ }
+ }
+
+ private static Engine initializeQuteEngine(Formatter formatter, Format format, String theme) {
+ Engine engine = Engine.builder()
+ .addDefaults()
+ .addSectionHelper(new UserTagSectionHelper.Factory("configProperty", "configProperty"))
+ .addSectionHelper(new UserTagSectionHelper.Factory("configSection", "configSection"))
+ .addSectionHelper(new UserTagSectionHelper.Factory("envVar", "envVar"))
+ .addSectionHelper(new UserTagSectionHelper.Factory("durationNote", "durationNote"))
+ .addSectionHelper(new UserTagSectionHelper.Factory("memorySizeNote", "memorySizeNote"))
+ .addValueResolver(new ReflectionValueResolver())
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(String.class)
+ .applyToName("escapeCellContent")
+ .applyToNoParameters()
+ .resolveSync(ctx -> formatter.escapeCellContent((String) ctx.getBase()))
+ .build())
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(String.class)
+ .applyToName("toAnchor")
+ .applyToNoParameters()
+ .resolveSync(ctx -> formatter.toAnchor((String) ctx.getBase()))
+ .build())
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(ConfigRootKey.class)
+ .applyToName("displayConfigRootDescription")
+ .applyToParameters(1)
+ .resolveSync(ctx -> formatter
+ .displayConfigRootDescription((ConfigRootKey) ctx.getBase(),
+ (int) ctx.evaluate(ctx.getParams().get(0)).toCompletableFuture().join()))
+ .build())
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(ConfigProperty.class)
+ .applyToName("toAnchor")
+ .applyToParameters(2)
+ .resolveSync(ctx -> formatter
+ .toAnchor(((Extension) ctx.evaluate(ctx.getParams().get(0)).toCompletableFuture().join())
+ .artifactId() +
+ // the additional suffix
+ ctx.evaluate(ctx.getParams().get(1)).toCompletableFuture().join() +
+ "_" + ((ConfigProperty) ctx.getBase()).getPath().property()))
+ .build())
+ // we need a different anchor for sections as otherwise we can have a conflict
+ // (typically when you have an `enabled` property with parent name just under the section level)
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(ConfigSection.class)
+ .applyToName("toAnchor")
+ .applyToParameters(2)
+ .resolveSync(ctx -> formatter
+ .toAnchor(((Extension) ctx.evaluate(ctx.getParams().get(0)).toCompletableFuture().join())
+ .artifactId() +
+ // the additional suffix
+ ctx.evaluate(ctx.getParams().get(1)).toCompletableFuture().join() +
+ "_section_" + ((ConfigSection) ctx.getBase()).getPath().property()))
+ .build())
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(ConfigProperty.class)
+ .applyToName("formatTypeDescription")
+ .applyToNoParameters()
+ .resolveSync(ctx -> formatter.formatTypeDescription((ConfigProperty) ctx.getBase()))
+ .build())
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(ConfigProperty.class)
+ .applyToName("formatDescription")
+ .applyToNoParameters()
+ .resolveSync(ctx -> formatter.formatDescription((ConfigProperty) ctx.getBase()))
+ .build())
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(ConfigProperty.class)
+ .applyToName("formatDefaultValue")
+ .applyToNoParameters()
+ .resolveSync(ctx -> formatter.formatDefaultValue((ConfigProperty) ctx.getBase()))
+ .build())
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(ConfigSection.class)
+ .applyToName("formatTitle")
+ .applyToNoParameters()
+ .resolveSync(ctx -> formatter.formatSectionTitle((ConfigSection) ctx.getBase()))
+ .build())
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(ConfigSection.class)
+ .applyToName("adjustedLevel")
+ .applyToParameters(1)
+ .resolveSync(ctx -> formatter
+ .adjustedLevel((ConfigSection) ctx.getBase(),
+ (boolean) ctx.evaluate(ctx.getParams().get(0)).toCompletableFuture().join()))
+ .build())
+ .addValueResolver(ValueResolver.builder()
+ .applyToBaseClass(Extension.class)
+ .applyToName("formatName")
+ .applyToNoParameters()
+ .resolveSync(ctx -> formatter.formatName((Extension) ctx.getBase()))
+ .build())
+ .build();
+
+ engine.putTemplate("configReference",
+ engine.parse(getTemplate("templates", format, theme, "configReference", false)));
+ engine.putTemplate("allConfig",
+ engine.parse(getTemplate("templates", format, theme, "allConfig", false)));
+ engine.putTemplate("configProperty",
+ engine.parse(getTemplate("templates", format, theme, "configProperty", true)));
+ engine.putTemplate("configSection",
+ engine.parse(getTemplate("templates", format, theme, "configSection", true)));
+ engine.putTemplate("envVar",
+ engine.parse(getTemplate("templates", format, theme, "envVar", true)));
+ engine.putTemplate("durationNote",
+ engine.parse(getTemplate("templates", format, theme, "durationNote", true)));
+ engine.putTemplate("memorySizeNote",
+ engine.parse(getTemplate("templates", format, theme, "memorySizeNote", true)));
+
+ return engine;
+ }
+
+ private static String getTemplate(String root, Format format, String theme, String template, boolean tag) {
+ List candidates = new ArrayList<>();
+ candidates.add(
+ root + "/" + format + "/" + theme + "/" + (tag ? "tags/" : "") + template + ".qute." + format.getExtension());
+ if (!Format.DEFAULT_THEME.equals(theme)) {
+ candidates
+ .add(root + "/" + format + "/" + Format.DEFAULT_THEME + "/" + (tag ? "tags/" : "") + template + ".qute."
+ + format.getExtension());
+ }
+
+ InputStream is = null;
+ ;
+ for (String candidate : candidates) {
+ is = Thread.currentThread().getContextClassLoader().getResourceAsStream(candidate);
+ if (is != null) {
+ break;
+ }
+ }
+
+ if (is == null) {
+ throw new IllegalArgumentException("Unable to find a template for these candidates " + candidates);
+ }
+
+ try {
+ return new String(is.readAllBytes(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Unable to read the template: " + template, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ throw new UncheckedIOException("Unable to close InputStream for template: " + template, e);
+ }
+ }
+ }
+
+ /**
+ * A section path can contain quotes when being inside a Map.
+ *
+ * While not very common, we sometimes have to generate a section inside a Map
+ * e.g. for the XDS config of the gRPC client.
+ */
+ private static String cleanSectionPath(String sectionPath) {
+ return sectionPath.replace('"', '-');
+ }
+
+ private String stripAdocSuffix(String fileName) {
+ return fileName.substring(0, fileName.length() - ADOC_SUFFIX.length());
+ }
+}
diff --git a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/AsciidocFormatter.java b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/AbstractFormatter.java
similarity index 75%
rename from devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/AsciidocFormatter.java
rename to devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/AbstractFormatter.java
index 1617471b3260b..49384bb924e52 100644
--- a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/AsciidocFormatter.java
+++ b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/AbstractFormatter.java
@@ -1,32 +1,32 @@
-package io.quarkus.maven.config.doc;
+package io.quarkus.maven.config.doc.generator;
import java.text.Normalizer;
import java.time.Duration;
import java.util.Optional;
import java.util.stream.Collectors;
+import io.quarkus.annotation.processor.documentation.config.formatter.JavadocTransformer;
import io.quarkus.annotation.processor.documentation.config.merger.JavadocRepository;
import io.quarkus.annotation.processor.documentation.config.merger.MergedModel.ConfigRootKey;
import io.quarkus.annotation.processor.documentation.config.model.ConfigProperty;
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.JavadocElements.JavadocElement;
+import io.quarkus.annotation.processor.documentation.config.model.JavadocFormat;
import io.quarkus.annotation.processor.documentation.config.util.Types;
-final class AsciidocFormatter {
+abstract class AbstractFormatter implements Formatter {
- private static final String TOOLTIP_MACRO = "tooltip:%s[%s]";
- private static final String MORE_INFO_ABOUT_TYPE_FORMAT = "link:#%s[icon:question-circle[title=More information about the %s format]]";
+ protected final JavadocRepository javadocRepository;
+ protected final boolean enableEnumTooltips;
- private final JavadocRepository javadocRepository;
- private final boolean enableEnumTooltips;
-
- AsciidocFormatter(JavadocRepository javadocRepository, boolean enableEnumTooltips) {
+ AbstractFormatter(JavadocRepository javadocRepository, boolean enableEnumTooltips) {
this.javadocRepository = javadocRepository;
this.enableEnumTooltips = enableEnumTooltips;
}
- boolean displayConfigRootDescription(ConfigRootKey configRootKey, int mapSize) {
+ @Override
+ public boolean displayConfigRootDescription(ConfigRootKey configRootKey, int mapSize) {
if (mapSize <= 1) {
return false;
}
@@ -34,7 +34,8 @@ boolean displayConfigRootDescription(ConfigRootKey configRootKey, int mapSize) {
return configRootKey.description() != null;
}
- String formatDescription(ConfigProperty configProperty) {
+ @Override
+ public String formatDescription(ConfigProperty configProperty) {
Optional javadocElement = javadocRepository.getElement(configProperty.getSourceClass(),
configProperty.getSourceName());
@@ -42,7 +43,8 @@ String formatDescription(ConfigProperty configProperty) {
return null;
}
- String description = javadocElement.get().description();
+ String description = JavadocTransformer.transform(javadocElement.get().description(), javadocElement.get().format(),
+ javadocFormat());
if (description == null || description.isBlank()) {
return null;
}
@@ -50,7 +52,8 @@ String formatDescription(ConfigProperty configProperty) {
return description + "\n\n";
}
- String formatTypeDescription(ConfigProperty configProperty) {
+ @Override
+ public String formatTypeDescription(ConfigProperty configProperty) {
String typeContent = "";
if (configProperty.isEnum() && enableEnumTooltips) {
@@ -62,14 +65,14 @@ String formatTypeDescription(ConfigProperty configProperty) {
return "`" + e.getValue().configValue() + "`";
}
- return String.format(TOOLTIP_MACRO, e.getValue().configValue(),
- cleanTooltipContent(javadocElement.get().description()));
+ return tooltip(e.getValue().configValue(), JavadocTransformer
+ .transform(javadocElement.get().description(), javadocElement.get().format(), javadocFormat()));
})
.collect(Collectors.joining(", "));
} else {
typeContent = configProperty.getTypeDescription();
if (configProperty.getJavadocSiteLink() != null) {
- typeContent = String.format("link:%s[%s]", configProperty.getJavadocSiteLink(), typeContent);
+ typeContent = link(configProperty.getJavadocSiteLink(), typeContent);
}
}
if (configProperty.isList()) {
@@ -77,17 +80,16 @@ String formatTypeDescription(ConfigProperty configProperty) {
}
if (Duration.class.getName().equals(configProperty.getType())) {
- typeContent += " " + String.format(MORE_INFO_ABOUT_TYPE_FORMAT,
- "duration-note-anchor-{summaryTableId}", Duration.class.getSimpleName());
+ typeContent += " " + moreInformationAboutType("duration-note-anchor", Duration.class.getSimpleName());
} else if (Types.MEMORY_SIZE_TYPE.equals(configProperty.getType())) {
- typeContent += " " + String.format(MORE_INFO_ABOUT_TYPE_FORMAT,
- "memory-size-note-anchor-{summaryTableId}", "MemorySize");
+ typeContent += " " + moreInformationAboutType("memory-size-note-anchor", "MemorySize");
}
return typeContent;
}
- String formatDefaultValue(ConfigProperty configProperty) {
+ @Override
+ public String formatDefaultValue(ConfigProperty configProperty) {
String defaultValue = configProperty.getDefaultValue();
if (defaultValue == null) {
@@ -105,7 +107,7 @@ String formatDefaultValue(ConfigProperty configProperty) {
enumConstant.get());
if (javadocElement.isPresent()) {
- return String.format(TOOLTIP_MACRO, defaultValue, cleanTooltipContent(javadocElement.get().description()));
+ return tooltip(defaultValue, javadocElement.get().description());
}
}
}
@@ -113,7 +115,8 @@ String formatDefaultValue(ConfigProperty configProperty) {
return "`" + defaultValue + "`";
}
- int adjustedLevel(ConfigSection configSection, boolean multiRoot) {
+ @Override
+ public int adjustedLevel(ConfigSection configSection, boolean multiRoot) {
if (multiRoot) {
return configSection.getLevel() + 1;
}
@@ -121,7 +124,8 @@ int adjustedLevel(ConfigSection configSection, boolean multiRoot) {
return configSection.getLevel();
}
- String escapeCellContent(String value) {
+ @Override
+ public String escapeCellContent(String value) {
if (value == null) {
return null;
}
@@ -129,7 +133,8 @@ String escapeCellContent(String value) {
return value.replace("|", "\\|");
}
- String toAnchor(String value) {
+ @Override
+ public String toAnchor(String value) {
// remove accents
value = Normalizer.normalize(value, Normalizer.Form.NFKC)
.replaceAll("[àáâãäåāąă]", "a")
@@ -185,7 +190,8 @@ String toAnchor(String value) {
return value.toLowerCase();
}
- String formatSectionTitle(ConfigSection configSection) {
+ @Override
+ public String formatSectionTitle(ConfigSection configSection) {
Optional javadocElement = javadocRepository.getElement(configSection.getSourceClass(),
configSection.getSourceName());
@@ -194,7 +200,8 @@ String formatSectionTitle(ConfigSection configSection) {
"Couldn't find section title for: " + configSection.getSourceClass() + "#" + configSection.getSourceName());
}
- String javadoc = javadocElement.get().description();
+ String javadoc = JavadocTransformer.transform(javadocElement.get().description(), javadocElement.get().format(),
+ javadocFormat());
if (javadoc == null || javadoc.isBlank()) {
throw new IllegalStateException(
"Couldn't find section title for: " + configSection.getSourceClass() + "#" + configSection.getSourceName());
@@ -203,7 +210,8 @@ String formatSectionTitle(ConfigSection configSection) {
return trimFinalDot(javadoc);
}
- String formatName(Extension extension) {
+ @Override
+ public String formatName(Extension extension) {
if (extension.name() == null) {
return extension.artifactId();
}
@@ -226,14 +234,11 @@ private static String trimFinalDot(String javadoc) {
return javadoc.substring(0, dotIndex);
}
- /**
- * Note that this is extremely brittle. Apparently, colons breaks the tooltips but if escaped with \, the \ appears in the
- * output.
- *
- * We should probably have some warnings/errors as to what is accepted in enum Javadoc.
- */
- private String cleanTooltipContent(String tooltipContent) {
- return tooltipContent.replace("
", "").replace("
", "").replace("\n+\n", " ").replace("\n", " ")
- .replace(":", "\\:").replace("[", "\\]").replace("]", "\\]");
- }
+ protected abstract JavadocFormat javadocFormat();
+
+ protected abstract String moreInformationAboutType(String anchorRoot, String type);
+
+ protected abstract String link(String href, String description);
+
+ protected abstract String tooltip(String value, String javadocDescription);
}
diff --git a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/AsciidocFormatter.java b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/AsciidocFormatter.java
new file mode 100644
index 0000000000000..dab5e3d1aa1d3
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/AsciidocFormatter.java
@@ -0,0 +1,42 @@
+package io.quarkus.maven.config.doc.generator;
+
+import io.quarkus.annotation.processor.documentation.config.merger.JavadocRepository;
+import io.quarkus.annotation.processor.documentation.config.model.JavadocFormat;
+
+final class AsciidocFormatter extends AbstractFormatter {
+
+ private static final String TOOLTIP_MACRO = "tooltip:%s[%s]";
+ private static final String MORE_INFO_ABOUT_TYPE_FORMAT = "link:#%s[icon:question-circle[title=More information about the %s format]]";
+
+ AsciidocFormatter(JavadocRepository javadocRepository, boolean enableEnumTooltips) {
+ super(javadocRepository, enableEnumTooltips);
+ }
+
+ @Override
+ protected JavadocFormat javadocFormat() {
+ return JavadocFormat.ASCIIDOC;
+ }
+
+ protected String moreInformationAboutType(String anchorRoot, String type) {
+ return String.format(MORE_INFO_ABOUT_TYPE_FORMAT, anchorRoot + "-{summaryTableId}", type);
+ }
+
+ protected String tooltip(String value, String javadocDescription) {
+ return String.format(TOOLTIP_MACRO, value, cleanTooltipContent(javadocDescription));
+ }
+
+ /**
+ * Note that this is extremely brittle. Apparently, colons breaks the tooltips but if escaped with \, the \ appears in the
+ * output.
+ *
+ * We should probably have some warnings/errors as to what is accepted in enum Javadoc.
+ */
+ private String cleanTooltipContent(String tooltipContent) {
+ return tooltipContent.replace("
", "").replace("
", "").replace("\n+\n", " ").replace("\n", " ")
+ .replace(":", "\\:").replace("[", "\\]").replace("]", "\\]");
+ }
+
+ protected String link(String href, String description) {
+ return String.format("link:%s[%s]", href, description);
+ }
+}
diff --git a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/Format.java b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/Format.java
new file mode 100644
index 0000000000000..905d8fa14b74e
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/Format.java
@@ -0,0 +1,35 @@
+package io.quarkus.maven.config.doc.generator;
+
+import java.util.Set;
+
+public enum Format {
+
+ asciidoc("adoc", Set.of(Format.DEFAULT_THEME)),
+ markdown("md", Set.of(Format.DEFAULT_THEME, "github"));
+
+ public static final String DEFAULT_THEME = "default";
+
+ private final String extension;
+ private final Set supportedThemes;
+
+ private Format(String extension, Set supportedThemes) {
+ this.extension = extension;
+ this.supportedThemes = supportedThemes;
+ }
+
+ public String getExtension() {
+ return extension;
+ }
+
+ public String normalizeTheme(String theme) {
+ theme = theme.trim();
+
+ return supportedThemes.contains(theme) ? theme : DEFAULT_THEME;
+ }
+
+ public static Format normalizeFormat(String format) {
+ format = format.trim();
+
+ return Format.valueOf(format);
+ }
+}
diff --git a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/Formatter.java b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/Formatter.java
new file mode 100644
index 0000000000000..3552ab26f6e20
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/Formatter.java
@@ -0,0 +1,39 @@
+package io.quarkus.maven.config.doc.generator;
+
+import io.quarkus.annotation.processor.documentation.config.merger.JavadocRepository;
+import io.quarkus.annotation.processor.documentation.config.merger.MergedModel.ConfigRootKey;
+import io.quarkus.annotation.processor.documentation.config.model.ConfigProperty;
+import io.quarkus.annotation.processor.documentation.config.model.ConfigSection;
+import io.quarkus.annotation.processor.documentation.config.model.Extension;
+
+public interface Formatter {
+
+ boolean displayConfigRootDescription(ConfigRootKey configRootKey, int mapSize);
+
+ String formatDescription(ConfigProperty configProperty);
+
+ String formatTypeDescription(ConfigProperty configProperty);
+
+ String formatDefaultValue(ConfigProperty configProperty);
+
+ int adjustedLevel(ConfigSection configSection, boolean multiRoot);
+
+ String escapeCellContent(String value);
+
+ String toAnchor(String value);
+
+ String formatSectionTitle(ConfigSection configSection);
+
+ String formatName(Extension extension);
+
+ static Formatter getFormatter(JavadocRepository javadocRepository, boolean enableEnumTooltips, Format format) {
+ switch (format) {
+ case asciidoc:
+ return new AsciidocFormatter(javadocRepository, enableEnumTooltips);
+ case markdown:
+ return new MarkdownFormatter(javadocRepository, enableEnumTooltips);
+ default:
+ throw new IllegalArgumentException("Unsupported format: " + format);
+ }
+ }
+}
\ No newline at end of file
diff --git a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/MarkdownFormatter.java b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/MarkdownFormatter.java
new file mode 100644
index 0000000000000..425e7481134fe
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/MarkdownFormatter.java
@@ -0,0 +1,48 @@
+package io.quarkus.maven.config.doc.generator;
+
+import io.quarkus.annotation.processor.documentation.config.merger.JavadocRepository;
+import io.quarkus.annotation.processor.documentation.config.model.ConfigSection;
+import io.quarkus.annotation.processor.documentation.config.model.JavadocFormat;
+
+final class MarkdownFormatter extends AbstractFormatter {
+
+ private static final String MORE_INFO_ABOUT_TYPE_FORMAT = "[🛈](#%s)";
+
+ MarkdownFormatter(JavadocRepository javadocRepository, boolean enableEnumTooltips) {
+ super(javadocRepository, enableEnumTooltips);
+ }
+
+ @Override
+ protected JavadocFormat javadocFormat() {
+ return JavadocFormat.MARKDOWN;
+ }
+
+ @Override
+ public String formatSectionTitle(ConfigSection configSection) {
+ // markdown only has 6 heading levels
+ int headingLevel = Math.min(6, 2 + configSection.getLevel());
+ return "#".repeat(headingLevel) + " " + super.formatSectionTitle(configSection);
+ }
+
+ @Override
+ public String escapeCellContent(String value) {
+ String cellContent = super.escapeCellContent(value);
+ return cellContent == null ? null : cellContent.replace("\n\n", " ").replace("\n", " ");
+ }
+
+ @Override
+ protected String moreInformationAboutType(String anchorRoot, String type) {
+ return MORE_INFO_ABOUT_TYPE_FORMAT.formatted(anchorRoot);
+ }
+
+ @Override
+ protected String link(String href, String description) {
+ return String.format("[%2$s](%1$s)", href, description);
+ }
+
+ @Override
+ protected String tooltip(String value, String javadocDescription) {
+ // we don't have tooltip support in Markdown
+ return "`" + value + "`";
+ }
+}
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/META-INF/plexus/components.xml b/devtools/config-doc-maven-plugin/src/main/resources/META-INF/plexus/components.xml
index 615f650ef1fa4..4d890ae972d01 100644
--- a/devtools/config-doc-maven-plugin/src/main/resources/META-INF/plexus/components.xml
+++ b/devtools/config-doc-maven-plugin/src/main/resources/META-INF/plexus/components.xml
@@ -11,7 +11,7 @@
- io.quarkus:quarkus-config-doc-maven-plugin:${project.version}:generate-asciidoc
+ io.quarkus:quarkus-config-doc-maven-plugin:${project.version}:generate-config-doc
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/allConfig.qute.adoc b/devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/allConfig.qute.adoc
similarity index 100%
rename from devtools/config-doc-maven-plugin/src/main/resources/templates/allConfig.qute.adoc
rename to devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/allConfig.qute.adoc
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/configReference.qute.adoc b/devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/configReference.qute.adoc
similarity index 94%
rename from devtools/config-doc-maven-plugin/src/main/resources/templates/configReference.qute.adoc
rename to devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/configReference.qute.adoc
index c694aeaf7a289..c5151982992c2 100644
--- a/devtools/config-doc-maven-plugin/src/main/resources/templates/configReference.qute.adoc
+++ b/devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/configReference.qute.adoc
@@ -16,6 +16,9 @@ h|Default
{#configProperty configProperty=item extension=extension additionalAnchorPrefix=additionalAnchorPrefix /}
{/if}
+{#else}
+3+|No configuration properties found.
+
{/for}
|===
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/tags/configProperty.qute.adoc b/devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/tags/configProperty.qute.adoc
similarity index 100%
rename from devtools/config-doc-maven-plugin/src/main/resources/templates/tags/configProperty.qute.adoc
rename to devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/tags/configProperty.qute.adoc
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/tags/configSection.qute.adoc b/devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/tags/configSection.qute.adoc
similarity index 100%
rename from devtools/config-doc-maven-plugin/src/main/resources/templates/tags/configSection.qute.adoc
rename to devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/tags/configSection.qute.adoc
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/tags/durationNote.qute.adoc b/devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/tags/durationNote.qute.adoc
similarity index 100%
rename from devtools/config-doc-maven-plugin/src/main/resources/templates/tags/durationNote.qute.adoc
rename to devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/tags/durationNote.qute.adoc
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/tags/envVar.qute.adoc b/devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/tags/envVar.qute.adoc
similarity index 100%
rename from devtools/config-doc-maven-plugin/src/main/resources/templates/tags/envVar.qute.adoc
rename to devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/tags/envVar.qute.adoc
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/tags/memorySizeNote.qute.adoc b/devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/tags/memorySizeNote.qute.adoc
similarity index 100%
rename from devtools/config-doc-maven-plugin/src/main/resources/templates/tags/memorySizeNote.qute.adoc
rename to devtools/config-doc-maven-plugin/src/main/resources/templates/asciidoc/default/tags/memorySizeNote.qute.adoc
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/allConfig.qute.md b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/allConfig.qute.md
new file mode 100644
index 0000000000000..5b89612f0fa23
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/allConfig.qute.md
@@ -0,0 +1,27 @@
+🔒: Configuration property fixed at build time - All other configuration properties are overridable at runtime
+
+{#for extensionConfigRootsEntry in configRootsByExtensions}
+
+# {extensionConfigRootsEntry.key.formatName}
+
+| Configuration property | Type | Default |
+|------------------------|------|---------|
+{#for configRoot in extensionConfigRootsEntry.value.values}
+{#for item in configRoot.items}
+{#if !item.deprecated}
+{#if !item.isSection}
+{#configProperty configProperty=item extension=extensionConfigRootsEntry.key additionalAnchorPrefix=additionalAnchorPrefix /}
+{#else}
+{#configSection configSection=item extension=extensionConfigRootsEntry.key additionalAnchorPrefix=additionalAnchorPrefix /}
+{/if}
+{/if}
+{/for}
+{/for}
+{/for}
+
+{#if includeDurationNote}
+{#durationNote summaryTableId /}
+{/if}
+{#if includeMemorySizeNote}
+{#memorySizeNote summaryTableId /}
+{/if}
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/configReference.qute.md b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/configReference.qute.md
new file mode 100644
index 0000000000000..3c5237e397e66
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/configReference.qute.md
@@ -0,0 +1,20 @@
+🔒: Configuration property fixed at build time - All other configuration properties are overridable at runtime
+
+{#if configItemCollection.nonDeprecatedProperties}
+| Configuration property | Type | Default |
+|------------------------|------|---------|
+{#for property in configItemCollection.nonDeprecatedProperties}
+{#configProperty configProperty=property extension=extension additionalAnchorPrefix=additionalAnchorPrefix /}
+{/for}
+{/if}
+{#for section in configItemCollection.nonDeprecatedSections}
+
+{#configSection configSection=section extension=extension additionalAnchorPrefix=additionalAnchorPrefix displayConfigRootDescription=false /}
+{/for}
+
+{#if includeDurationNote}
+{#durationNote summaryTableId /}
+{/if}
+{#if includeMemorySizeNote}
+{#memorySizeNote summaryTableId /}
+{/if}
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/configProperty.qute.md b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/configProperty.qute.md
new file mode 100644
index 0000000000000..ce0462b7b2ce1
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/configProperty.qute.md
@@ -0,0 +1 @@
+| {#if configProperty.phase.fixedAtBuildTime}🔒 {/if}`{configProperty.path.property}`{#for additionalPath in configProperty.additionalPaths} `{additionalPath.property}`{/for} {configProperty.formatDescription.escapeCellContent.or("")}{#envVar configProperty /} | {configProperty.formatTypeDescription.escapeCellContent.or("")} | {#if configProperty.defaultValue}{configProperty.formatDefaultValue.escapeCellContent}{/if} |
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/configSection.qute.md b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/configSection.qute.md
new file mode 100644
index 0000000000000..69f5a2012af94
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/configSection.qute.md
@@ -0,0 +1,13 @@
+{configSection.formatTitle}
+
+{#if configSection.nonDeprecatedProperties}
+| Configuration property | Type | Default |
+|------------------------|------|---------|
+{#for property in configSection.nonDeprecatedProperties}
+{#configProperty configProperty=property extension=extension additionalAnchorPrefix=additionalAnchorPrefix /}
+{/for}
+{/if}
+{#for subsection in configSection.nonDeprecatedSections}
+
+{#configSection configSection=subsection extension=extension additionalAnchorPrefix=additionalAnchorPrefix /}
+{/for}
\ No newline at end of file
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/durationNote.qute.md b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/durationNote.qute.md
new file mode 100644
index 0000000000000..94d2ce93f735d
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/durationNote.qute.md
@@ -0,0 +1,17 @@
+
+
+> [!NOTE]
+> ### About the Duration format
+>
+> To write duration values, use the standard `java.time.Duration` format.
+> See the [Duration#parse()](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html#parse(java.lang.CharSequence)) Java API documentation] for more information.
+>
+> You can also use a simplified format, starting with a number:
+>
+> * If the value is only a number, it represents time in seconds.
+> * If the value is a number followed by `ms`, it represents time in milliseconds.
+>
+> In other cases, the simplified format is translated to the `java.time.Duration` format for parsing:
+>
+> * If the value is a number followed by `h`, `m`, or `s`, it is prefixed with `PT`.
+> * If the value is a number followed by `d`, it is prefixed with `P`.
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/envVar.qute.md b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/envVar.qute.md
new file mode 100644
index 0000000000000..0037f6eee6545
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/envVar.qute.md
@@ -0,0 +1 @@
+Environment variable: `{configProperty.path.environmentVariable}`
\ No newline at end of file
diff --git a/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/memorySizeNote.qute.md b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/memorySizeNote.qute.md
new file mode 100644
index 0000000000000..f6ce0be9db3ff
--- /dev/null
+++ b/devtools/config-doc-maven-plugin/src/main/resources/templates/markdown/default/tags/memorySizeNote.qute.md
@@ -0,0 +1,8 @@
+
+
+> [!NOTE]
+> ### About the MemorySize format
+>
+> A size configuration option recognizes strings in this format (shown as a regular expression): `[0-9]+[KkMmGgTtPpEeZzYy]?`.
+>
+> If no suffix is given, assume bytes.
diff --git a/docs/pom.xml b/docs/pom.xml
index ee82231a6665a..fe8a3b2c4e450 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -3210,7 +3210,7 @@
generate-config-doc
process-resources
- generate-asciidoc
+ generate-config-doc
${skipDocs}
diff --git a/docs/src/main/asciidoc/amqp-reference.adoc b/docs/src/main/asciidoc/amqp-reference.adoc
index e5bcdce9ecf22..74cc19c2daa6b 100644
--- a/docs/src/main/asciidoc/amqp-reference.adoc
+++ b/docs/src/main/asciidoc/amqp-reference.adoc
@@ -478,6 +478,19 @@ public AmqpClientOptions getNamedOptions() {
}
----
+== TLS Configuration
+
+AMQP 1.0 Messaging extension integrates with the xref:./tls-registry-reference.adoc[Quarkus TLS registry] to configure the Vert.x AMQP client.
+
+To configure the TLS for an AMQP 1.0 channel, you need to provide a named TLS configuration in the `application.properties`:
+
+[source, properties]
+----
+quarkus.tls.your-tls-config.trust-store.pem.certs=ca.crt,ca2.pem
+# ...
+mp.messaging.incoming.prices.tls-configuration-name=your-tls-config
+----
+
== Health reporting
If you use the AMQP connector with the `quarkus-smallrye-health` extension, it contributes to the readiness and liveness probes.
diff --git a/docs/src/main/asciidoc/datasource.adoc b/docs/src/main/asciidoc/datasource.adoc
index 4828026f53e22..70cfc46a743fa 100644
--- a/docs/src/main/asciidoc/datasource.adoc
+++ b/docs/src/main/asciidoc/datasource.adoc
@@ -952,7 +952,7 @@ include::{generated-dir}/config/quarkus-reactive-mssql-client.adoc[opts=optional
==== Reactive Oracle-specific configuration
-At the moment, there are no Oracle-specific configuration properties.
+include::{generated-dir}/config/quarkus-reactive-oracle-client.adoc[opts=optional, leveloffset=+1]
==== Reactive PostgreSQL-specific configuration
diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc
index 0abd8ab7adf6d..52bfa5b39d77d 100644
--- a/docs/src/main/asciidoc/kafka.adoc
+++ b/docs/src/main/asciidoc/kafka.adoc
@@ -2153,6 +2153,36 @@ Update the `oauth.client.id`, `oauth.client.secret` and `oauth.token.endpoint.ur
OAuth authentication works for both JVM and native modes. Since SSL in not enabled by default in native mode, `quarkus.ssl.native=true` must be added to support JaasClientOauthLoginCallbackHandler, which uses SSL. (See the xref:native-and-ssl.adoc[Using SSL with Native Executables] guide for more details.)
+== TLS Configuration
+
+Kafka client extension integrates with the xref:./tls-registry-reference.adoc[Quarkus TLS registry] to configure clients.
+
+To configure the TLS for the default Kafka configuration, you need to provide a named TLS configuration in the `application.properties`:
+
+[source, properties]
+----
+quarkus.tls.your-tls-config.trust-store.pem.certs=target/certs/kafka.crt,target/certs/kafka-ca.crt
+# ...
+kafka.tls-configuration-name=your-tls-config
+# enable ssl security protocol
+kafka.security.protocol=ssl
+----
+
+This will in turn provide the Kafka client with a `ssl.engine.factory.class` implementation.
+
+[IMPORTANT]
+====
+Make sure also to enable the SSL channel security protocol using the `security.protocol` property configured to `SSL` or `SASL_SSL`.
+====
+
+Quarkus Messaging channels can be configured individually to use a specific TLS configuration:
+
+[source, properties]
+----
+mp.messaging.incoming.your-channel.tls-configuration-name=your-tls-config
+mp.messaging.incoming.your-channel.security.protocol=ssl
+----
+
== Testing a Kafka application
=== Testing without a broker
diff --git a/docs/src/main/asciidoc/messaging.adoc b/docs/src/main/asciidoc/messaging.adoc
index 05724027b4a0d..38bd19ddd68c6 100644
--- a/docs/src/main/asciidoc/messaging.adoc
+++ b/docs/src/main/asciidoc/messaging.adoc
@@ -600,6 +600,33 @@ You can disable tracing for a specific channel using the following configuration
mp.messaging.incoming.data.tracing-enabled=false
----
+== TLS Configuration
+
+Some messaging extensions integrate with the xref:./tls-registry-reference.adoc[Quarkus TLS Registry] to configure the underlying client.
+To configure the TLS on a channel, you need to provide the named TLS configuration to the `tls-configuration-name` property:
+
+[source, properties]
+----
+quarkus.tls.my-tls-config.trust-store=truststore.jks
+quarkus.tls.my-tls-config.trust-store-password=secret
+mp.messaging.incoming.my-channel.tls-configuration-name=my-tls-config
+----
+
+Or you can configure it globally on all channels of a connector:
+
+[source, properties]
+----
+mp.messaging.connector.smallrye-pulsar.tls-configuration-name=my-tls-config
+----
+
+Currently, the following messaging extensions support configuration through the Quarkus TLS Registry:
+
+* Kafka: Provides the `ssl.engine.factory.class` property for the Kafka client.
+* Pulsar: Only mTLS authentication is supported.
+* RabbitMQ
+* AMQP 1.0
+* MQTT
+
== Testing
=== Testing with Dev Services
diff --git a/docs/src/main/asciidoc/pulsar.adoc b/docs/src/main/asciidoc/pulsar.adoc
index fa61691f869cc..bf2203449a9a4 100644
--- a/docs/src/main/asciidoc/pulsar.adoc
+++ b/docs/src/main/asciidoc/pulsar.adoc
@@ -1118,6 +1118,23 @@ class PulsarConfig {
}
----
+==== Configuring authentication to Pulsar using mTLS
+
+Pulsar Messaging extension integrates with the xref:./tls-registry-reference.adoc[Quarkus TLS registry] to authenticate clients using mTLS.
+
+To configure the mTLS for a Pulsar channel, you need to provide a named TLS configuration in the `application.properties`:
+
+[source, properties]
+----
+quarkus.tls.my-tls-config.trust-store.p12.path=target/certs/pulsar-client-truststore.p12
+quarkus.tls.my-tls-config.trust-store.p12.password=secret
+quarkus.tls.my-tls-config.key-store.p12.path=target/certs/pulsar-client-keystore.p12
+quarkus.tls.my-tls-config.key-store.p12.password=secret
+
+mp.messaging.incoming.prices.tls-configuration-name=my-tls-config
+----
+
+
==== Configuring access to Datastax Luna Streaming
Luna Streaming is a production-ready distribution of Apache Pulsar, with tools and support from DataStax.
diff --git a/docs/src/main/asciidoc/rabbitmq-reference.adoc b/docs/src/main/asciidoc/rabbitmq-reference.adoc
index a4bf77788ea9e..5b2922ddb55be 100644
--- a/docs/src/main/asciidoc/rabbitmq-reference.adoc
+++ b/docs/src/main/asciidoc/rabbitmq-reference.adoc
@@ -389,6 +389,19 @@ You need to indicate the name of the client using the `client-options-name` attr
mp.messaging.incoming.prices.client-options-name=my-named-options
----
+== TLS Configuration
+
+RabbitMQ Messaging extension integrates with the xref:./tls-registry-reference.adoc[Quarkus TLS registry] to configure the Vert.x RabbitMQ client.
+
+To configure the TLS for a channel, you need to provide a named TLS configuration in the `application.properties`:
+
+[source, properties]
+----
+quarkus.tls.your-tls-config.trust-store.pem.certs=ca.crt,ca2.pem
+# ...
+mp.messaging.incoming.prices.tls-configuration-name=your-tls-config
+----
+
== Health reporting
If you use the RabbitMQ connector with the `quarkus-smallrye-health` extension, it contributes to the readiness and liveness probes.
diff --git a/docs/src/main/asciidoc/reactive-sql-clients.adoc b/docs/src/main/asciidoc/reactive-sql-clients.adoc
index 4ce05554a1b89..5b64977d6a91c 100644
--- a/docs/src/main/asciidoc/reactive-sql-clients.adoc
+++ b/docs/src/main/asciidoc/reactive-sql-clients.adoc
@@ -922,7 +922,7 @@ include::{generated-dir}/config/quarkus-reactive-mssql-client.adoc[opts=optional
=== Oracle
-At the moment, there are no Oracle-specific configuration properties.
+include::{generated-dir}/config/quarkus-reactive-oracle-client.adoc[opts=optional, leveloffset=+1]
=== PostgreSQL
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
index f6853f9f6c163..6e1969a7caf00 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
@@ -335,7 +335,7 @@ public boolean test(BeanInfo bean) {
builder.setTransformPrivateInjectedFields(arcConfig.transformPrivateInjectedFields);
builder.setFailOnInterceptedPrivateMethod(arcConfig.failOnInterceptedPrivateMethod);
builder.setJtaCapabilities(capabilities.isPresent(Capability.TRANSACTIONS));
- builder.setGenerateSources(BootstrapDebug.DEBUG_SOURCES_DIR != null);
+ builder.setGenerateSources(BootstrapDebug.debugSourcesDir() != null);
builder.setAllowMocking(launchModeBuildItem.getLaunchMode() == LaunchMode.TEST);
builder.setStrictCompatibility(arcConfig.strictCompatibility);
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanGizmoAdaptor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanGizmoAdaptor.java
index b38f206af8951..6adbd0d320685 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanGizmoAdaptor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanGizmoAdaptor.java
@@ -16,7 +16,7 @@ public class GeneratedBeanGizmoAdaptor implements ClassOutput {
public GeneratedBeanGizmoAdaptor(BuildProducer classOutput) {
this.classOutput = classOutput;
- this.sources = BootstrapDebug.DEBUG_SOURCES_DIR != null ? new ConcurrentHashMap<>() : null;
+ this.sources = BootstrapDebug.debugSourcesDir() != null ? new ConcurrentHashMap<>() : null;
}
@Override
diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessInheritanceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessInheritanceTest.java
index 1fff2080e1951..48b07f9779c35 100644
--- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessInheritanceTest.java
+++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessInheritanceTest.java
@@ -2,6 +2,11 @@
import static org.assertj.core.api.Assertions.assertThat;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.logging.LogManager;
+
import jakarta.inject.Inject;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
@@ -16,6 +21,7 @@
import jakarta.transaction.UserTransaction;
import org.hibernate.Hibernate;
+import org.jboss.logmanager.Level;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -27,6 +33,19 @@
*/
public class PublicFieldAccessInheritanceTest {
+ // FIXME Temporary debug options for https://github.com/quarkusio/quarkus/issues/42479
+ // Needs to be set very early (e.g. as system properties) in order to affect the build;
+ // see https://quarkusio.zulipchat.com/#narrow/stream/187038-dev/topic/Build.20logs
+ private static final Map DEBUG_PROPERTIES = Map.of(
+ "quarkus.debug.transformed-classes-dir", "target/debug/${testRunId}/transformed-classes",
+ "quarkus.debug.generated-classes-dir", "target/debug/${testRunId}/generated-classes");
+ // FIXME Temporary trace categories for https://github.com/quarkusio/quarkus/issues/42479
+ // Needs to be set very early (e.g. programmatically) in order to affect the build;
+ // needs to be set programmatically in order to not leak to other tests.
+ // See https://quarkusio.zulipchat.com/#narrow/stream/187038-dev/topic/Build.20logs
+ // See https://github.com/quarkusio/quarkus/issues/43180
+ private static final List TRACE_CATEGORIES = List.of("org.hibernate", "io.quarkus.hibernate", "io.quarkus.panache");
+
@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
@@ -34,7 +53,31 @@ public class PublicFieldAccessInheritanceTest {
.addClass(MyAbstractEntity.class)
.addClass(MyConcreteEntity.class)
.addClass(FieldAccessEnhancedDelegate.class))
- .withConfigurationResource("application.properties");
+ .withConfigurationResource("application.properties")
+ // FIXME Temporary debug options for https://github.com/quarkusio/quarkus/issues/42479
+ .overrideConfigKey("quarkus.hibernate-orm.log.sql", "true")
+ // Not doing this because it has side effects on other tests for some reason;
+ // see https://github.com/quarkusio/quarkus/issues/43180
+ // It's not necessary anyway as the only effect of this config property is to change
+ // the logging level for a specific "org.hibernate.something" category, which we already do below.
+ //.overrideConfigKey("quarkus.hibernate-orm.log.bind-parameters", "true")
+ .setBeforeAllCustomizer(() -> {
+ // Used to differentiate reruns of flaky tests in Maven
+ var testRunId = PublicFieldAccessInheritanceTest.class + "/" + UUID.randomUUID();
+ System.out.println("Test run ID: " + testRunId);
+ DEBUG_PROPERTIES.forEach((key, value) -> System.setProperty(key, value.replace("${testRunId}", testRunId)));
+ for (String category : TRACE_CATEGORIES) {
+ LogManager.getLogManager().getLogger(category)
+ .setLevel(Level.TRACE);
+ }
+ })
+ .setAfterAllCustomizer(() -> {
+ DEBUG_PROPERTIES.keySet().forEach(System::clearProperty);
+ for (String category : TRACE_CATEGORIES) {
+ LogManager.getLogManager().getLogger(category)
+ .setLevel(null);
+ }
+ });
@Inject
EntityManager em;
diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java
index 6fed92c085e9b..47b5d1b6d1e6c 100644
--- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java
+++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java
@@ -69,6 +69,7 @@
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
+import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem;
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.IndexDependencyBuildItem;
@@ -105,6 +106,7 @@
import io.quarkus.kafka.client.serialization.ObjectMapperDeserializer;
import io.quarkus.kafka.client.serialization.ObjectMapperSerializer;
import io.quarkus.kafka.client.tls.QuarkusKafkaSslEngineFactory;
+import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem;
public class KafkaProcessor {
@@ -214,6 +216,7 @@ void relaxSaslElytron(BuildProducer config
@BuildStep
public void build(
KafkaBuildTimeConfig config, CurateOutcomeBuildItem curateOutcomeBuildItem,
+ BuildProducer configDescBuildItems,
CombinedIndexBuildItem indexBuildItem, BuildProducer reflectiveClass,
BuildProducer serviceProviders,
BuildProducer proxies,
@@ -289,6 +292,8 @@ public void build(
reflectiveClass.produce(
ReflectiveClassBuildItem.builder(QuarkusKafkaSslEngineFactory.class).build());
+ configDescBuildItems.produce(new ConfigDescriptionBuildItem("kafka.tls-configuration-name", null,
+ "The tls-configuration to use for the Kafka client", null, null, ConfigPhase.RUN_TIME));
}
@BuildStep(onlyIf = { HasSnappy.class, NativeOrNativeSourcesBuild.class })
diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/dns/MongoDnsClient.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/dns/MongoDnsClient.java
index d20705bedaec5..56ab2a4ddd155 100644
--- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/dns/MongoDnsClient.java
+++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/dns/MongoDnsClient.java
@@ -13,6 +13,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -26,6 +27,7 @@
import io.quarkus.mongodb.runtime.MongodbConfig;
import io.quarkus.runtime.annotations.RegisterForReflection;
+import io.smallrye.mutiny.Uni;
import io.vertx.core.dns.DnsClientOptions;
import io.vertx.mutiny.core.Vertx;
import io.vertx.mutiny.core.dns.SrvRecord;
@@ -130,12 +132,21 @@ private List resolveSrvRequest(final String srvHost) {
if (SRV_CACHE.containsKey(srvHost)) {
srvRecords = SRV_CACHE.get(srvHost);
} else {
- srvRecords = dnsClient.resolveSRV(srvHost).invoke(new Consumer<>() {
- @Override
- public void accept(List srvRecords) {
- SRV_CACHE.put(srvHost, srvRecords);
- }
- }).await().atMost(timeout);
+ srvRecords = Uni.createFrom().> deferred(
+ new Supplier<>() {
+ @Override
+ public Uni extends List> get() {
+ return dnsClient.resolveSRV(srvHost);
+ }
+ })
+ .onFailure().retry().withBackOff(Duration.ofSeconds(1)).atMost(3)
+ .invoke(new Consumer<>() {
+ @Override
+ public void accept(List srvRecords) {
+ SRV_CACHE.put(srvHost, srvRecords);
+ }
+ })
+ .await().atMost(timeout);
}
if (srvRecords.isEmpty()) {
@@ -167,12 +178,22 @@ public List resolveTxtRequest(final String host) {
try {
Duration timeout = config.getOptionalValue(DNS_LOOKUP_TIMEOUT, Duration.class)
.orElse(Duration.ofSeconds(5));
- return dnsClient.resolveTXT(host).invoke(new Consumer<>() {
- @Override
- public void accept(List strings) {
- TXT_CACHE.put(host, strings);
- }
- }).await().atMost(timeout);
+
+ return Uni.createFrom().> deferred(
+ new Supplier<>() {
+ @Override
+ public Uni extends List> get() {
+ return dnsClient.resolveTXT(host);
+ }
+ })
+ .onFailure().retry().withBackOff(Duration.ofSeconds(1)).atMost(3)
+ .invoke(new Consumer<>() {
+ @Override
+ public void accept(List strings) {
+ TXT_CACHE.put(host, strings);
+ }
+ })
+ .await().atMost(timeout);
} catch (Throwable e) {
throw new MongoConfigurationException("Unable to look up TXT record for host " + host, e);
}
diff --git a/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/DataSourceReactiveOracleConfig.java b/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/DataSourceReactiveOracleConfig.java
index fd546210e454f..778e30be8ac13 100644
--- a/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/DataSourceReactiveOracleConfig.java
+++ b/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/DataSourceReactiveOracleConfig.java
@@ -5,5 +5,4 @@
@ConfigGroup
public interface DataSourceReactiveOracleConfig {
- // when adding properties here, make sure you include the generated doc in datasource.adoc and reactive-sql-clients.adoc
}
diff --git a/extensions/resteasy-reactive/rest-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyReader.java b/extensions/resteasy-reactive/rest-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyReader.java
index ab833016c9bc7..a8d8eec468b01 100644
--- a/extensions/resteasy-reactive/rest-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyReader.java
+++ b/extensions/resteasy-reactive/rest-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyReader.java
@@ -2,6 +2,7 @@
import java.io.IOException;
import java.io.InputStream;
+import java.io.PushbackInputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
@@ -20,7 +21,6 @@
import jakarta.xml.bind.UnmarshalException;
import jakarta.xml.bind.Unmarshaller;
-import org.jboss.resteasy.reactive.common.util.StreamUtil;
import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader;
import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
@@ -93,14 +93,16 @@ private Unmarshaller getUnmarshall(Class type) throws JAXBException {
}
private Object doReadFrom(Class type, Type genericType, InputStream entityStream) throws IOException {
- if (isInputStreamEmpty(entityStream)) {
+ PushbackInputStream pushbackEntityStream = new PushbackInputStream(entityStream);
+ if (isStreamEmpty(pushbackEntityStream)) {
return null;
}
-
- return unmarshal(entityStream, type);
+ return unmarshal(pushbackEntityStream, type);
}
- private boolean isInputStreamEmpty(InputStream entityStream) throws IOException {
- return StreamUtil.isEmpty(entityStream) || entityStream.available() == 0;
+ private boolean isStreamEmpty(PushbackInputStream pushbackStream) throws IOException {
+ int firstByte = pushbackStream.read();
+ pushbackStream.unread(firstByte);
+ return firstByte == -1;
}
}
diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
index 3f67e713f24bf..c7154a5b83acd 100644
--- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
+++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
@@ -93,6 +93,7 @@
import io.quarkus.smallrye.openapi.deployment.filter.AutoRolesAllowedFilter;
import io.quarkus.smallrye.openapi.deployment.filter.AutoServerFilter;
import io.quarkus.smallrye.openapi.deployment.filter.AutoTagFilter;
+import io.quarkus.smallrye.openapi.deployment.filter.ClassAndMethod;
import io.quarkus.smallrye.openapi.deployment.filter.SecurityConfigFilter;
import io.quarkus.smallrye.openapi.deployment.spi.AddToOpenAPIDefinitionBuildItem;
import io.quarkus.smallrye.openapi.deployment.spi.IgnoreStaticDocumentBuildItem;
@@ -578,7 +579,8 @@ private OASFilter getAutoTagFilter(OpenApiFilteredIndexViewBuildItem apiFiltered
SmallRyeOpenApiConfig config) {
if (config.autoAddTags) {
- Map classNamesMethodReferences = getClassNamesMethodReferences(apiFilteredIndexViewBuildItem);
+ Map classNamesMethodReferences = getClassNamesMethodReferences(
+ apiFilteredIndexViewBuildItem);
if (!classNamesMethodReferences.isEmpty()) {
return new AutoTagFilter(classNamesMethodReferences);
@@ -680,7 +682,8 @@ private static Stream> getMethods(Anno
return Stream.empty();
}
- private Map getClassNamesMethodReferences(OpenApiFilteredIndexViewBuildItem apiFilteredIndexViewBuildItem) {
+ private Map getClassNamesMethodReferences(
+ OpenApiFilteredIndexViewBuildItem apiFilteredIndexViewBuildItem) {
FilteredIndexView filteredIndex = apiFilteredIndexViewBuildItem.getIndex();
List openapiAnnotations = new ArrayList<>();
Set allOpenAPIEndpoints = getAllOpenAPIEndpoints();
@@ -688,7 +691,7 @@ private Map getClassNamesMethodReferences(OpenApiFilteredIndexVi
openapiAnnotations.addAll(filteredIndex.getAnnotations(dotName));
}
- Map classNames = new HashMap<>();
+ Map classNames = new HashMap<>();
for (AnnotationInstance ai : openapiAnnotations) {
if (ai.target().kind().equals(AnnotationTarget.Kind.METHOD)) {
@@ -704,7 +707,7 @@ private Map getClassNamesMethodReferences(OpenApiFilteredIndexVi
.getAllKnownSubclasses(declaringClass.name()), classNames);
} else {
String ref = JandexUtil.createUniqueMethodReference(declaringClass, method);
- classNames.put(ref, declaringClass.simpleName());
+ classNames.put(ref, new ClassAndMethod(declaringClass.simpleName(), method.name()));
}
}
}
@@ -712,16 +715,18 @@ private Map getClassNamesMethodReferences(OpenApiFilteredIndexVi
}
void addMethodImplementationClassNames(MethodInfo method, Type[] params, Collection classes,
- Map classNames) {
+ Map classNames) {
for (ClassInfo impl : classes) {
String simpleClassName = impl.simpleName();
MethodInfo implMethod = impl.method(method.name(), params);
if (implMethod != null) {
- classNames.put(JandexUtil.createUniqueMethodReference(impl, implMethod), simpleClassName);
+ classNames.put(JandexUtil.createUniqueMethodReference(impl, implMethod),
+ new ClassAndMethod(simpleClassName, implMethod.name()));
}
- classNames.put(JandexUtil.createUniqueMethodReference(impl, method), simpleClassName);
+ classNames.put(JandexUtil.createUniqueMethodReference(impl, method),
+ new ClassAndMethod(simpleClassName, method.name()));
}
}
diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/AutoTagFilter.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/AutoTagFilter.java
index 0794d20aaff2d..22a119e1e6fe8 100644
--- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/AutoTagFilter.java
+++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/AutoTagFilter.java
@@ -15,21 +15,21 @@
* Automatically tag operations based on the class name.
*/
public class AutoTagFilter implements OASFilter {
- private Map classNameMap;
+ private Map classNameMap;
public AutoTagFilter() {
}
- public AutoTagFilter(Map classNameMap) {
+ public AutoTagFilter(Map classNameMap) {
this.classNameMap = classNameMap;
}
- public Map getClassNameMap() {
+ public Map getClassNameMap() {
return classNameMap;
}
- public void setClassNameMap(Map classNameMap) {
+ public void setClassNameMap(Map classNameMap) {
this.classNameMap = classNameMap;
}
@@ -45,12 +45,23 @@ public void filterOpenAPI(OpenAPI openAPI) {
Map operations = pathItem.getValue().getOperations();
if (operations != null && !operations.isEmpty()) {
for (Operation operation : operations.values()) {
+
+ if (operation.getDescription() == null || operation.getDescription().isBlank()) {
+ // Auto add a description
+ OperationImpl operationImpl = (OperationImpl) operation;
+ String methodRef = operationImpl.getMethodRef();
+ if (classNameMap.containsKey(methodRef)) {
+ operation.setDescription(capitalizeFirstLetter(
+ splitCamelCase(classNameMap.get(methodRef).methodName())));
+ }
+ }
+
if (operation.getTags() == null || operation.getTags().isEmpty()) {
// Auto add a tag
OperationImpl operationImpl = (OperationImpl) operation;
String methodRef = operationImpl.getMethodRef();
if (classNameMap.containsKey(methodRef)) {
- operation.addTag(splitCamelCase(classNameMap.get(methodRef)));
+ operation.addTag(splitCamelCase(classNameMap.get(methodRef).className()));
}
}
}
@@ -69,4 +80,11 @@ private String splitCamelCase(String s) {
"(?<=[A-Za-z])(?=[^A-Za-z])"),
" ");
}
+
+ private String capitalizeFirstLetter(String str) {
+ if (str == null || str.isEmpty()) {
+ return str;
+ }
+ return str.substring(0, 1).toUpperCase() + str.substring(1);
+ }
}
\ No newline at end of file
diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/ClassAndMethod.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/ClassAndMethod.java
new file mode 100644
index 0000000000000..d5f1bce941d38
--- /dev/null
+++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/ClassAndMethod.java
@@ -0,0 +1,5 @@
+package io.quarkus.smallrye.openapi.deployment.filter;
+
+public record ClassAndMethod(String className, String methodName) {
+
+}
diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringProcessor.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringProcessor.java
index 41fbd927a99a6..4edc06b5615a2 100644
--- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringProcessor.java
+++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringProcessor.java
@@ -214,6 +214,9 @@ private void handleMethodAnnotationWithOutgoing(BuildProducer
new DeploymentException("Empty @Outgoing annotation on method " + method)));
}
if (outgoing != null) {
+ configDescriptionBuildItemBuildProducer.produce(new ConfigDescriptionBuildItem(
+ "mp.messaging.outgoing." + outgoing.value().asString() + ".tls-configuration-name", null,
+ "The tls-configuration to use", null, null, ConfigPhase.RUN_TIME));
configDescriptionBuildItemBuildProducer.produce(new ConfigDescriptionBuildItem(
"mp.messaging.outgoing." + outgoing.value().asString() + ".connector", null,
"The connector to use", null, null, ConfigPhase.BUILD_TIME));
@@ -232,6 +235,9 @@ private void handleMethodAnnotationWithOutgoings(BuildProducer
validationErrors.produce(new ValidationPhaseBuildItem.ValidationErrorBuildItem(
new DeploymentException("Empty @Outgoing annotation on method " + method)));
}
+ configDescriptionBuildItemBuildProducer.produce(new ConfigDescriptionBuildItem(
+ "mp.messaging.outgoing." + instance.value().asString() + ".tls-configuration-name", null,
+ "The tls-configuration to use", null, null, ConfigPhase.RUN_TIME));
configDescriptionBuildItemBuildProducer.produce(new ConfigDescriptionBuildItem(
"mp.messaging.outgoing." + instance.value().asString() + ".connector", null,
"The connector to use", null, null, ConfigPhase.BUILD_TIME));
@@ -250,6 +256,9 @@ private void handleMethodAnnotationWithIncomings(BuildProducer
validationErrors.produce(new ValidationPhaseBuildItem.ValidationErrorBuildItem(
new DeploymentException("Empty @Incoming annotation on method " + method)));
}
+ configDescriptionBuildItemBuildProducer.produce(new ConfigDescriptionBuildItem(
+ "mp.messaging.incoming." + instance.value().asString() + ".tls-configuration-name", null,
+ "The tls-configuration to use", null, null, ConfigPhase.RUN_TIME));
configDescriptionBuildItemBuildProducer.produce(new ConfigDescriptionBuildItem(
"mp.messaging.incoming." + instance.value().asString() + ".connector", null,
"The connector to use", null, null, ConfigPhase.BUILD_TIME));
@@ -267,6 +276,9 @@ private void handleMethodAnnotatedWithIncoming(BuildProducer a
new DeploymentException("Empty @Incoming annotation on method " + method)));
}
if (incoming != null) {
+ configDescriptionBuildItemBuildProducer.produce(new ConfigDescriptionBuildItem(
+ "mp.messaging.incoming." + incoming.value().asString() + ".tls-configuration-name", null,
+ "The tls-configuration to use", null, null, ConfigPhase.RUN_TIME));
configDescriptionBuildItemBuildProducer.produce(new ConfigDescriptionBuildItem(
"mp.messaging.incoming." + incoming.value().asString() + ".connector", null,
"The connector to use", null, null, ConfigPhase.BUILD_TIME));
diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-continuous-testing.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-continuous-testing.js
index 7c82175e701c4..e255967f68e0e 100644
--- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-continuous-testing.js
+++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-continuous-testing.js
@@ -80,7 +80,8 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
_state: {state: true},
_results: {state: true},
_busy: {state: true},
- _detailsOpenedItem: {state: true, type: Array}
+ _detailsOpenedItem: {state: true, type: Array},
+ _displayTags: {state: true, type: Boolean},
};
constructor() {
@@ -89,6 +90,7 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
this._detailsOpenedItem = [];
this._chartTitles = ["passed", "failed", "skipped"];
this._chartColors = ['--lumo-success-text-color', '--lumo-error-text-color', '--lumo-contrast-70pct'];
+ this._displayTags = true;
}
connectedCallback() {
@@ -135,14 +137,56 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
}
render() {
+ let results = this._prepareResultsToRender();
return html`
- ${this._renderMenuBar()}
- ${this._renderResultSet()}
+ ${this._renderMenuBar(results)}
+ ${this._renderResults(results)}
${this._renderBarChart()}
`;
}
- _renderMenuBar(){
+ _prepareResultsToRender() {
+ if(this._state && this._state.running && this._results && this._results.results) {
+ let itemsByState = {
+ passing: [],
+ failing: [],
+ skipped: [],
+ }
+ Object
+ .values(this._results.results)
+ .forEach(item => {
+ itemsByState.passing.push(...item.passing.filter( obj => obj.test === true ));
+ itemsByState.failing.push(...item.failing.filter( obj => obj.test === true ));
+ itemsByState.skipped.push(...item.skipped.filter( obj => obj.test === true ));
+ });
+ let items = itemsByState.failing.concat(
+ itemsByState.passing,
+ itemsByState.skipped
+ );
+ let hasTags = items.find( item => item.tags && item.tags.length > 0 );
+ return {
+ items: items,
+ meta: {
+ hasTags: hasTags,
+ failing: itemsByState.failing.length,
+ passing: itemsByState.passing.length,
+ skipped: itemsByState.skipped.length,
+ },
+ }
+ } else {
+ return {
+ items: [],
+ meta: {
+ hasTags: false,
+ failing: 0,
+ passing: 0,
+ skipped: 0,
+ },
+ };
+ }
+ }
+
+ _renderMenuBar(results){
if(this._state){
return html``;
@@ -193,62 +238,28 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
}
- _renderResultSet(){
- if(this._state && this._state.running && this._results && this._results.results) {
-
- let failingResults = this._results.failing;
- let passingResults = this._results.passing;
- let skippedResults = this._results.skipped;
-
- var allResults = failingResults.concat(passingResults, skippedResults);
-
- return html`${this._renderResults(allResults)}`;
- }
-
- }
-
_renderResults(results){
- if(results.length > 0){
+ if(results.items.length > 0){
+
+ return html`
+ {
+ const prop = event.detail.value;
+ this._detailsOpenedItem = prop ? [prop] : [];
+ }}"
+ ${gridRowDetailsRenderer(this._descriptionRenderer, [])}
+ >
+ ${
+ this._displayTags && results.meta.hasTags
+ ? html` this._tagsRenderer(prop), [])}> `
+ : ''
+ }
+ this._testRenderer(prop), [])}>
+ this._nameRenderer(prop), [])}>
+ this._timeRenderer(prop), [])}>>
+ `;
- let items = [];
-
- for (let i = 0; i < results.length; i++) {
- let result = results[i];
-
- let failingResult = result.failing.filter(function( obj ) {
- return obj.test === true;
- });
- let passingResult = result.passing.filter(function( obj ) {
- return obj.test === true;
- });
- let skippedResult = result.skipped.filter(function( obj ) {
- return obj.test === true;
- });
-
- items.push.apply(items, failingResult);
- items.push.apply(items, passingResult);
- items.push.apply(items, skippedResult);
- }
-
- if(items.length>0){
- return html`
- {
- const prop = event.detail.value;
- this._detailsOpenedItem = prop ? [prop] : [];
- }}"
- ${gridRowDetailsRenderer(this._descriptionRenderer, [])}
- >
-
- this._testRenderer(prop), [])}>
- this._nameRenderer(prop), [])}>
- this._timeRenderer(prop), [])}>>
- `;
- }else{
- return html`No tests`;
- }
-
}else{
return html`No tests`;
}
@@ -286,6 +297,35 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
)}`;
}
+ _tagToColor(tag){
+ // Step 0: two strings with the last char differing by 1 should render to totally different colors
+ const tagValue = tag + tag;
+ // Step 1: Convert the string to a numeric hash value
+ let hash = 0;
+ for (let i = 0; i < tagValue.length; i++) {
+ hash = tagValue.charCodeAt(i) + ((hash << 5) - hash);
+ }
+
+ // Step 2: Convert the numeric hash value to a hex color code
+ let color = '#';
+ const normalizeFactor = 0.2; // cut 20% light and dark values
+ for (let i = 0; i < 3; i++) {
+ const value = Math.round(((hash >> (i * 8)) & 0xFF) * (1-2*normalizeFactor) + 255*normalizeFactor);
+ color += ('00' + value.toString(16)).slice(-2);
+ }
+
+ return color;
+ }
+
+ _tagsRenderer(testLine){
+ return html`${testLine.tags.map((tag, index) => {
+ const color = this._tagToColor(tag);
+ return html`
+ ${"io.quarkus.test.junit.QuarkusTest" === tag ? "Q" : tag}
+ `;
+ })}`;
+ }
+
_testRenderer(testLine){
let level = testLine.testExecutionResult.status.toLowerCase();
@@ -369,6 +409,17 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
}
}
+ _renderToggleDisplayTags(results) {
+ if(this._state && this._state.running){
+ return html`
+ `;
+ }
+ }
+
_start(){
if(!this._busy){
this._busy = true;
@@ -407,5 +458,9 @@ export class QwcContinuousTesting extends QwcHotReloadElement {
this._busy = false;
});
}
+
+ _toggleDisplayTags(){
+ this._displayTags = !this._displayTags;
+ }
}
customElements.define('qwc-continuous-testing', QwcContinuousTesting);
\ No newline at end of file
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ResourceNotFoundData.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ResourceNotFoundData.java
index b9f28a85dee5a..d17b2ddcf9e8b 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ResourceNotFoundData.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ResourceNotFoundData.java
@@ -124,8 +124,9 @@ public String getHTMLContent() {
// Additional Endpoints
if (!this.additionalEndpoints.isEmpty()) {
+ List endpoints = getSortedAdditionalRouteDescriptions();
builder.resourcesStart(ADDITIONAL_ENDPOINTS);
- for (AdditionalRouteDescription additionalEndpoint : this.additionalEndpoints) {
+ for (AdditionalRouteDescription additionalEndpoint : endpoints) {
builder.staticResourcePath(additionalEndpoint.getUri(), additionalEndpoint.getDescription());
}
builder.resourcesEnd();
@@ -189,7 +190,8 @@ public JsonObject getJsonContent() {
// Additional Endpoints
if (!this.additionalEndpoints.isEmpty()) {
JsonArray ae = new JsonArray();
- for (AdditionalRouteDescription additionalEndpoint : this.additionalEndpoints) {
+ List endpoints = getSortedAdditionalRouteDescriptions();
+ for (AdditionalRouteDescription additionalEndpoint : endpoints) {
ae.add(JsonObject.of(URI, additionalEndpoint.getUri(), DESCRIPTION, additionalEndpoint.getDescription()));
}
infoMap.put(ADDITIONAL_ENDPOINTS, ae);
@@ -250,7 +252,8 @@ public String getTextContent() {
// Additional Endpoints
if (!this.additionalEndpoints.isEmpty()) {
sw.write(ADDITIONAL_ENDPOINTS + NL);
- for (AdditionalRouteDescription additionalEndpoint : this.additionalEndpoints) {
+ List endpoints = getSortedAdditionalRouteDescriptions();
+ for (AdditionalRouteDescription additionalEndpoint : endpoints) {
sw.write(TAB + "- " + additionalEndpoint.getUri() + NL);
sw.write(TAB + TAB + "- " + additionalEndpoint.getDescription() + NL);
}
@@ -341,6 +344,13 @@ private boolean isHtmlFileName(String fileName) {
return fileName.endsWith(".html") || fileName.endsWith(".htm") || fileName.endsWith(".xhtml");
}
+ private List getSortedAdditionalRouteDescriptions() {
+ return this.additionalEndpoints.stream().sorted(
+ Comparator.comparingInt((AdditionalRouteDescription desc) -> desc.getUri().split("/").length)
+ .thenComparing(AdditionalRouteDescription::getUri))
+ .toList();
+ }
+
private static final String HEADING = "404 - Resource Not Found";
private static final String RESOURCE_ENDPOINTS = "Resource Endpoints";
private static final String SERVLET_MAPPINGS = "Servlet mappings";
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapDebug.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapDebug.java
index 604d6c23838ba..befb358db400d 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapDebug.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapDebug.java
@@ -2,12 +2,39 @@
public final class BootstrapDebug {
- public static final String DEBUG_SOURCES_DIR = System.getProperty("quarkus.debug.generated-sources-dir");
-
+ /**
+ * @deprecated Use {@link #debugClassesDir()} instead for more flexibility when testing.
+ */
+ @Deprecated
public static final String DEBUG_CLASSES_DIR = System.getProperty("quarkus.debug.generated-classes-dir");
+ /**
+ * @deprecated Use {@link #transformedClassesDir()} instead for more flexibility when testing.
+ */
+ @Deprecated
public static final String DEBUG_TRANSFORMED_CLASSES_DIR = System.getProperty("quarkus.debug.transformed-classes-dir");
+ /**
+ * @deprecated Use {@link #debugSourcesDir()} instead for more flexibility when testing.
+ */
+ @Deprecated
+ public static final String DEBUG_SOURCES_DIR = System.getProperty("quarkus.debug.generated-sources-dir");
+
+ // We're exposing the configuration properties as methods and not constants,
+ // because in the case of tests, the system property could change over the life of the JVM.
+
+ public static String debugClassesDir() {
+ return System.getProperty("quarkus.debug.generated-classes-dir");
+ }
+
+ public static String transformedClassesDir() {
+ return System.getProperty("quarkus.debug.transformed-classes-dir");
+ }
+
+ public static String debugSourcesDir() {
+ return System.getProperty("quarkus.debug.generated-sources-dir");
+ }
+
private BootstrapDebug() {
}