You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by el...@apache.org on 2026/10/11 15:30:55 UTC
(maven-reporting-api) branch fix-92-getOutputPath-recursion created (now 4665b5d)
This is an automated email from the ASF dual-hosted git repository.
elharo pushed a change to branch fix-92-getOutputPath-recursion
in repository https://gitbox.apache.org/repos/asf/maven-reporting-api.git
at 4665b5d Fix infinite recursion between getOutputPath() and getOutputName()
This branch includes the following new commits:
new 4665b5d Fix infinite recursion between getOutputPath() and getOutputName()
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(maven-reporting-api) 01/01: Fix infinite recursion between getOutputPath() and getOutputName()
Posted by el...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
elharo pushed a commit to branch fix-92-getOutputPath-recursion
in repository https://gitbox.apache.org/repos/asf/maven-reporting-api.git
commit 4665b5d683c4d8b8bc5d82bb24c65740e4737811
Author: Elliotte Rusty Harold <el...@ibiblio.org>
AuthorDate: Sun Oct 11 15:30:42 2026 +0000
Fix infinite recursion between getOutputPath() and getOutputName()
Both are default methods that delegate to each other. An implementation
overriding neither recursed forever and failed with StackOverflowError
when the report output path was resolved.
getOutputPath() now fails fast with UnsupportedOperationException when
neither method is overridden, while still delegating to getOutputName()
for implementations that override it. Implementations overriding either
method keep working unchanged.
Fixes #92
---
.../org/apache/maven/reporting/MavenReport.java | 17 +++++
.../apache/maven/reporting/MavenReportTest.java | 79 ++++++++++++++++++----
2 files changed, 84 insertions(+), 12 deletions(-)
diff --git a/src/main/java/org/apache/maven/reporting/MavenReport.java b/src/main/java/org/apache/maven/reporting/MavenReport.java
index 454315e..b66ef43 100644
--- a/src/main/java/org/apache/maven/reporting/MavenReport.java
+++ b/src/main/java/org/apache/maven/reporting/MavenReport.java
@@ -84,11 +84,28 @@ public interface MavenReport {
* <p>
* Note: This method won't be {@code default} anymore when {@link #getOutputName()} is removed.
* You are advised to implement it as soon as possible.
+ * <p>
+ * An implementation must override this method or the deprecated {@link #getOutputName()}. If it
+ * overrides neither, this method throws an {@link UnsupportedOperationException} instead of
+ * recursing forever between the two default methods.
*
* @since 4.0.0
* @return the relative output path of this report
+ * @throws UnsupportedOperationException if neither this method nor {@link #getOutputName()} is
+ * overridden
*/
default String getOutputPath() {
+ try {
+ // Only delegate to the deprecated method when it is actually overridden. Otherwise the
+ // two default methods would call each other until a StackOverflowError is thrown.
+ if (getClass().getMethod("getOutputName").isDefault()) {
+ throw new UnsupportedOperationException(
+ "A MavenReport implementation must override getOutputPath() or getOutputName()");
+ }
+ } catch (NoSuchMethodException e) {
+ // getOutputName() is declared by this interface, so it is always present.
+ throw new IllegalStateException(e);
+ }
return getOutputName();
}
diff --git a/src/test/java/org/apache/maven/reporting/MavenReportTest.java b/src/test/java/org/apache/maven/reporting/MavenReportTest.java
index 4c66ba9..b75e8ae 100644
--- a/src/test/java/org/apache/maven/reporting/MavenReportTest.java
+++ b/src/test/java/org/apache/maven/reporting/MavenReportTest.java
@@ -28,23 +28,17 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
class MavenReportTest {
private static final Locale LOCALE = Locale.ENGLISH;
- /** Records which generate method the implementation received. */
- private abstract static class RecordingReport implements MavenReport {
- String called;
- Sink sink;
- SinkFactory sinkFactory;
- Locale locale;
-
- @Override
- public String getOutputPath() {
- return "report";
- }
-
+ /**
+ * Implements every {@link MavenReport} method except the output path/name, which are
+ * deliberately left to subclasses so that the default delegation can be exercised.
+ */
+ private abstract static class StubReport implements MavenReport {
@Override
public String getCategoryName() {
return CATEGORY_PROJECT_REPORTS;
@@ -79,6 +73,14 @@ class MavenReportTest {
}
}
+ /** Records which generate method the implementation received. */
+ private abstract static class RecordingReport extends StubReport {
+ String called;
+ Sink sink;
+ SinkFactory sinkFactory;
+ Locale locale;
+ }
+
/** A report written before 4.1.0: implements only the single-page method. */
private static class SinglePageReport extends RecordingReport {
@Override
@@ -101,6 +103,26 @@ class MavenReportTest {
}
}
+ /** A report that overrides neither getOutputPath() nor getOutputName(). */
+ private static class NoOutputPathReport extends StubReport {}
+
+ /** A report that only implements the recommended getOutputPath(). */
+ private static class OutputPathReport extends StubReport {
+ @Override
+ public String getOutputPath() {
+ return "path";
+ }
+ }
+
+ /** A report that only implements the deprecated getOutputName(). */
+ private static class OutputNameReport extends StubReport {
+ @Override
+ @SuppressWarnings("deprecation")
+ public String getOutputName() {
+ return "name";
+ }
+ }
+
@Test
void multiPageCallReachesSinglePageImplementation() throws MavenReportException {
SinglePageReport report = new SinglePageReport();
@@ -122,4 +144,37 @@ class MavenReportTest {
assertNull(report.sinkFactory);
assertSame(LOCALE, report.locale);
}
+
+ @Test
+ void getOutputPathFailsFastWhenNeitherMethodIsImplemented() {
+ NoOutputPathReport report = new NoOutputPathReport();
+
+ assertThrows(UnsupportedOperationException.class, report::getOutputPath);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void getOutputNameFailsFastWhenNeitherMethodIsImplemented() {
+ NoOutputPathReport report = new NoOutputPathReport();
+
+ assertThrows(UnsupportedOperationException.class, report::getOutputName);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void getOutputNameDelegatesToOverriddenGetOutputPath() {
+ OutputPathReport report = new OutputPathReport();
+
+ assertEquals("path", report.getOutputPath());
+ assertEquals("path", report.getOutputName());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void getOutputPathDelegatesToOverriddenGetOutputName() {
+ OutputNameReport report = new OutputNameReport();
+
+ assertEquals("name", report.getOutputName());
+ assertEquals("name", report.getOutputPath());
+ }
}