diff --git a/micrometer-docs-generator/src/main/java/io/micrometer/docs/commons/AbstractSearchingFileVisitor.java b/micrometer-docs-generator/src/main/java/io/micrometer/docs/commons/AbstractSearchingFileVisitor.java index b402ddac..55aa507a 100644 --- a/micrometer-docs-generator/src/main/java/io/micrometer/docs/commons/AbstractSearchingFileVisitor.java +++ b/micrometer-docs-generator/src/main/java/io/micrometer/docs/commons/AbstractSearchingFileVisitor.java @@ -34,6 +34,7 @@ import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.source.EnumConstantSource; import org.jboss.forge.roaster.model.source.JavaEnumSource; +import org.jboss.forge.roaster.model.source.JavaInterfaceSource; import org.jboss.forge.roaster.model.source.JavaSource; import org.jboss.forge.roaster.model.source.MethodSource; @@ -71,6 +72,20 @@ else if (!path.toString().endsWith(".java")) { } JavaEnumSource enumSource = (JavaEnumSource) javaSource; + enumSource.getInterfaces().forEach(interfaceName -> { + // Find any interfaces that match our supported interfaces + JavaInterfaceSource foundInterface = searchHelper.searchExtendingDocumentationInterfaceName(interfaceName, + this); + if (foundInterface != null) { + try { + supportedInterfaces().add(Class.forName(interfaceName)); + } + catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + }); + if (supportedInterfaces().stream().noneMatch(enumSource::hasInterface)) { return FileVisitResult.CONTINUE; } diff --git a/micrometer-docs-generator/src/main/java/io/micrometer/docs/commons/JavaSourceSearchHelper.java b/micrometer-docs-generator/src/main/java/io/micrometer/docs/commons/JavaSourceSearchHelper.java index b12b5be9..1328ca30 100644 --- a/micrometer-docs-generator/src/main/java/io/micrometer/docs/commons/JavaSourceSearchHelper.java +++ b/micrometer-docs-generator/src/main/java/io/micrometer/docs/commons/JavaSourceSearchHelper.java @@ -47,6 +47,7 @@ import org.jboss.forge.roaster.model.source.EnumConstantSource; import org.jboss.forge.roaster.model.source.Import; import org.jboss.forge.roaster.model.source.JavaEnumSource; +import org.jboss.forge.roaster.model.source.JavaInterfaceSource; import org.jboss.forge.roaster.model.source.JavaSource; import org.jboss.forge.roaster.model.source.MethodHolderSource; import org.jboss.forge.roaster.model.source.MethodSource; @@ -151,6 +152,20 @@ public JavaSource search(String qualifiedName) { return info.getJavaSource(); } + /** + * Search a {@link JavaInterfaceSource} by qualified class name. + * @param qualifiedName a qualified class name + * @return matched {@link JavaInterfaceSource} or {@code null} if not found. + */ + @Nullable + public JavaInterfaceSource searchInterface(String qualifiedName) { + JavaSource javaSource = search(qualifiedName); + if (javaSource == null || !javaSource.isInterface()) { + return null; + } + return (JavaInterfaceSource) javaSource; + } + /** * Search the class which is referenced by the enclosing class. * @param enclosingJavaSource enclosing java class source @@ -450,6 +465,61 @@ private JavaSource searchJavaSourceByRoasterTypeName(JavaSource enclosingJ return null; } + /** + * Search for interfaces that extend the documentation interfaces. + * @param interfaceName + * @param abstractSearchingFileVisitor + * @return the {@link JavaInterfaceSource} that extends the documentation interface or + * {@code null} if no matching interface is found + */ + @Nullable + public JavaInterfaceSource searchExtendingDocumentationInterfaceName(String interfaceName, + AbstractSearchingFileVisitor abstractSearchingFileVisitor) { + // delegate to recursive search + return searchExtendingDocumentationInterfaceName(interfaceName, new HashSet<>(), abstractSearchingFileVisitor); + } + + @Nullable + private JavaInterfaceSource searchExtendingDocumentationInterfaceName(String interfaceName, Set visited, + AbstractSearchingFileVisitor visitor) { + // Search for the interface + JavaInterfaceSource interfaceSource = searchInterface(interfaceName); + if (interfaceSource == null) { + return null; + } + + String qualifiedName = interfaceSource.getQualifiedName(); + logger.trace("Searching ExtendingDocumentationInterface on {} - start", qualifiedName); + + // Cycle detection + if (visited.contains(qualifiedName)) { + return null; + } + visited.add(qualifiedName); + + // Check if this interface is supported + if (visitor.supportedInterfaces().stream().anyMatch(name -> name.getName().equals(qualifiedName))) { + return interfaceSource; + } + + // Check all extended interfaces + for (String childInterface : interfaceSource.getInterfaces()) { + // Check if child is directly supported + if (visitor.supportedInterfaces().stream().anyMatch(i -> i.getName().equals(childInterface))) { + return interfaceSource; + } + + // Recursive search through child + JavaInterfaceSource result = searchExtendingDocumentationInterfaceName(childInterface, visited, visitor); + if (result != null) { + return result; + } + } + + // No matching interface found + return null; + } + /** * Hierarchically search the implementing name of {@link ObservationConvention} or * {@link GlobalObservationConvention}. diff --git a/micrometer-docs-generator/src/main/java/io/micrometer/docs/metrics/MetricSearchingFileVisitor.java b/micrometer-docs-generator/src/main/java/io/micrometer/docs/metrics/MetricSearchingFileVisitor.java index 6c4858c6..76c75eaa 100644 --- a/micrometer-docs-generator/src/main/java/io/micrometer/docs/metrics/MetricSearchingFileVisitor.java +++ b/micrometer-docs-generator/src/main/java/io/micrometer/docs/metrics/MetricSearchingFileVisitor.java @@ -56,6 +56,9 @@ class MetricSearchingFileVisitor extends AbstractSearchingFileVisitor { private final Collection entries; + private final Collection> supportedInterfaces = new ArrayList<>( + Arrays.asList(MeterDocumentation.class, ObservationDocumentation.class)); + MetricSearchingFileVisitor(Pattern pattern, Collection entries, JavaSourceSearchHelper searchHelper) { super(pattern, searchHelper); this.entries = entries; @@ -63,7 +66,7 @@ class MetricSearchingFileVisitor extends AbstractSearchingFileVisitor { @Override public Collection> supportedInterfaces() { - return Arrays.asList(MeterDocumentation.class, ObservationDocumentation.class); + return supportedInterfaces; } @Override diff --git a/micrometer-docs-generator/src/main/java/io/micrometer/docs/spans/SpanSearchingFileVisitor.java b/micrometer-docs-generator/src/main/java/io/micrometer/docs/spans/SpanSearchingFileVisitor.java index 070944b8..a524e98c 100644 --- a/micrometer-docs-generator/src/main/java/io/micrometer/docs/spans/SpanSearchingFileVisitor.java +++ b/micrometer-docs-generator/src/main/java/io/micrometer/docs/spans/SpanSearchingFileVisitor.java @@ -56,6 +56,9 @@ class SpanSearchingFileVisitor extends AbstractSearchingFileVisitor { private final Collection spanEntries; + private final Collection> supportedInterfaces = new ArrayList<>( + Arrays.asList(SpanDocumentation.class, ObservationDocumentation.class)); + /** * The enclosing enum classes for overriding will be excluded from documentation */ @@ -68,7 +71,7 @@ class SpanSearchingFileVisitor extends AbstractSearchingFileVisitor { @Override public Collection> supportedInterfaces() { - return Arrays.asList(SpanDocumentation.class, ObservationDocumentation.class); + return supportedInterfaces; } @Override diff --git a/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendExtendedMeterDoc.java b/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendExtendedMeterDoc.java new file mode 100644 index 00000000..76fef7c2 --- /dev/null +++ b/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendExtendedMeterDoc.java @@ -0,0 +1,25 @@ +/** + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 io.micrometer.docs.metrics.test2; + +import java.io.Serializable; + +// Added any interface (Serializable) to test if the parser really goes through all extending interfaces +public interface ExtendExtendedMeterDoc extends Serializable, ExtendMeterDocumentation { + + String secondDescription(); + +} diff --git a/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendExtendedMyMetrics.java b/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendExtendedMyMetrics.java new file mode 100644 index 00000000..7113ab28 --- /dev/null +++ b/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendExtendedMyMetrics.java @@ -0,0 +1,47 @@ +/** + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 io.micrometer.docs.metrics.test2; + +import io.micrometer.core.instrument.Meter; + +public enum ExtendExtendedMyMetrics implements ExtendExtendedMeterDoc { + + /** + * Test metric which extends the extended MeterDocumentation interface. + */ + EXTEND_EXTENDED_FOO { + @Override + public String secondDescription() { + return "This comes from ExtendedExtendMeterDoc interface"; + } + + @Override + public String getDescription() { + return "This comes from ExtendMeterDocumentation interface"; + } + + @Override + public String getName() { + return "extend.extended.foo"; + } + + @Override + public Meter.Type getType() { + return Meter.Type.COUNTER; + } + } + +} diff --git a/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendMeterDocumentation.java b/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendMeterDocumentation.java new file mode 100644 index 00000000..1f8c2fb8 --- /dev/null +++ b/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendMeterDocumentation.java @@ -0,0 +1,28 @@ +/** + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 io.micrometer.docs.metrics.test2; + +import io.micrometer.core.instrument.docs.MeterDocumentation; + +public interface ExtendMeterDocumentation extends MeterDocumentation { + + /** + * Returns the description (also known as {@code help} in some systems) for the given + * meter. + */ + String getDescription(); + +} diff --git a/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendedMyMetrics.java b/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendedMyMetrics.java new file mode 100644 index 00000000..17f20d2c --- /dev/null +++ b/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/ExtendedMyMetrics.java @@ -0,0 +1,42 @@ +/** + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 io.micrometer.docs.metrics.test2; + +import io.micrometer.core.instrument.Meter; + +public enum ExtendedMyMetrics implements ExtendMeterDocumentation { + + /** + * Test metric which extends the MeterDocumentation interface + */ + FOO { + @Override + public String getDescription() { + return "foo metric"; + } + + @Override + public String getName() { + return "extended.foo"; + } + + @Override + public Meter.Type getType() { + return Meter.Type.COUNTER; + } + }; + +} diff --git a/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/MetricSearchingFileVisitorTest.java b/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/MetricSearchingFileVisitorTest.java new file mode 100644 index 00000000..c240a49c --- /dev/null +++ b/micrometer-docs-generator/src/test/java/io/micrometer/docs/metrics/test2/MetricSearchingFileVisitorTest.java @@ -0,0 +1,45 @@ +/** + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 io.micrometer.docs.metrics.test2; + +import io.micrometer.docs.metrics.MetricsDocGenerator; +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.Pattern; + +class MetricSearchingFileVisitorTest { + + @Test + void should_render_metrics_from_sub_class_interfaces() throws IOException { + Path output = Paths.get(".", "build", "_metrics.adoc"); + + File sourceRoot = new File(".", "src/test"); + new MetricsDocGenerator(sourceRoot, Pattern.compile(".*/docs/metrics/test2/[a-zA-Z]+\\.java"), + "templates/metrics.adoc.hbs", output) + .generate(); + + BDDAssertions.then(new String(Files.readAllBytes(output))) + .contains("**Metric name** `extended.foo`. **Type** `counter`") + .contains("**Metric name** `extend.extended.foo`. **Type** `counter`"); + } + +} diff --git a/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendExtendedMySpan.java b/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendExtendedMySpan.java new file mode 100644 index 00000000..81af4574 --- /dev/null +++ b/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendExtendedMySpan.java @@ -0,0 +1,40 @@ +/** + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 io.micrometer.docs.spans.test7; + +public enum ExtendExtendedMySpan implements ExtendExtendedSpanDocumentation { + + /** + * Test span for sub-classing documentation interfaces + */ + EXTEND_EXTENDED_FOO { + @Override + public String secondDescription() { + return "This comes from ExtendExtendedSpanDocumentation"; + } + + @Override + public String getDescription() { + return "This comes from ExtendSpanDocumentation"; + } + + @Override + public String getName() { + return "extend extended foo"; + } + } + +} diff --git a/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendExtendedSpanDocumentation.java b/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendExtendedSpanDocumentation.java new file mode 100644 index 00000000..ada06d37 --- /dev/null +++ b/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendExtendedSpanDocumentation.java @@ -0,0 +1,22 @@ +/** + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 io.micrometer.docs.spans.test7; + +public interface ExtendExtendedSpanDocumentation extends ExtendSpanDocumentation { + + String secondDescription(); + +} diff --git a/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendSpanDocumentation.java b/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendSpanDocumentation.java new file mode 100644 index 00000000..5dda10ca --- /dev/null +++ b/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendSpanDocumentation.java @@ -0,0 +1,24 @@ +/** + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 io.micrometer.docs.spans.test7; + +import io.micrometer.tracing.docs.SpanDocumentation; + +public interface ExtendSpanDocumentation extends SpanDocumentation { + + String getDescription(); + +} diff --git a/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendedMySpan.java b/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendedMySpan.java new file mode 100644 index 00000000..ebc279a4 --- /dev/null +++ b/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/ExtendedMySpan.java @@ -0,0 +1,35 @@ +/** + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 io.micrometer.docs.spans.test7; + +public enum ExtendedMySpan implements ExtendSpanDocumentation { + + /** + * Test span for sub-classing documentation interfaces + */ + FOO { + @Override + public String getDescription() { + return "Description method from ExtendSpanDocumentation"; + } + + @Override + public String getName() { + return "extended foo"; + } + } + +} diff --git a/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/SpanSearchingFileVisitorTest.java b/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/SpanSearchingFileVisitorTest.java new file mode 100644 index 00000000..ab01b340 --- /dev/null +++ b/micrometer-docs-generator/src/test/java/io/micrometer/docs/spans/test7/SpanSearchingFileVisitorTest.java @@ -0,0 +1,44 @@ +/** + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 io.micrometer.docs.spans.test7; + +import io.micrometer.docs.spans.SpansDocGenerator; +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.Pattern; + +class SpanSearchingFileVisitorTest { + + @Test + void should_render_spans_from_sub_class_interfaces() throws IOException { + File root = new File("./src/test/java/io/micrometer/docs/spans/test7"); + Path output = Paths.get(".", "build/test7", "_spans.adoc"); + Files.createDirectories(output.getParent()); + + new SpansDocGenerator(root, Pattern.compile(".*"), "templates/spans.adoc.hbs", output).generate(); + + BDDAssertions.then(new String(Files.readAllBytes(output))) + .contains("**Span name** `extended foo`.") + .contains("**Span name** `extend extended foo`."); + } + +}