diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fcae87fee9..19b9bf3cd7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,7 @@ hadoop = "3.4.1" iceberg = "1.7.1" dropwizard = "4.0.11" +picocli = "4.7.6" slf4j = "2.0.16" swagger = "1.6.14" @@ -40,6 +41,7 @@ bouncycastle-bcprov = { module = "org.bouncycastle:bcprov-jdk18on", version = "1 caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version = "3.1.8" } commons-codec1 = { module = "commons-codec:commons-codec", version = "1.17.2" } commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.17.0" } +commons-text = { module = "org.apache.commons:commons-text", version = "1.13.0" } dropwizard-bom = { module = "io.dropwizard:dropwizard-bom", version.ref = "dropwizard" } eclipselink = { module = "org.eclipse.persistence:eclipselink", version = "4.0.5" } errorprone = { module = "com.google.errorprone:error_prone_core", version = "2.36.0" } @@ -67,10 +69,13 @@ micrometer-bom = { module = "io.micrometer:micrometer-bom", version = "1.14.3" } mockito-core = { module = "org.mockito:mockito-core", version = "5.15.2" } opentelemetry-bom = { module = "io.opentelemetry:opentelemetry-bom", version = "1.46.0" } opentelemetry-semconv = { module = "io.opentelemetry.semconv:opentelemetry-semconv", version = "1.25.0-alpha" } +picocli = { module = "info.picocli:picocli-codegen", version.ref = "picocli" } +picocli-codegen = { module = "info.picocli:picocli-codegen", version.ref = "picocli" } prometheus-metrics-exporter-servlet-jakarta = { module = "io.prometheus:prometheus-metrics-exporter-servlet-jakarta", version = "1.3.5" } s3mock-testcontainers = { module = "com.adobe.testing:s3mock-testcontainers", version = "3.12.0" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } smallrye-common-annotation = { module = "io.smallrye.common:smallrye-common-annotation", version = "2.9.0" } +smallrye-config-core = { module = "io.smallrye.config:smallrye-config-core", version = "3.10.2" } spotbugs-annotations = { module = "com.github.spotbugs:spotbugs-annotations", version = "4.8.6" } swagger-annotations = { module = "io.swagger:swagger-annotations", version.ref = "swagger" } swagger-jaxrs = { module = "io.swagger:swagger-jaxrs", version.ref = "swagger" } diff --git a/gradle/projects.main.properties b/gradle/projects.main.properties index 71490c3cb4..1938df25d3 100644 --- a/gradle/projects.main.properties +++ b/gradle/projects.main.properties @@ -29,3 +29,7 @@ polaris-jpa-model=extension/persistence/jpa-model polaris-tests=integration-tests aggregated-license-report=aggregated-license-report polaris-version=tools/version + +polaris-config-docs-annotations=tools/config-docs/annotations +polaris-config-docs-generator=tools/config-docs/generator +polaris-config-docs-site=tools/config-docs/site diff --git a/tools/config-docs/annotations/build.gradle.kts b/tools/config-docs/annotations/build.gradle.kts new file mode 100644 index 0000000000..90bb2ac84d --- /dev/null +++ b/tools/config-docs/annotations/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +plugins { + id("polaris-client") + `java-library` +} + +description = "Polaris reference docs annotations" diff --git a/tools/config-docs/annotations/src/main/java/org/apache/polaris/docs/ConfigDocs.java b/tools/config-docs/annotations/src/main/java/org/apache/polaris/docs/ConfigDocs.java new file mode 100644 index 0000000000..6997f0b4fd --- /dev/null +++ b/tools/config-docs/annotations/src/main/java/org/apache/polaris/docs/ConfigDocs.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Contains annotations for {@code :polaris-config-doc-generator}. */ +public interface ConfigDocs { + /** + * For properties-configs, declares a class containing configuration constants for "properties" + * and gives it a page name. The generated markdown files will start with this name. + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.SOURCE) + @interface ConfigPageGroup { + String name(); + } + + /** + * Define the "section" in which the config option appears. + * + *

For properties-configs, this declares that a property constant field appears in the + * generated markdown files. + * + *

For smallrye-configs, this declares that a property appears under a different "prefix". + */ + @Target({ElementType.FIELD, ElementType.METHOD}) + @Retention(RetentionPolicy.SOURCE) + @interface ConfigItem { + /** + * The name of the "section" in which this constant field shall appear. The name of the + * generated markdown file will "end" with this name. + */ + String section() default ""; + + /** For smallrye-configs only: the section docs are taken from property type's javadoc. */ + boolean sectionDocFromType() default false; + } + + /** + * For smallrye-configs, gives map-keys a name that appears as a placeholder in the fully + * qualified config name in the generated docs. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.SOURCE) + @interface ConfigPropertyName { + String value() default ""; + } +} diff --git a/tools/config-docs/generator/build.gradle.kts b/tools/config-docs/generator/build.gradle.kts new file mode 100644 index 0000000000..61ea0218fc --- /dev/null +++ b/tools/config-docs/generator/build.gradle.kts @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +plugins { + id("polaris-server") + `java-library` + `java-test-fixtures` +} + +description = "Generates Polaris reference docs" + +val genTesting by configurations.creating + +dependencies { + implementation(project(":polaris-config-docs-annotations")) + + implementation(libs.commons.text) + + implementation(libs.smallrye.config.core) + implementation(libs.picocli) + annotationProcessor(libs.picocli.codegen) + + testFixturesApi(platform(libs.junit.bom)) + testFixturesApi("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testFixturesApi(libs.assertj.core) + testFixturesApi(libs.mockito.core) + + genTesting(project(":polaris-config-docs-annotations")) + genTesting(libs.smallrye.config.core) +} + +tasks.named("test") { + // The test needs the classpath for the necessary dependencies (annotations + smallrye-config). + // Resolving the dependencies must happen during task execution (not configuration). + jvmArgumentProviders.add( + CommandLineArgumentProvider { + // So, in theory, all 'org.gradle.category' attributes should use the type + // org.gradle.api.attributes.Category, + // as Category.CATEGORY_ATTRIBUTE is defined. BUT! Some attributes have an attribute type == + // String.class! + + val categoryAttributeAsString = Attribute.of("org.gradle.category", String::class.java) + + val libraries = + genTesting.incoming.artifacts + .filter { a -> + // dependencies: + // org.gradle.category=library + val category = + a.variant.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE) + ?: a.variant.attributes.getAttribute(categoryAttributeAsString) + category != null && category.toString() == Category.LIBRARY + } + .map { a -> a.file } + + listOf("-Dtesting.libraries=" + libraries.joinToString(":")) + } + ) +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/DocGenDoclet.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/DocGenDoclet.java new file mode 100644 index 0000000000..f0c785e091 --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/DocGenDoclet.java @@ -0,0 +1,360 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static org.apache.polaris.docs.generator.SmallRyeConfigs.concatWithDot; + +import com.sun.source.doctree.DocCommentTree; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import javax.lang.model.SourceVersion; +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; + +public class DocGenDoclet implements Doclet { + + private Path outputDirectory = Paths.get("."); + + private final Option directoryOption = + new Option() { + @Override + public int getArgumentCount() { + return 1; + } + + @Override + public String getDescription() { + return "Directory to write .md files to"; + } + + @Override + public Kind getKind() { + return Kind.STANDARD; + } + + @Override + public List getNames() { + return List.of("-d", "--directory"); + } + + @Override + public String getParameters() { + return "directory"; + } + + @Override + public boolean process(String option, List arguments) { + outputDirectory = Paths.get(arguments.get(0)); + return true; + } + }; + private final Option notimestampDummy = new DummyOption(List.of("-notimestamp"), 0); + private final Option doctitleDummy = new DummyOption(List.of("-doctitle"), 1); + private final Option windowtitleDummy = new DummyOption(List.of("-windowtitle"), 1); + + @Override + public boolean run(DocletEnvironment environment) { + var propertiesConfigs = new PropertiesConfigs(environment); + var smallryeConfigs = new SmallRyeConfigs(environment); + + for (var includedElement : environment.getIncludedElements()) { + try { + includedElement.accept(propertiesConfigs.visitor(), null); + includedElement.accept(smallryeConfigs.visitor(), null); + } catch (RuntimeException ex) { + throw new RuntimeException("Failure processing included element " + includedElement, ex); + } + } + + propertiesConfigPages(propertiesConfigs); + + smallryeConfigPages(environment, smallryeConfigs); + + return true; + } + + private void propertiesConfigPages(PropertiesConfigs propertiesConfigs) { + for (var page : propertiesConfigs.pages()) { + System.out.println("Generating properties config pages for " + page.name()); + for (Map.Entry> e : page.sectionItems().entrySet()) { + var section = e.getKey(); + if (section.isEmpty()) { + section = "main"; + } + System.out.println("... generating page section " + section); + var items = e.getValue(); + + var file = outputDirectory.resolve(page.name() + "-" + safeFileName(section) + ".md"); + try (var fw = Files.newBufferedWriter(file, UTF_8, CREATE, TRUNCATE_EXISTING); + var writer = new PrintWriter(fw)) { + writer.println("| Property | Description |"); + writer.println("|----------|-------------|"); + for (var item : items) { + // TODO add _pluggable_ formatter (javadoc to markdown, later: javadoc to asciidoc?) + var md = new MarkdownPropertyFormatter(item); + if (!md.isHidden()) { + writer.print("| `"); + writer.print(md.propertyName()); + writer.print("` | "); + writer.print(md.description().replaceAll("\n", "
")); + writer.println(" |"); + } + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + } + } + + private void smallryeConfigPages(DocletEnvironment environment, SmallRyeConfigs smallryeConfigs) { + var sectionPages = new HashMap(); + + for (var mappingInfo : smallryeConfigs.configMappingInfos()) { + smallryeProcessRootMappingInfo(environment, smallryeConfigs, mappingInfo, sectionPages); + } + + sectionPages.values().stream() + .filter(p -> !p.isEmpty()) + .forEach( + page -> { + System.out.printf( + "... generating smallrye config page for section %s%n", page.section); + var file = outputDirectory.resolve("smallrye-" + safeFileName(page.section) + ".md"); + try (var pw = + new PrintWriter( + Files.newBufferedWriter(file, UTF_8, CREATE, TRUNCATE_EXISTING))) { + page.writeTo(pw); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + private void smallryeProcessRootMappingInfo( + DocletEnvironment environment, + SmallRyeConfigs smallryeConfigs, + SmallRyeConfigMappingInfo mappingInfo, + Map sectionPages) { + var effectiveSection = mappingInfo.prefix(); + var propertyNamePrefix = mappingInfo.prefix(); + smallryeProcessMappingInfo( + "", + environment, + smallryeConfigs, + effectiveSection, + mappingInfo, + propertyNamePrefix, + sectionPages); + } + + private void smallryeProcessPropertyMappingInfo( + String logIndent, + DocletEnvironment environment, + SmallRyeConfigs smallryeConfigs, + String section, + SmallRyeConfigMappingInfo mappingInfo, + String propertyNamePrefix, + Map sectionPages) { + smallryeProcessMappingInfo( + logIndent + " ", + environment, + smallryeConfigs, + section, + mappingInfo, + propertyNamePrefix, + sectionPages); + } + + private void smallryeProcessMappingInfo( + String logIndent, + DocletEnvironment environment, + SmallRyeConfigs smallryeConfigs, + String effectiveSection, + SmallRyeConfigMappingInfo mappingInfo, + String propertyNamePrefix, + Map sectionPages) { + + // Eagerly create page, so we have the comment from the type. + sectionPages.computeIfAbsent( + effectiveSection, + s -> new SmallRyeConfigSectionPage(s, mappingInfo.element(), mappingInfo.typeComment())); + + mappingInfo + .properties(environment) + .forEach( + prop -> + smallryeProcessProperty( + logIndent, + environment, + smallryeConfigs, + mappingInfo, + effectiveSection, + prop, + propertyNamePrefix, + sectionPages)); + } + + private void smallryeProcessProperty( + String logIndent, + DocletEnvironment environment, + SmallRyeConfigs smallryeConfigs, + SmallRyeConfigMappingInfo mappingInfo, + String section, + SmallRyeConfigPropertyInfo propertyInfo, + String propertyNamePrefix, + Map sectionPages) { + + var effectiveSection = + propertyInfo.prefixOverride().map(o -> concatWithDot(section, o)).orElse(section); + + var md = new MarkdownPropertyFormatter(propertyInfo); + if (md.isHidden()) { + return; + } + var fullName = formatPropertyName(propertyNamePrefix, md.propertyName(), md.propertySuffix()); + + var page = + sectionPages.computeIfAbsent( + effectiveSection, + s -> { + DocCommentTree doc = + propertyInfo.sectionDocFromType() + ? propertyInfo + .groupType() + .map(smallryeConfigs::getConfigMappingInfo) + .map(SmallRyeConfigMappingInfo::typeComment) + .orElse(null) + : propertyInfo.doc(); + return new SmallRyeConfigSectionPage(s, mappingInfo.element(), doc); + }); + propertyInfo.prefixOverride().ifPresent(o -> page.incrementSectionRef()); + if (propertyInfo.isSettableType()) { + page.addProperty(fullName, propertyInfo, md); + } + + propertyInfo + .groupType() + .ifPresent( + groupType -> + smallryeProcessPropertyMappingInfo( + logIndent + " ", + environment, + smallryeConfigs, + effectiveSection, + smallryeConfigs.getConfigMappingInfo(groupType), + fullName, + sectionPages)); + } + + private String formatPropertyName( + String propertyNamePrefix, String propertyName, String propertySuffix) { + var r = concatWithDot(propertyNamePrefix, propertyName); + return propertySuffix.isEmpty() ? r : concatWithDot(r, "`_`<" + propertySuffix + ">`_`"); + } + + private String safeFileName(String str) { + var sb = new StringBuilder(); + var len = str.length(); + var hadLOD = false; + for (int i = 0; i < len; i++) { + var c = str.charAt(i); + if (Character.isLetterOrDigit(c)) { + sb.append(c); + hadLOD = true; + } else { + if (hadLOD) { + sb.append('_'); + hadLOD = false; + } + } + } + return sb.toString(); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + @Override + public Set getSupportedOptions() { + return Set.of(directoryOption, doctitleDummy, windowtitleDummy, notimestampDummy); + } + + @Override + public String getName() { + return "PolarisReferenceDocsDoclet"; + } + + @Override + public void init(Locale locale, Reporter reporter) {} + + static final class DummyOption implements Option { + private final List names; + private final int argumentCount; + + DummyOption(List names, int argumentCount) { + this.names = names; + this.argumentCount = argumentCount; + } + + @Override + public boolean process(String option, List arguments) { + return true; + } + + @Override + public String getParameters() { + return ""; + } + + @Override + public List getNames() { + return names; + } + + @Override + public Kind getKind() { + return null; + } + + @Override + public String getDescription() { + return "Ignored"; + } + + @Override + public int getArgumentCount() { + return argumentCount; + } + } +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/MarkdownFormatter.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/MarkdownFormatter.java new file mode 100644 index 0000000000..a08abcf99d --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/MarkdownFormatter.java @@ -0,0 +1,634 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import static java.util.Objects.requireNonNull; + +import com.sun.source.doctree.AttributeTree; +import com.sun.source.doctree.DeprecatedTree; +import com.sun.source.doctree.DocCommentTree; +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.EndElementTree; +import com.sun.source.doctree.EntityTree; +import com.sun.source.doctree.ErroneousTree; +import com.sun.source.doctree.HiddenTree; +import com.sun.source.doctree.IndexTree; +import com.sun.source.doctree.LinkTree; +import com.sun.source.doctree.LiteralTree; +import com.sun.source.doctree.ReferenceTree; +import com.sun.source.doctree.SeeTree; +import com.sun.source.doctree.SinceTree; +import com.sun.source.doctree.StartElementTree; +import com.sun.source.doctree.SummaryTree; +import com.sun.source.doctree.TextTree; +import com.sun.source.doctree.UnknownBlockTagTree; +import com.sun.source.doctree.UnknownInlineTagTree; +import com.sun.source.doctree.ValueTree; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; +import org.apache.commons.text.StringEscapeUtils; + +public abstract class MarkdownFormatter { + private final Element element; + private String javadocDeprecated; + private String javadocSee; + private String javadocHidden; + private String javadocSince; + private String javadocSummary; + private String javadocBody; + + public MarkdownFormatter(Element element, DocCommentTree commentTree) { + this.element = element; + + if (commentTree != null) { + + // @see, @deprecated, etc + for (DocTree doc : commentTree.getBlockTags()) { + switch (doc.getKind()) { + case SEE: + { + // @see + var seeTree = (SeeTree) doc; + var reference = seeTree.getReference(); + this.javadocSee = format(reference); + } + break; + case DEPRECATED: + { + // @deprecated + var deprecatedTree = (DeprecatedTree) doc; + var body = deprecatedTree.getBody(); + this.javadocDeprecated = format(body); + } + break; + case HIDDEN: + { + // @hidden + var hiddenTree = (HiddenTree) doc; + var body = hiddenTree.getBody(); + this.javadocHidden = format(body); + } + break; + case SINCE: + { + // @since + var sinceTree = (SinceTree) doc; + var body = sinceTree.getBody(); + this.javadocSince = format(body); + } + break; + default: + break; + } + } + + var fullBody = commentTree.getFullBody(); + if (!fullBody.isEmpty()) { + var body = + switch (fullBody.getFirst().getKind()) { + case SUMMARY -> { + var summaryTree = (SummaryTree) fullBody.getFirst(); + this.javadocSummary = format(summaryTree.getSummary()); + yield fullBody.subList(1, fullBody.size()); + } + default -> { + this.javadocSummary = format(commentTree.getFirstSentence()); + yield commentTree.getBody(); + } + }; + this.javadocBody = format(body); + } + } + } + + public String description() { + var sb = new StringBuilder(); + if (javadocSummary != null) { + sb.append(javadocSummary); + } + if (javadocBody != null) { + sb.append(' ').append(javadocBody); + } + if (javadocSince != null) { + sb.append("\n\nSince: ").append(javadocSince); + } + if (javadocSee != null) { + sb.append("\n\nSee: ").append(javadocSee); + } + if (javadocDeprecated != null) { + sb.append("\n\n_Deprecated_ ").append(javadocDeprecated); + } else if (element != null) { + var deprecated = element.getAnnotation(Deprecated.class) != null; + if (deprecated) { + sb.append("\n\n_Deprecated_ "); + } + } + return sb.toString(); + } + + public boolean isHidden() { + return javadocHidden != null; + } + + private String format(List docTrees) { + var s = new MDFormat().formatList(docTrees); + do { + var r = s.replaceAll("\n\n\n", "\n\n"); + if (r.equals(s)) { + return s; + } + s = r; + } while (true); + } + + static class RootTarget extends Target { + RootTarget() { + super(""); + } + } + + abstract static class ListTarget extends Target { + final String itemPrefix; + + public ListTarget(String itemPrefix, String indent) { + super(indent); + this.itemPrefix = itemPrefix; + } + + ListItemTarget newItem() { + return new ListItemTarget(indent); + } + } + + static class OrderedListTarget extends ListTarget { + OrderedListTarget(String indent) { + super("\n" + indent + " 1. ", indent + " "); + } + } + + static class UnorderedListTarget extends ListTarget { + UnorderedListTarget(String indent) { + super("\n" + indent + " * ", indent + " "); + } + } + + static class ListItemTarget extends Target { + ListItemTarget(String indent) { + super(indent); + } + } + + static class ATagTarget extends Target { + final Map attributes; + + ATagTarget(String indent, Map attributes) { + super(indent); + this.attributes = attributes; + } + } + + abstract static class Target { + final StringBuilder text = new StringBuilder(); + final String indent; + + Target(String indent) { + this.indent = indent; + } + + void addText(String text) { + addTextOrCode(text, false); + } + + void addTextOrCode(String text, boolean code) { + var t = text.replaceAll("\n", " "); + t = t.replaceFirst("^\\s*", ""); + + if (t.isEmpty() && !text.isEmpty()) { + maybeAddSeparator(); + return; + } + + var e = text.replaceFirst("\\s*$", ""); + + if (text.charAt(0) != t.charAt(0)) { + maybeAddSeparator(); + } + + if (code) { + this.text.append('`'); + } + this.text.append(t); + if (code) { + this.text.append('`'); + } + + if (!e.equals(t)) { + maybeAddSeparator(); + } + } + + void addCode(String text) { + addTextOrCode(text, true); + } + + void maybeAddSeparator() { + var len = text.length(); + if (len == 0) { + return; + } + if (Character.isWhitespace(text.charAt(len - 1))) { + return; + } + text.append(' '); + } + + void trimRight() { + var l = text.length(); + while (l > 0 && Character.isWhitespace(text.charAt(l - 1))) { + text.setLength(--l); + } + } + } + + private class MDFormat { + final Deque stack = new ArrayDeque<>(); + + String formatList(List docTrees) { + var root = new RootTarget(); + stack.add(root); + process(docTrees); + return root.text.toString(); + } + + private void process(List docTrees) { + for (var docTree : docTrees) { + process(docTree); + } + } + + private void process(DocTree doc) { + var target = requireNonNull(stack.peekLast()); + switch (doc.getKind()) { + case DOC_COMMENT: + { + var docCommentTree = (DocCommentTree) doc; + + var first = docCommentTree.getFirstSentence(); + for (var ch : first) { + process(ch); + } + var body = docCommentTree.getBody(); + for (var ch : body) { + process(ch); + } + + // `block` has all the `@see` and such + var block = docCommentTree.getBlockTags(); + process(block); + } + break; + case COMMENT: + { + // CommentTree commentTree = (CommentTree) doc; + // String body = commentTree.getBody(); + // target.text.append(body); + } + break; + + case CODE: + { + // @code + var literalTree = (LiteralTree) doc; + var body = literalTree.getBody(); + target.addCode(body.getBody()); + } + break; + case LINK: + { + // @link + link((LinkTree) doc, target, true); + } + break; + case LINK_PLAIN: + { + // @linkplain + link((LinkTree) doc, target, false); + } + break; + case VALUE: + { + // @value + value((ValueTree) doc, target); + } + break; + case INDEX: + { + // @index + var indexTree = (IndexTree) doc; + // var description = indexTree.getDescription(); + var searchTerm = indexTree.getSearchTerm(); + process(searchTerm); + } + break; + case SUMMARY: + { + // @summary (alternative to first sentence) + var summaryTree = (SummaryTree) doc; + var summary = summaryTree.getSummary(); + // no special handling here + process(summary); + } + break; + case DOC_ROOT: + case DOC_TYPE: + case INHERIT_DOC: + // ignored "inline" tags + break; + + case ENTITY: + { + // HTML entity + var entityTree = (EntityTree) doc; + var unescaped = StringEscapeUtils.unescapeHtml4(entityTree.toString()); + target.addText(unescaped); + } + break; + case IDENTIFIER: + { + // identifier + // IdentifierTree identifierTree = (IdentifierTree) doc; + // Name name = identifierTree.getName(); + } + break; + case REFERENCE: + { + // reference tree + var referenceTree = (ReferenceTree) doc; + var signature = referenceTree.getSignature(); + target.text.append(signature); + } + break; + + case TEXT: + { + var textTree = (TextTree) doc; + // TODO process HTML entities ? + target.addText(textTree.getBody()); + } + break; + case LITERAL: + { + var literalTree = (LiteralTree) doc; + var textTree = literalTree.getBody(); + target.addText(textTree.getBody()); + } + break; + case ERRONEOUS: + { + // invalid text + var erroneousTree = (ErroneousTree) doc; + target.addText(erroneousTree.getBody()); + } + break; + + case UNKNOWN_BLOCK_TAG: + { + var unknownBlockTagTree = (UnknownBlockTagTree) doc; + var content = unknownBlockTagTree.getContent(); + process(content); + } + break; + case UNKNOWN_INLINE_TAG: + { + var unknownInlineTagTree = (UnknownInlineTagTree) doc; + var content = unknownInlineTagTree.getContent(); + process(content); + } + break; + case START_ELEMENT: + { + // start HTML element + var startElementTree = (StartElementTree) doc; + var name = startElementTree.getName(); + var attributes = startElementTree.getAttributes(); + // boolean selfClosing = startElementTree.isSelfClosing(); + + var attributeMap = + attributes.stream() + .filter(d -> d.getKind() == DocTree.Kind.ATTRIBUTE) + .map(AttributeTree.class::cast) + .collect( + Collectors.toMap( + a -> a.getName().toString().toLowerCase(Locale.ROOT), + a -> format(a.getValue()))); + + switch (name.toString().toLowerCase(Locale.ROOT)) { + case "p": + target.text.append("\n\n").append(target.indent); + break; + case "a": + target.text.append('['); + stack.addLast(new ATagTarget(target.indent, attributeMap)); + break; + case "em": + case "i": + target.text.append('_'); + break; + case "b": + target.text.append("**"); + break; + case "ol": + target.text.append("\n\n"); + stack.addLast(new OrderedListTarget(target.indent)); + break; + case "ul": + target.text.append("\n\n"); + stack.addLast(new UnorderedListTarget(target.indent)); + break; + case "li": + while (!(target instanceof ListTarget listTarget)) { + var last = stack.removeLast(); + target = stack.peekLast(); + requireNonNull(target).text.append(last.text); + } + target.text.append(listTarget.itemPrefix); + stack.addLast(listTarget.newItem()); + break; + case "code": + target.text.append('`'); + break; + default: + break; + } + + process(attributes); + } + break; + case END_ELEMENT: + { + // end HTML element + var endElementTree = (EndElementTree) doc; + var name = endElementTree.getName(); + switch (name.toString().toLowerCase(Locale.ROOT)) { + case "p": + // noop + break; + case "a": + var a = (ATagTarget) stack.removeLast(); + target = stack.peekLast(); + requireNonNull(target).addText(a.text.toString()); + target.text.append("](").append(a.attributes.get("href")).append(")"); + break; + case "em": + case "i": + target.trimRight(); + target.text.append('_'); + break; + case "b": + target.trimRight(); + target.text.append("**"); + break; + case "ol": + case "ul": + while (!(target instanceof ListTarget)) { + var last = stack.removeLast(); + target = stack.peekLast(); + requireNonNull(target).text.append(last.text); + } + var list = stack.removeLast(); + target = stack.peekLast(); + requireNonNull(target).text.append(list.text).append("\n\n"); + break; + case "li": + while (!(target instanceof ListTarget)) { + var last = stack.removeLast(); + target = stack.peekLast(); + requireNonNull(target).text.append(last.text); + } + requireNonNull(stack.peekLast()).text.append(stack.removeLast().text); + break; + case "code": + target.text.append('`'); + break; + default: + break; + } + } + break; + case ATTRIBUTE: + // Attribute of an HTML tag + break; + + // "block" tags + case AUTHOR: + case DEPRECATED: + case EXCEPTION: + case HIDDEN: + case PARAM: + case PROVIDES: + case RETURN: + case SEE: + case SERIAL: + case SERIAL_DATA: + case SERIAL_FIELD: + case SINCE: + case THROWS: + case USES: + case VERSION: + // ignored "block" tags + break; + + case OTHER: + default: + break; + } + } + + private void value(ValueTree valueTree, Target target) { + // TODO resolve reference properly + var reference = valueTree.getReference(); + var signature = reference.getSignature().trim(); + if (signature.startsWith("#") && element != null) { + var referenced = + element.getEnclosingElement().getEnclosedElements().stream() + .filter(enc -> enc.getSimpleName().toString().equals(signature.substring(1))) + .findFirst(); + if (referenced.isPresent()) { + var ref = referenced.get(); + if (ref instanceof VariableElement variableElement) { + var value = variableElement.getConstantValue(); + if (value instanceof String) { + target.text.append('"').append(value).append('"'); + } else { + target.text.append(value); + } + } + } else { + target.text.append(signature); + } + } else { + process(reference); + } + } + + private void link(LinkTree linkTree, Target target, boolean codeValue) { + // TODO resolve reference properly + + var label = linkTree.getLabel(); + if (!label.isEmpty()) { + process(label); + } + + var reference = linkTree.getReference(); + var signature = reference.getSignature().trim(); + target.maybeAddSeparator(); + target.text.append("("); + if (codeValue) { + target.text.append('`'); + } + if (signature.startsWith("#") && element != null) { + var referenced = + element.getEnclosingElement().getEnclosedElements().stream() + .filter(enc -> enc.getSimpleName().toString().equals(signature.substring(1))) + .findFirst(); + if (referenced.isPresent()) { + var ref = referenced.get(); + if (ref instanceof VariableElement variableElement) { + var value = variableElement.getConstantValue(); + target.text.append(value); + } + } else { + target.text.append(signature); + } + } else { + process(reference); + } + if (codeValue) { + target.text.append('`'); + } + target.text.append(')'); + } + } +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/MarkdownPropertyFormatter.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/MarkdownPropertyFormatter.java new file mode 100644 index 0000000000..d434748d04 --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/MarkdownPropertyFormatter.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +public class MarkdownPropertyFormatter extends MarkdownFormatter { + + private final PropertyInfo propertyInfo; + + public MarkdownPropertyFormatter(PropertyInfo propertyInfo) { + super(propertyInfo.propertyElement(), propertyInfo.doc()); + this.propertyInfo = propertyInfo; + } + + public String propertyName() { + return propertyInfo.propertyName(); + } + + public String propertySuffix() { + return propertyInfo.propertySuffix(); + } + + public String propertyType() { + return '`' + propertyInfo.simplifiedTypeName() + '`'; + } +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/MarkdownTypeFormatter.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/MarkdownTypeFormatter.java new file mode 100644 index 0000000000..001f16aac6 --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/MarkdownTypeFormatter.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import com.sun.source.doctree.DocCommentTree; +import javax.lang.model.element.Element; + +public class MarkdownTypeFormatter extends MarkdownFormatter { + + public MarkdownTypeFormatter(Element element, DocCommentTree commentTree) { + super(element, commentTree); + } +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertiesConfigItem.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertiesConfigItem.java new file mode 100644 index 0000000000..02041cb3ad --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertiesConfigItem.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import com.sun.source.doctree.DocCommentTree; +import java.util.Optional; +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; + +public class PropertiesConfigItem implements PropertyInfo { + private final VariableElement field; + private final DocCommentTree doc; + + public PropertiesConfigItem(VariableElement field, DocCommentTree doc) { + this.field = field; + this.doc = doc; + } + + @Override + public Element propertyElement() { + return field; + } + + @Override + public DocCommentTree doc() { + return doc; + } + + @Override + public String defaultValue() { + return ""; + } + + @Override + public String simplifiedTypeName() { + return ""; + } + + @Override + public String propertyName() { + return field.getConstantValue().toString(); + } + + @Override + public String propertySuffix() { + return ""; + } + + @Override + public Optional> groupType() { + return Optional.empty(); + } +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertiesConfigPageGroup.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertiesConfigPageGroup.java new file mode 100644 index 0000000000..91f7bb9f2f --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertiesConfigPageGroup.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import static javax.lang.model.element.ElementKind.FIELD; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.AbstractElementVisitor8; +import jdk.javadoc.doclet.DocletEnvironment; +import org.apache.polaris.docs.ConfigDocs.ConfigItem; + +public class PropertiesConfigPageGroup { + + public static final Set EXPECTED_FIELD_MODIFIERS = + Set.of(Modifier.FINAL, Modifier.STATIC); + + private final String name; + + private final Map> items = new LinkedHashMap<>(); + + public PropertiesConfigPageGroup(String name) { + this.name = name; + } + + public Map> sectionItems() { + var sections = new LinkedHashMap>(); + forEachSection(sections::put); + return sections; + } + + public void forEachSection(BiConsumer> consumer) { + var processedSections = new HashSet(); + + for (var e : items.entrySet()) { + var section = e.getKey(); + if (processedSections.add(section)) { + consumer.accept(section, e.getValue()); + } + } + } + + public String name() { + return name; + } + + ElementVisitor visitor() { + return new AbstractElementVisitor8<>() { + @Override + public Void visitPackage(PackageElement e, DocletEnvironment env) { + return null; + } + + @Override + public Void visitType(TypeElement e, DocletEnvironment env) { + return e.accept(this, env); + } + + @Override + public Void visitVariable(VariableElement e, DocletEnvironment env) { + var configItem = e.getAnnotation(ConfigItem.class); + if (configItem != null) { + var propertyKey = e.getConstantValue(); + if (e.getKind() == FIELD + && e.getModifiers().containsAll(EXPECTED_FIELD_MODIFIERS) + && propertyKey instanceof String) { + var docComment = env.getDocTrees().getDocCommentTree(e); + + items + .computeIfAbsent(configItem.section(), g -> new ArrayList<>()) + .add(new PropertiesConfigItem(e, docComment)); + } + // else: @ConfigItem placed on something else than a constant String field. + } + return null; + } + + @Override + public Void visitExecutable(ExecutableElement e, DocletEnvironment env) { + return null; + } + + @Override + public Void visitTypeParameter(TypeParameterElement e, DocletEnvironment env) { + return null; + } + }; + } +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertiesConfigs.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertiesConfigs.java new file mode 100644 index 0000000000..2bf607d1c1 --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertiesConfigs.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import java.util.HashMap; +import java.util.Map; +import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.AbstractElementVisitor8; +import jdk.javadoc.doclet.DocletEnvironment; +import org.apache.polaris.docs.ConfigDocs.ConfigPageGroup; + +public class PropertiesConfigs { + private final DocletEnvironment env; + private final Map pages = new HashMap<>(); + + public PropertiesConfigs(DocletEnvironment env) { + this.env = env; + } + + public PropertiesConfigPageGroup page(String name) { + return pages.computeIfAbsent(name, PropertiesConfigPageGroup::new); + } + + public Iterable pages() { + return pages.values(); + } + + public ElementVisitor visitor() { + return new AbstractElementVisitor8<>() { + @Override + public Void visitPackage(PackageElement e, Void ignore) { + return null; + } + + @Override + public Void visitType(TypeElement e, Void ignore) { + ConfigPageGroup configPageGroup = e.getAnnotation(ConfigPageGroup.class); + if (configPageGroup != null) { + PropertiesConfigPageGroup page = page(configPageGroup.name()); + + e.getEnclosedElements().forEach(element -> element.accept(page.visitor(), env)); + } + return null; + } + + @Override + public Void visitVariable(VariableElement e, Void ignore) { + return null; + } + + @Override + public Void visitExecutable(ExecutableElement e, Void ignore) { + return null; + } + + @Override + public Void visitTypeParameter(TypeParameterElement e, Void ignore) { + return null; + } + }; + } +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertyInfo.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertyInfo.java new file mode 100644 index 0000000000..5c8fa72f07 --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/PropertyInfo.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import com.sun.source.doctree.DocCommentTree; +import java.util.Optional; +import javax.lang.model.element.Element; + +public interface PropertyInfo { + Element propertyElement(); + + String propertyName(); + + String propertySuffix(); + + String simplifiedTypeName(); + + String defaultValue(); + + DocCommentTree doc(); + + Optional> groupType(); +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/ReferenceConfigDocsGenerator.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/ReferenceConfigDocsGenerator.java new file mode 100644 index 0000000000..a891a25c56 --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/ReferenceConfigDocsGenerator.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import static javax.tools.DocumentationTool.Location.DOCUMENTATION_OUTPUT; +import static javax.tools.JavaFileObject.Kind.SOURCE; +import static javax.tools.StandardLocation.CLASS_PATH; +import static javax.tools.StandardLocation.SOURCE_PATH; + +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.concurrent.Callable; +import javax.tools.DiagnosticListener; +import javax.tools.DocumentationTool; +import javax.tools.JavaFileObject; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +/** + * Tool to run {@link DocGenDoclet}, the markdown docs generation. + * + *

This is built as a separate tool, because running {@link DocGenDoclet} inside {@code javadoc} + * leads to wrong and incomplete results for smallrye-config documentation due to class loader + * isolation issues. {@code javadoc} uses a separate {@link ClassLoader} for the doclet, which + * breaks the proper inspection via smallrye-config's using {@link + * io.smallrye.config.ConfigMappingInterface}: the default values are missing, properties from + * supertypes are missing and the property names are usually wrong. + * + *

This separate tool approach makes the integration into Gradle easier, too. + */ +@Command( + name = "generate", + mixinStandardHelpOptions = true, + description = "Generate markdown documentation") +public class ReferenceConfigDocsGenerator implements Callable { + @Option( + names = {"-cp", "--classpath"}, + arity = "*", + split = ":") + List classpath = new ArrayList<>(); + + @Option( + names = {"-sp", "--sourcepath"}, + arity = "*", + split = ":") + List sourcepath = new ArrayList<>(); + + @Option( + names = {"-d", "--destination"}, + arity = "1", + required = true) + Path output; + + @Option(names = {"-v", "--verbose"}) + boolean verbose; + + public ReferenceConfigDocsGenerator() {} + + public ReferenceConfigDocsGenerator( + List sourcepath, List classpath, Path output, boolean verbose) { + this.classpath = classpath; + this.sourcepath = sourcepath; + this.output = output; + this.verbose = verbose; + } + + @Override + public Integer call() throws Exception { + var docTool = + ServiceLoader.load(DocumentationTool.class) + .findFirst() + .orElseThrow(() -> new IllegalStateException("No DocumentationTool instance present")); + + Files.createDirectories(output); + + var fileManager = docTool.getStandardFileManager(null, null, null); + fileManager.setLocationFromPaths(SOURCE_PATH, sourcepath); + fileManager.setLocationFromPaths(CLASS_PATH, classpath); + fileManager.setLocationFromPaths(DOCUMENTATION_OUTPUT, List.of(output)); + + var sourceFiles = fileManager.list(SOURCE_PATH, "", Set.of(SOURCE), true); + + var options = + List.of( + "--ignore-source-errors", + "-Xmaxwarns", + verbose ? "100" : "1", + "-Xmaxerrs", + verbose ? "100" : "1", + "-d", + output.toString()); + + DiagnosticListener diagnostics = verbose ? null : diagnostic -> {}; + var out = verbose ? null : Writer.nullWriter(); + + var docTask = + docTool.getTask(out, fileManager, diagnostics, DocGenDoclet.class, options, sourceFiles); + + var result = docTask.call(); + + return result ? 0 : 1; + } + + public static void main(String[] args) { + var tool = new ReferenceConfigDocsGenerator(); + var commandLine = + new CommandLine(tool) + .setExecutionExceptionHandler( + (ex, cmd, parseResult) -> { + // Print the full stack trace in all other cases. + cmd.getErr().println(cmd.getColorScheme().richStackTraceString(ex)); + return cmd.getExitCodeExceptionMapper() != null + ? cmd.getExitCodeExceptionMapper().getExitCode(ex) + : cmd.getCommandSpec().exitCodeOnExecutionException(); + }); + System.exit(commandLine.execute(args)); + } +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigMappingInfo.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigMappingInfo.java new file mode 100644 index 0000000000..f7ce63a189 --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigMappingInfo.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import com.sun.source.doctree.DocCommentTree; +import io.smallrye.config.ConfigMapping.NamingStrategy; +import io.smallrye.config.ConfigMappingInterface; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.AbstractElementVisitor8; +import jdk.javadoc.doclet.DocletEnvironment; + +public final class SmallRyeConfigMappingInfo { + private final String prefix; + private final List configMappingInterfaces = new ArrayList<>(); + private final Map> methodExecutables = new HashMap<>(); + private final Set propertiesMethodNameOrder = new LinkedHashSet<>(); + private final Map methodNameToProperty = + new LinkedHashMap<>(); + private DocCommentTree typeComment; + private TypeElement element; + + SmallRyeConfigMappingInfo(String prefix) { + this.prefix = prefix; + } + + public Stream properties(DocletEnvironment env) { + for (var configMappingInterface : configMappingInterfaces) { + for (var property : configMappingInterface.getProperties()) { + var methodName = property.getMethod().getName(); + methodNameToProperty.putIfAbsent(methodName, property); + } + } + + return propertiesMethodNameOrder.stream() + .map(name -> buildPropertyInfo(env, name)) + .filter(Objects::nonNull); + } + + String prefix() { + return prefix; + } + + TypeElement element() { + return element; + } + + DocCommentTree typeComment() { + return typeComment; + } + + private SmallRyeConfigPropertyInfo buildPropertyInfo(DocletEnvironment env, String methodName) { + var property = methodNameToProperty.get(methodName); + if (property == null) { + return null; + } + + DocCommentTree doc = null; + + var executables = methodExecutables.get(methodName); + if (executables == null) { + return null; + } + + ExecutableElement docExec = null; + + for (var executable : executables) { + if (doc == null) { + doc = env.getDocTrees().getDocCommentTree(executable); + if (doc != null) { + docExec = executable; + } + } + } + + if (docExec == null) { + docExec = executables.get(0); + } + + var namingStrategy = + configMappingInterfaces.isEmpty() + ? NamingStrategy.KEBAB_CASE + : configMappingInterfaces.get(0).getNamingStrategy(); + var propertyName = property.getPropertyName(namingStrategy); + + return new SmallRyeConfigPropertyInfo(docExec, property, propertyName, doc); + } + + void processType( + DocletEnvironment env, + ConfigMappingInterface configMappingInterface, + TypeElement typeElement) { + configMappingInterfaces.add(configMappingInterface); + + // TODO use the order of the properties as in the source file? or define another annotation? + + // Collect properties defined by the current type-element (type that declares a + // `@ConfigMapping`) + for (var property : configMappingInterface.getProperties()) { + var method = property.getMethod(); + methodExecutables.putIfAbsent(method.getName(), new ArrayList<>()); + } + + typeElement.accept(executablesVisitor, null); + if (typeComment == null) { + typeComment = env.getDocTrees().getDocCommentTree(typeElement); + if (typeComment != null) { + this.element = typeElement; + } + } + } + + final ElementVisitor executablesVisitor = + new AbstractElementVisitor8<>() { + + @Override + public Void visitType(TypeElement e, Void unused) { + for (var enclosedElement : e.getEnclosedElements()) { + if (enclosedElement.asType().getKind() == TypeKind.EXECUTABLE) { + enclosedElement.accept(this, null); + } + } + return null; + } + + @Override + public Void visitExecutable(ExecutableElement e, Void unused) { + if (e.getKind() == ElementKind.METHOD + && (e.getModifiers().contains(Modifier.ABSTRACT) + || e.getModifiers().contains(Modifier.DEFAULT)) + && e.getParameters().isEmpty() + && e.getReturnType().getKind() != TypeKind.VOID) { + var methodName = e.getSimpleName().toString(); + propertiesMethodNameOrder.add(methodName); + var methodList = methodExecutables.get(methodName); + if (methodList != null) { + methodList.add(e); + } + } + return null; + } + + @Override + public Void visitPackage(PackageElement e, Void unused) { + return null; + } + + @Override + public Void visitVariable(VariableElement e, Void unused) { + return null; + } + + @Override + public Void visitTypeParameter(TypeParameterElement e, Void unused) { + return null; + } + }; +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigPropertyInfo.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigPropertyInfo.java new file mode 100644 index 0000000000..9b534fb473 --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigPropertyInfo.java @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import com.sun.source.doctree.DocCommentTree; +import io.smallrye.config.ConfigMappingInterface.Property; +import java.net.URI; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.stream.Collectors; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import org.apache.polaris.docs.ConfigDocs.ConfigItem; +import org.apache.polaris.docs.ConfigDocs.ConfigPropertyName; + +public class SmallRyeConfigPropertyInfo implements PropertyInfo { + private final Property property; + private final String propertyName; + private final DocCommentTree doc; + private final ExecutableElement element; + + SmallRyeConfigPropertyInfo( + ExecutableElement element, Property property, String propertyName, DocCommentTree doc) { + this.element = element; + this.property = property; + this.propertyName = propertyName; + this.doc = doc; + } + + @Override + public Element propertyElement() { + return element; + } + + @Override + public String propertyName() { + return propertyName; + } + + public Optional prefixOverride() { + var item = element.getAnnotation(ConfigItem.class); + if (item != null) { + var section = item.section(); + if (section != null && !section.isEmpty()) { + return Optional.of(section); + } + } + return Optional.empty(); + } + + public boolean sectionDocFromType() { + var item = element.getAnnotation(ConfigItem.class); + return item != null && item.sectionDocFromType(); + } + + @Override + public String propertySuffix() { + if (property.isMap()) { + var ci = element.getAnnotation(ConfigPropertyName.class); + return ci == null || ci.value().isEmpty() ? "name" : ci.value(); + } + return ""; + } + + @Override + public String simplifiedTypeName() { + return simplifiedTypeName(property); + } + + public boolean isSettableType() { + return isSettableType(property); + } + + @Override + public Optional> groupType() { + var p = property; + if (p.isOptional()) { + p = p.asOptional().getNestedProperty(); + } + if (p.isCollection()) { + p = p.asCollection().getElement(); + } + if (p.isMap()) { + p = p.asMap().getValueProperty(); + } + return p.isGroup() + ? Optional.of(p.asGroup().getGroupType().getInterfaceType()) + : Optional.empty(); + } + + public static boolean isSettableType(Property property) { + if (property.isOptional()) { + var nested = property.asOptional().getNestedProperty(); + return isSettableType(nested); + } + if (property.isCollection()) { + return true; + } + if (property.isMap()) { + var map = property.asMap(); + var value = map.getValueProperty(); + return isSettableType(value); + } + if (property.isPrimitive()) { + return true; + } + if (property.isLeaf()) { + return true; + } + if (property.isGroup()) { + // Represents a type that consists of multiple fields/properties, which are documented + // underneath this property. + return false; + } + throw new UnsupportedOperationException("Don't know how to handle " + property); + } + + public static String simplifiedTypeName(Property property) { + if (property.isOptional()) { + var nested = property.asOptional().getNestedProperty(); + return simplifiedTypeName(nested); + } + if (property.isCollection()) { + var coll = property.asCollection(); + var element = coll.getElement(); + return "list of " + simplifiedTypeName(element); + } + if (property.isMap()) { + var map = property.asMap(); + var value = map.getValueProperty(); + return simplifiedTypeName(value); + } + if (property.isPrimitive()) { + return property.asPrimitive().getPrimitiveType().getSimpleName(); + } + if (property.isLeaf()) { + var leaf = property.asLeaf(); + var rawType = leaf.getValueRawType(); + if (rawType.isEnum()) { + return Arrays.stream(rawType.getEnumConstants()) + .map(Enum.class::cast) + .map(Enum::name) + .collect(Collectors.joining(", ")); + } + if (property.hasConvertWith()) { + // A smallrye-config converter always takes a string. + return "string"; + } + if (rawType == OptionalInt.class) { + return "int"; + } + if (rawType == OptionalLong.class) { + return "long"; + } + if (rawType == OptionalDouble.class) { + return "double"; + } + if (rawType == Boolean.class) { + return "boolean"; + } + if (rawType == Byte.class) { + return "byte"; + } + if (rawType == Short.class) { + return "short"; + } + if (rawType == Integer.class) { + return "int"; + } + if (rawType == Long.class) { + return "long"; + } + if (rawType == Float.class) { + return "float"; + } + if (rawType == Double.class) { + return "double"; + } + if (rawType == Character.class) { + return "char"; + } + if (rawType == String.class) { + return "string"; + } + if (rawType == Duration.class) { + return "duration"; + } + if (rawType == Instant.class) { + return "instant"; + } + if (rawType == URI.class) { + return "uri"; + } + if (rawType == Path.class) { + return "path"; + } + return rawType.getSimpleName(); + } + if (property.isGroup()) { + // Represents a type that consists of multiple fields/properties, which are documented + // underneath this property. + return ""; + } + throw new UnsupportedOperationException("Don't know how to handle " + property); + } + + @Override + public DocCommentTree doc() { + return doc; + } + + @Override + public String defaultValue() { + return defaultValue(property); + } + + public static String defaultValue(Property property) { + if (property.isOptional()) { + var nested = property.asOptional().getNestedProperty(); + return defaultValue(nested); + } + if (property.isPrimitive()) { + var primitive = property.asPrimitive(); + return primitive.hasDefaultValue() ? primitive.getDefaultValue() : null; + } + if (property.isLeaf()) { + var leaf = property.asLeaf(); + return leaf.hasDefaultValue() ? leaf.getDefaultValue() : null; + } + return null; + } +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigSectionPage.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigSectionPage.java new file mode 100644 index 0000000000..f8e3584c0f --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigSectionPage.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import com.sun.source.doctree.DocCommentTree; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.element.TypeElement; + +public class SmallRyeConfigSectionPage { + final String section; + final List lines = new ArrayList<>(); + final TypeElement typeElement; + DocCommentTree comment; + boolean empty = true; + int sectionRef; + + SmallRyeConfigSectionPage(String section, TypeElement typeElement, DocCommentTree comment) { + this.section = section; + this.typeElement = typeElement; + this.comment = comment; + } + + public void addProperty( + String propertyFullName, + SmallRyeConfigPropertyInfo propertyInfo, + MarkdownPropertyFormatter md) { + if (empty) { + lines.add("| Property | Default Value | Type | Description |"); + lines.add("|----------|---------------|------|-------------|"); + + empty = false; + } + + var fullNameCode = ('`' + propertyFullName + '`').replaceAll("``", ""); + var dv = propertyInfo.defaultValue(); + + lines.add( + "| " + + fullNameCode + + " | " + + (dv != null ? (dv.isEmpty() ? "(empty)" : '`' + dv + '`') : "") + + " | " + + md.propertyType() + + " | " + + md.description().replaceAll("\n", "
") + + " |"); + } + + public boolean isEmpty() { + return comment == null && empty; + } + + public void writeTo(PrintWriter pw) { + if (comment != null) { + var typeFormatter = new MarkdownTypeFormatter(typeElement, comment); + pw.println(typeFormatter.description().trim()); + pw.println(); + } + + lines.forEach(pw::println); + } + + public void incrementSectionRef() { + if (++sectionRef > 1) { + comment = null; + } + } +} diff --git a/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigs.java b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigs.java new file mode 100644 index 0000000000..3895f17402 --- /dev/null +++ b/tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/SmallRyeConfigs.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import static java.util.Arrays.asList; + +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.ConfigMappingInterface; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.AbstractElementVisitor8; +import javax.tools.StandardLocation; +import jdk.javadoc.doclet.DocletEnvironment; + +public class SmallRyeConfigs { + private final DocletEnvironment env; + + private final ClassLoader classLoader; + + private final Map configMappingInfos = new HashMap<>(); + private final Map configMappingByType = new HashMap<>(); + + public SmallRyeConfigs(DocletEnvironment env) { + this.classLoader = env.getJavaFileManager().getClassLoader(StandardLocation.CLASS_PATH); + this.env = env; + } + + Collection configMappingInfos() { + return configMappingInfos.values(); + } + + static String concatWithDot(String s1, String s2) { + var sb = new StringBuilder(); + var v1 = s1 != null && !s1.isEmpty(); + var v2 = s2 != null && !s2.isEmpty(); + if (v1) { + sb.append(s1); + } + if (v1 && v2) { + sb.append('.'); + } + if (v2) { + sb.append(s2); + } + return sb.toString(); + } + + public SmallRyeConfigMappingInfo getConfigMappingInfo(Class type) { + var typeName = type.getName(); + var info = configMappingByType.get(typeName); + if (info == null) { + info = new SmallRyeConfigMappingInfo(""); + configMappingByType.put(typeName, info); + TypeElement elem = env.getElementUtils().getTypeElement(typeName); + if (elem == null) { + throw new NullPointerException("Type " + typeName + " not found"); + } + elem.accept(visitor(), null); + } + return info; + } + + ElementVisitor visitor() { + return new AbstractElementVisitor8<>() { + + @Override + public Void visitPackage(PackageElement e, Void ignore) { + return null; + } + + @Override + public Void visitType(TypeElement e, Void ignore) { + switch (e.getKind()) { + case CLASS: + case INTERFACE: + var configMapping = e.getAnnotation(ConfigMapping.class); + SmallRyeConfigMappingInfo mappingInfo; + ConfigMappingInterface configMappingInterface; + + var className = e.getQualifiedName().toString(); + + Class clazz; + if (configMapping != null) { + + mappingInfo = + configMappingInfos.computeIfAbsent( + configMapping.prefix(), SmallRyeConfigMappingInfo::new); + + clazz = loadClass(className); + try { + configMappingInterface = ConfigMappingInterface.getConfigurationInterface(clazz); + } catch (RuntimeException ex) { + throw new RuntimeException("Failed to process mapped " + clazz, ex); + } + configMappingByType.put( + configMappingInterface.getInterfaceType().getName(), mappingInfo); + } else { + mappingInfo = configMappingByType.get(className); + if (mappingInfo == null) { + return null; + } + clazz = loadClass(className); + try { + configMappingInterface = ConfigMappingInterface.getConfigurationInterface(clazz); + } catch (RuntimeException ex) { + throw new RuntimeException("Failed to process implicitly added " + clazz, ex); + } + } + + mappingInfo.processType(env, configMappingInterface, e); + + var seen = new HashSet>(); + var remaining = new ArrayDeque<>(asList(configMappingInterface.getSuperTypes())); + while (!remaining.isEmpty()) { + var superType = remaining.removeFirst(); + if (!seen.add(superType.getInterfaceType())) { + continue; + } + + remaining.addAll(asList(superType.getSuperTypes())); + + var superTypeElement = + env.getElementUtils().getTypeElement(superType.getInterfaceType().getName()); + mappingInfo.processType(env, superType, superTypeElement); + } + + // Under some (not really understood) circumstances, + // ConfigMappingInterface.getSuperTypes() does not return really all extended + // interfaces. So we traverse the super types via reflection here, skipping all the + // types that have been visited above. + var remainingClasses = new ArrayDeque>(); + if (clazz.getSuperclass() != null) { + remainingClasses.add(clazz.getSuperclass()); + } + remainingClasses.addAll(asList(clazz.getInterfaces())); + while (!remainingClasses.isEmpty()) { + var c = remainingClasses.removeFirst(); + if (!seen.add(c)) { + continue; + } + + var superTypeElement = env.getElementUtils().getTypeElement(c.getName()); + mappingInfo.processType(env, configMappingInterface, superTypeElement); + + if (c.getSuperclass() != null) { + remainingClasses.add(c.getSuperclass()); + } + remainingClasses.addAll(asList(c.getInterfaces())); + } + break; + default: + break; + } + + return null; + } + + private Class loadClass(String className) { + try { + return Class.forName(className, false, classLoader); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public Void visitVariable(VariableElement e, Void ignore) { + return null; + } + + @Override + public Void visitExecutable(ExecutableElement e, Void ignore) { + return null; + } + + @Override + public Void visitTypeParameter(TypeParameterElement e, Void ignore) { + return null; + } + }; + } +} diff --git a/tools/config-docs/generator/src/test/java/org/apache/polaris/docs/generator/TestDocGenTool.java b/tools/config-docs/generator/src/test/java/org/apache/polaris/docs/generator/TestDocGenTool.java new file mode 100644 index 0000000000..daee695c6f --- /dev/null +++ b/tools/config-docs/generator/src/test/java/org/apache/polaris/docs/generator/TestDocGenTool.java @@ -0,0 +1,271 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.docs.generator; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; + +@ExtendWith(SoftAssertionsExtension.class) +public class TestDocGenTool { + @InjectSoftAssertions protected SoftAssertions soft; + + @Test + public void docGenTool(@TempDir Path dir) throws Exception { + var classpath = + Arrays.stream(System.getProperty("testing.libraries").split(":")) + .map(Paths::get) + .collect(Collectors.toList()); + + var tool = + new ReferenceConfigDocsGenerator(List.of(Paths.get("src/test/java")), classpath, dir, true); + var result = tool.call(); + soft.assertThat(result).isEqualTo(0); + + var fileProps = dir.resolve("props-main.md"); + var filePropsA = dir.resolve("props-a_a_a.md"); + var filePropsB = dir.resolve("props-b_b.md"); + soft.assertThat(fileProps) + .isRegularFile() + .content() + .isEqualTo( + """ + | Property | Description | + |----------|-------------| + | `property.one` | A property. "111" is the default value.

Some text there. | + | `property.four` | Four. | + """); + soft.assertThat(filePropsA) + .isRegularFile() + .content() + .isEqualTo( + """ + | Property | Description | + |----------|-------------| + | `property.three` | Some (`value two`) more (`one two three`).

* foo
* bar
* baz

blah

* FOO
* BAR
* BAZ

* foo
* bar
* baz

| + """); + soft.assertThat(filePropsB) + .isRegularFile() + .content() + .isEqualTo( + """ + | Property | Description | + |----------|-------------| + | `property.two` | Some summary for checkstyle.

Another property, need some words to cause a line break in the value tag here "111" for testing.

Some text there.

_Deprecated_ this is deprecated because of (`property.three`). | + | `property.five` | Five. | + """); + + var fileMyPrefix = dir.resolve("smallrye-my_prefix.md"); + var fileMyTypes = dir.resolve("smallrye-my_types.md"); + var fileExtremelyNested = dir.resolve("smallrye-extremely_nested.md"); + var fileVeryNested = dir.resolve("smallrye-very_nested.md"); + + soft.assertThat(fileMyPrefix) + .isRegularFile() + .content() + .isEqualTo( + """ + The docs for `my.prefix`.\s + + * Some \s + * unordered \s + * list \s + + Some more text. \s + + 1. one \s + 1. two \s + 1. three + + | Property | Default Value | Type | Description | + |----------|---------------|------|-------------| + | `my.prefix.some-weird-name` | `some-default` | `string` | Something that configures something. | + | `my.prefix.some-duration` | | `duration` | A duration of something. | + | `my.prefix.nested.other-int` | | `int` | | + | `my.prefix.nested.boxed-double` | | `double` |

_Deprecated_ | + | `my.prefix.list-of-strings` | | `list of string` | Example & < > "   € ® ©.

* ` session-iam-statements[0]= {"Effect":"Allow", "Action":"s3:*", "Resource":"arn:aws:s3:::*/alwaysAllowed/*"} `
* ` session-iam-statements[1]= {"Effect":"Deny", "Action":"s3:*", "Resource":"arn:aws:s3:::*/blocked/*"} `

| + | `my.prefix.some-int-thing` | | `int` | Something int-ish. | + """); + soft.assertThat(fileMyTypes) + .isRegularFile() + .content() + .isEqualTo( + """ + Documentation for `my.types`. + + | Property | Default Value | Type | Description | + |----------|---------------|------|-------------| + | `my.types.string` | | `string` | | + | `my.types.optional-string` | | `string` | | + | `my.types.duration` | | `duration` | | + | `my.types.optional-duration` | | `duration` | | + | `my.types.path` | | `path` | | + | `my.types.optional-path` | | `path` | | + | `my.types.uri` | | `uri` | | + | `my.types.optional-uri` | | `uri` | | + | `my.types.instant` | | `instant` | | + | `my.types.optional-instant` | | `instant` | | + | `my.types.string-list` | | `list of string` | | + | `my.types.string-string-map.`_``_ | | `string` | | + | `my.types.string-duration-map.`_``_ | | `duration` | | + | `my.types.string-path-map.`_``_ | | `path` | | + | `my.types.optional-int` | | `int` | | + | `my.types.optional-long` | | `long` | | + | `my.types.optional-double` | | `double` | | + | `my.types.int-boxed` | | `int` | | + | `my.types.long-boxed` | | `long` | | + | `my.types.double-boxed` | | `double` | | + | `my.types.float-boxed` | | `float` | | + | `my.types.int-prim` | | `int` | | + | `my.types.long-prim` | | `long` | | + | `my.types.double-prim` | | `double` | | + | `my.types.float-prim` | | `float` | | + | `my.types.bool-boxed` | | `boolean` | | + | `my.types.bool-prim` | | `boolean` | | + | `my.types.enum-thing` | | `ONE, TWO, THREE` | | + | `my.types.optional-enum` | | `ONE, TWO, THREE` | | + | `my.types.list-of-enum` | | `list of ONE, TWO, THREE` | | + | `my.types.map-to-enum.`_``_ | | `ONE, TWO, THREE` | | + | `my.types.optional-bool` | | `boolean` | | + | `my.types.mapped-a.some-weird-name` | `some-default` | `string` | Something that configures something. | + | `my.types.mapped-a.some-duration` | | `duration` | A duration of something. | + | `my.types.mapped-a.nested.other-int` | | `int` | | + | `my.types.mapped-a.nested.boxed-double` | | `double` |

_Deprecated_ | + | `my.types.mapped-a.list-of-strings` | | `list of string` | Example & < > "   € ® ©.

* ` session-iam-statements[0]= {"Effect":"Allow", "Action":"s3:*", "Resource":"arn:aws:s3:::*/alwaysAllowed/*"} `
* ` session-iam-statements[1]= {"Effect":"Deny", "Action":"s3:*", "Resource":"arn:aws:s3:::*/blocked/*"} `

| + | `my.types.mapped-a.some-int-thing` | | `int` | Something int-ish. | + | `my.types.optional-mapped-a.some-weird-name` | `some-default` | `string` | Something that configures something. | + | `my.types.optional-mapped-a.some-duration` | | `duration` | A duration of something. | + | `my.types.optional-mapped-a.nested.other-int` | | `int` | | + | `my.types.optional-mapped-a.nested.boxed-double` | | `double` |

_Deprecated_ | + | `my.types.optional-mapped-a.list-of-strings` | | `list of string` | Example & < > "   € ® ©.

* ` session-iam-statements[0]= {"Effect":"Allow", "Action":"s3:*", "Resource":"arn:aws:s3:::*/alwaysAllowed/*"} `
* ` session-iam-statements[1]= {"Effect":"Deny", "Action":"s3:*", "Resource":"arn:aws:s3:::*/blocked/*"} `

| + | `my.types.optional-mapped-a.some-int-thing` | | `int` | Something int-ish. | + | `my.types.map-string-mapped-a.`_``_`.some-weird-name` | `some-default` | `string` | Something that configures something. | + | `my.types.map-string-mapped-a.`_``_`.some-duration` | | `duration` | A duration of something. | + | `my.types.map-string-mapped-a.`_``_`.nested.other-int` | | `int` | | + | `my.types.map-string-mapped-a.`_``_`.nested.boxed-double` | | `double` |

_Deprecated_ | + | `my.types.map-string-mapped-a.`_``_`.list-of-strings` | | `list of string` | Example & < > "   € ® ©.

* ` session-iam-statements[0]= {"Effect":"Allow", "Action":"s3:*", "Resource":"arn:aws:s3:::*/alwaysAllowed/*"} `
* ` session-iam-statements[1]= {"Effect":"Deny", "Action":"s3:*", "Resource":"arn:aws:s3:::*/blocked/*"} `

| + | `my.types.map-string-mapped-a.`_``_`.some-int-thing` | | `int` | Something int-ish. | + | `my.types.some-duration` | | `duration` | A duration of something. | + | `my.types.config-option-foo` | | `string` | Something that configures something. | + | `my.types.some-int-thing` | | `int` | Something int-ish. | + """); + soft.assertThat(fileExtremelyNested) + .isRegularFile() + .content() + .isEqualTo( + """ + | Property | Default Value | Type | Description | + |----------|---------------|------|-------------| + | `extremely.nested.extremely-nested` | | `int` | Extremely nested. | + | `extremely.nested.nested-a1` | | `int` | A1. | + | `extremely.nested.nested-a2` | | `int` | A2. | + | `extremely.nested.nested-b1` | | `int` | B1. | + | `extremely.nested.nested-a11` | | `int` | A11. | + | `extremely.nested.nested-b12` | | `int` | B12. | + """); + soft.assertThat(fileVeryNested) + .isRegularFile() + .content() + .isEqualTo( + """ + | Property | Default Value | Type | Description | + |----------|---------------|------|-------------| + | `very.nested.very-nested` | | `int` | Very nested. | + | `very.nested.nested-a1` | | `int` | A1. | + | `very.nested.nested-a2` | | `int` | A2. | + | `very.nested.nested-b1` | | `int` | B1. | + | `very.nested.nested-a11` | | `int` | A11. | + | `very.nested.nested-b12` | | `int` | B12. | + """); + + var fileSectionA = dir.resolve("smallrye-my_types_Section_A.md"); + soft.assertThat(fileSectionA) + .isRegularFile() + .content() + .isEqualTo( + """ + Another map of string to `MappedA`, in its own section. + + | Property | Default Value | Type | Description | + |----------|---------------|------|-------------| + | `my.types.map.py.`_``_`.some-weird-name` | `some-default` | `string` | Something that configures something. | + | `my.types.map.py.`_``_`.some-duration` | | `duration` | A duration of something. | + | `my.types.map.py.`_``_`.nested.other-int` | | `int` | | + | `my.types.map.py.`_``_`.nested.boxed-double` | | `double` |

_Deprecated_ | + | `my.types.map.py.`_``_`.list-of-strings` | | `list of string` | Example & < > "   € ® ©.

* ` session-iam-statements[0]= {"Effect":"Allow", "Action":"s3:*", "Resource":"arn:aws:s3:::*/alwaysAllowed/*"} `
* ` session-iam-statements[1]= {"Effect":"Deny", "Action":"s3:*", "Resource":"arn:aws:s3:::*/blocked/*"} `

| + | `my.types.map.py.`_``_`.some-int-thing` | | `int` | Something int-ish. | + """); + + // Nested sections + var fileNestedRoot = dir.resolve("smallrye-nested_root.md"); + soft.assertThat(fileNestedRoot) + .isRegularFile() + .content() + .isEqualTo( + """ + Doc for NestedSectionsRoot. + + | Property | Default Value | Type | Description | + |----------|---------------|------|-------------| + | `nested.root.nested-c.int-not-in-c` | | `int` | | + """); + + var fileNestedA = dir.resolve("smallrye-nested_root_section_a.md"); + soft.assertThat(fileNestedA) + .isRegularFile() + .content() + .isEqualTo( + """ + | Property | Default Value | Type | Description | + |----------|---------------|------|-------------| + | `nested.root.nested-a.section-a.string-in-a` | | `string` | | + """); + + var fileNestedB = dir.resolve("smallrye-nested_root_section_b.md"); + soft.assertThat(fileNestedB) + .isRegularFile() + .content() + .isEqualTo( + """ + | Property | Default Value | Type | Description | + |----------|---------------|------|-------------| + | `nested.root.nested-b.section-b.string-in-b` | | `string` | | + """); + + var fileNestedC = dir.resolve("smallrye-nested_root_section_c.md"); + soft.assertThat(fileNestedC) + .isRegularFile() + .content() + .isEqualTo( + """ + | Property | Default Value | Type | Description | + |----------|---------------|------|-------------| + | `nested.root.nested-c.string-in-c` | | `string` | | + | `nested.root.nested-c.int-in-c` | | `int` | | + """); + } +} diff --git a/tools/config-docs/generator/src/test/java/tests/properties/ConfigProps.java b/tools/config-docs/generator/src/test/java/tests/properties/ConfigProps.java new file mode 100644 index 0000000000..59d461ef6d --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/properties/ConfigProps.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.properties; + +import org.apache.polaris.docs.ConfigDocs.ConfigItem; +import org.apache.polaris.docs.ConfigDocs.ConfigPageGroup; + +@SuppressWarnings("unused") +@ConfigPageGroup(name = "props") +public class ConfigProps { + /** + * A property. {@value #VALUE_ONE} is the default value. + * + *

Some text there. + */ + @ConfigItem public static final String CONF_ONE = "property.one"; + + /** + * Some summary for checkstyle. + * + *

Another property, need some words to cause a line break in the value tag here {@value + * #VALUE_ONE} for testing. + * + *

Some text there. + * + * @deprecated this is deprecated because of {@link #CONF_THREE}. + */ + @ConfigItem(section = "b b") + @Deprecated + public static final String CONF_TWO = "property.two"; + + /** + * Some {@link #VALUE_TWO} more {@link #VALUE_THREE}. + * + *

+ * + *

blah + * + *

+ * + * + */ + @ConfigItem(section = "a a a") + public static final String CONF_THREE = "property.three"; + + /** Four. */ + @ConfigItem public static final String CONF_FOUR = "property.four"; + + /** Five. */ + @ConfigItem(section = "b b") + public static final String CONF_FIVE = "property.five"; + + public static final String VALUE_ONE = "111"; + public static final String VALUE_TWO = "value two"; + public static final String VALUE_THREE = "one two three"; +} diff --git a/tools/config-docs/generator/src/test/java/tests/properties/MoreProps.java b/tools/config-docs/generator/src/test/java/tests/properties/MoreProps.java new file mode 100644 index 0000000000..d3b7600f08 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/properties/MoreProps.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.properties; + +import org.apache.polaris.docs.ConfigDocs.ConfigItem; +import org.apache.polaris.docs.ConfigDocs.ConfigPageGroup; + +@ConfigPageGroup(name = "more") +public class MoreProps { + @ConfigItem public static final String ONE = "one"; + @ConfigItem public static final String TWO = "two"; + @ConfigItem public static final String THREE = "three"; + + /** + * Sum. + * + * @hidden hidden thingy + */ + @ConfigItem public static final String FOUR = "four"; + + @ConfigItem @Deprecated public static final String FIVE = "five"; +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/AllTypes.java b/tools/config-docs/generator/src/test/java/tests/smallrye/AllTypes.java new file mode 100644 index 0000000000..de27516ae3 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/AllTypes.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithName; +import java.net.URI; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import org.apache.polaris.docs.ConfigDocs.ConfigItem; +import org.apache.polaris.docs.ConfigDocs.ConfigPropertyName; + +/** Documentation for {@code my.types}. */ +@ConfigMapping(prefix = "my.types") +public interface AllTypes extends InterfaceOne { + String string(); + + Optional optionalString(); + + Duration duration(); + + Optional optionalDuration(); + + Path path(); + + Optional optionalPath(); + + URI uri(); + + Optional optionalUri(); + + Instant instant(); + + Optional optionalInstant(); + + List stringList(); + + @ConfigPropertyName("stringkey") + Map stringStringMap(); + + @ConfigPropertyName("key2") + Map stringDurationMap(); + + Map stringPathMap(); + + OptionalInt optionalInt(); + + OptionalLong optionalLong(); + + OptionalDouble optionalDouble(); + + Integer intBoxed(); + + Long longBoxed(); + + Double doubleBoxed(); + + Float floatBoxed(); + + int intPrim(); + + long longPrim(); + + double doublePrim(); + + float floatPrim(); + + Boolean boolBoxed(); + + boolean boolPrim(); + + SomeEnum enumThing(); + + Optional optionalEnum(); + + List listOfEnum(); + + Map mapToEnum(); + + Optional optionalBool(); + + /** My {@code MappedA}. */ + MappedA mappedA(); + + /** Optional {@code MappedA}. */ + Optional optionalMappedA(); + + /** Map of string to {@code MappedA}. */ + @ConfigPropertyName("mappy") + Map mapStringMappedA(); + + /** Another map of string to {@code MappedA}, in its own section. */ + @ConfigItem(section = "Section A") + @WithName("map.py") + Map anotherMapStringMappedA(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/ExtremelyNested.java b/tools/config-docs/generator/src/test/java/tests/smallrye/ExtremelyNested.java new file mode 100644 index 0000000000..5c0daebb0c --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/ExtremelyNested.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "extremely.nested") +public interface ExtremelyNested extends NestedA, NestedB { + /** Extremely nested. */ + int extremelyNested(); + + @Override + int nestedA1(); + + @Override + int nestedA2(); + + @Override + int nestedB1(); + + @Override + int nestedA11(); + + @Override + int nestedB12(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/InterfaceOne.java b/tools/config-docs/generator/src/test/java/tests/smallrye/InterfaceOne.java new file mode 100644 index 0000000000..4815df2614 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/InterfaceOne.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +import java.time.Duration; + +public interface InterfaceOne extends InterfaceTwo { + /** A duration of something. */ + Duration someDuration(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/InterfaceThree.java b/tools/config-docs/generator/src/test/java/tests/smallrye/InterfaceThree.java new file mode 100644 index 0000000000..bce374eeec --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/InterfaceThree.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +import java.util.OptionalInt; + +public interface InterfaceThree { + /** Something int-ish. */ + OptionalInt someIntThing(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/InterfaceTwo.java b/tools/config-docs/generator/src/test/java/tests/smallrye/InterfaceTwo.java new file mode 100644 index 0000000000..b1540fab71 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/InterfaceTwo.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +public interface InterfaceTwo extends InterfaceThree { + /** Something that configures something. */ + String configOptionFoo(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/MappedA.java b/tools/config-docs/generator/src/test/java/tests/smallrye/MappedA.java new file mode 100644 index 0000000000..45c5999ec1 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/MappedA.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithName; +import java.time.Duration; +import java.util.List; + +/** + * The docs for {@code my.prefix}. + * + *
    + *
  • Some + *
  • unordered + *
  • list + *
+ * + * Some more text. + * + *
    + *
  1. one + *
  2. two + *
  3. three + *
+ */ +@ConfigMapping(prefix = "my.prefix") +public interface MappedA extends InterfaceOne { + @WithName("some-weird-name") + @WithDefault("some-default") + @Override + String configOptionFoo(); + + @Override + Duration someDuration(); + + OtherMapped nested(); + + /** + * Example & < > "   € ® ©. + * + *
    + *
  • + * session-iam-statements[0]={"Effect":"Allow", "Action":"s3:*", "Resource":"arn:aws:s3:::*/alwaysAllowed/*"} + * + *
  • + * session-iam-statements[1]={"Effect":"Deny", "Action":"s3:*", "Resource":"arn:aws:s3:::*/blocked/*"} + * + *
+ */ + List listOfStrings(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA.java new file mode 100644 index 0000000000..dfdbb568e5 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +public interface NestedA extends NestedA1, NestedA2 {} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA1.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA1.java new file mode 100644 index 0000000000..1c5ea29fff --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA1.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +public interface NestedA1 extends NestedA11 { + /** A1. */ + int nestedA1(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA11.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA11.java new file mode 100644 index 0000000000..94ef522c0b --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA11.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +public interface NestedA11 { + /** A11. */ + int nestedA11(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA2.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA2.java new file mode 100644 index 0000000000..43b27e7f91 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedA2.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +public interface NestedA2 extends NestedA11 { + /** A2. */ + int nestedA2(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedB.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedB.java new file mode 100644 index 0000000000..8a7b9d3c31 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedB.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +public interface NestedB extends NestedB1, NestedA2 {} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedB1.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedB1.java new file mode 100644 index 0000000000..d32e8370e4 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedB1.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +public interface NestedB1 extends NestedA11, NestedB12 { + /** B1. */ + int nestedB1(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedB12.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedB12.java new file mode 100644 index 0000000000..b758cc1bcd --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedB12.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +public interface NestedB12 { + /** B12. */ + int nestedB12(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionA.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionA.java new file mode 100644 index 0000000000..13e81a365f --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionA.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +import org.apache.polaris.docs.ConfigDocs; + +/** Doc for NestedSectionA. */ +public interface NestedSectionA { + @ConfigDocs.ConfigItem(section = "section_a") + NestedSectionTypeA sectionA(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionB.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionB.java new file mode 100644 index 0000000000..0da7030344 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionB.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +import org.apache.polaris.docs.ConfigDocs.ConfigItem; + +/** Doc for NestedSectionB. */ +public interface NestedSectionB { + @ConfigItem(section = "section_b") + NestedSectionTypeB sectionB(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionC.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionC.java new file mode 100644 index 0000000000..3ef360bf00 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionC.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +import org.apache.polaris.docs.ConfigDocs.ConfigItem; + +/** Doc for NestedSectionB. */ +public interface NestedSectionC { + @ConfigItem(section = "section_c") + String stringInC(); + + @ConfigItem(section = "section_c") + int intInC(); + + int intNotInC(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionTypeA.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionTypeA.java new file mode 100644 index 0000000000..6378d25922 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionTypeA.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +/** Doc for NestedSectionTypeA. */ +public interface NestedSectionTypeA { + String stringInA(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionTypeB.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionTypeB.java new file mode 100644 index 0000000000..3a6a49ced7 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionTypeB.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +/** Doc for NestedSectionTypeB. */ +public interface NestedSectionTypeB { + String stringInB(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionsRoot.java b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionsRoot.java new file mode 100644 index 0000000000..39f1505f90 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/NestedSectionsRoot.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +import io.smallrye.config.ConfigMapping; + +/** Doc for NestedSectionsRoot. */ +@ConfigMapping(prefix = "nested.root") +public interface NestedSectionsRoot { + NestedSectionA nestedA(); + + NestedSectionB nestedB(); + + NestedSectionC nestedC(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/OtherMapped.java b/tools/config-docs/generator/src/test/java/tests/smallrye/OtherMapped.java new file mode 100644 index 0000000000..e9f82849e3 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/OtherMapped.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +public interface OtherMapped { + int otherInt(); + + @Deprecated + Double boxedDouble(); +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/SomeEnum.java b/tools/config-docs/generator/src/test/java/tests/smallrye/SomeEnum.java new file mode 100644 index 0000000000..442743f7d8 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/SomeEnum.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +public enum SomeEnum { + ONE, + TWO, + THREE, +} diff --git a/tools/config-docs/generator/src/test/java/tests/smallrye/VeryNested.java b/tools/config-docs/generator/src/test/java/tests/smallrye/VeryNested.java new file mode 100644 index 0000000000..dd985adbc9 --- /dev/null +++ b/tools/config-docs/generator/src/test/java/tests/smallrye/VeryNested.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package tests.smallrye; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "very.nested") +public interface VeryNested extends NestedA, NestedB { + /** Very nested. */ + int veryNested(); +} diff --git a/tools/config-docs/site/build.gradle.kts b/tools/config-docs/site/build.gradle.kts new file mode 100644 index 0000000000..994f5ad283 --- /dev/null +++ b/tools/config-docs/site/build.gradle.kts @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +plugins { + `java-library` +} + +description = "Polaris site - reference docs" + +val genProjectPaths = listOf() + +val genProjects by configurations.creating +val genSources by configurations.creating +val doclet by configurations.creating + +dependencies { + doclet(project(":polaris-config-docs-annotations")) + doclet(project(":polaris-config-docs-generator")) + doclet(libs.smallrye.config.core) + + genProjects(project(":polaris-config-docs-annotations")) + + genProjectPaths.forEach { p -> + genProjects(project(p)) + genSources(project(p, "mainSourceElements")) + } +} + +val generatedMarkdownDocsDir = layout.buildDirectory.dir("generatedMarkdownDocs") + +val generatedMarkdownDocs = tasks.register("generatedMarkdownDocs") { + + mainClass = "org.apache.polaris.docs.generator.ReferenceConfigDocsGenerator" + + outputs.dir(generatedMarkdownDocsDir) + inputs.files(doclet) + inputs.files(genProjects) + inputs.files(genSources) + + doFirst { + delete(generatedMarkdownDocsDir) + } + + argumentProviders.add(CommandLineArgumentProvider { + + // So, in theory, all 'org.gradle.category' attributes should use the type + // org.gradle.api.attributes.Category, + // as Category.CATEGORY_ATTRIBUTE is defined. BUT! Some attributes have an attribute type == + // String.class! + val categoryAttributeAsString = Attribute.of("org.gradle.category", String::class.java) + + val classes = genProjects.incoming.artifacts + .filter { a -> + // dependencies: + // org.gradle.category=library + val category = + a.variant.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE) + ?: a.variant.attributes.getAttribute(categoryAttributeAsString) + category != null && category.toString() == Category.LIBRARY + } + .map { a -> a.file } + + val sources = genSources.incoming.artifacts + .filter { a -> + // sources: + // org.gradle.category=verification + // org.gradle.verificationtype=main-sources + + val category = a.variant.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE) + val verificationType = + a.variant.attributes.getAttribute(VerificationType.VERIFICATION_TYPE_ATTRIBUTE) + category != null && + category.name == Category.VERIFICATION && + verificationType != null && + verificationType.name == VerificationType.MAIN_SOURCES && + a.file.name != "resources" + } + .map { a -> a.file } + + listOf( + "--classpath", classes.joinToString(":"), + "--sourcepath", sources.joinToString(":"), + "--destination", generatedMarkdownDocsDir.get().toString() + ) + (if (logger.isInfoEnabled) listOf("--verbose") else listOf()) + }) + + classpath(doclet) +} + +val generateDocs by tasks.registering(Sync::class) { + dependsOn(generatedMarkdownDocs) + + val targetDir = layout.buildDirectory.dir("markdown-docs") + + outputs.dir(targetDir) + + into(targetDir) + + from(generatedMarkdownDocsDir) + + duplicatesStrategy = DuplicatesStrategy.FAIL + + doLast { delete(targetDir.get().dir("org")) } +}