diff --git a/src/main/java/org/apache/maven/plugins/help/EvaluateMojo.java b/src/main/java/org/apache/maven/plugins/help/EvaluateMojo.java index 4121208..f850835 100644 --- a/src/main/java/org/apache/maven/plugins/help/EvaluateMojo.java +++ b/src/main/java/org/apache/maven/plugins/help/EvaluateMojo.java @@ -385,7 +385,7 @@ private XStream getXStream() { /** {@inheritDoc} */ @Override public boolean canConvert(Class type) { - return Properties.class == type; + return Properties.class.isAssignableFrom(type); } /** {@inheritDoc} */ diff --git a/src/test/java/org/apache/maven/plugins/help/EvaluateMojoTest.java b/src/test/java/org/apache/maven/plugins/help/EvaluateMojoTest.java index 5fe15de..994ff26 100644 --- a/src/test/java/org/apache/maven/plugins/help/EvaluateMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/help/EvaluateMojoTest.java @@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import java.util.Properties; import org.apache.maven.api.di.Provides; import org.apache.maven.api.plugin.testing.InjectMojo; @@ -37,6 +38,7 @@ import static org.apache.maven.api.plugin.testing.MojoExtension.setVariableValueToObject; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -184,4 +186,45 @@ void testEvaluateQuiteModeWithOutputOnStdout(EvaluateMojo mojo) throws Exception assertEquals("org.apache.maven.its.help", stdResult); verify(log, never()).warn(anyString()); } + + /** + * Tests that a {@code Properties} subclass (like {@code SortedProperties}) is correctly serialized + * by XStream without falling through to {@code SerializableConverter} which would attempt + * reflective access to {@code java.util.Hashtable.table} (forbidden on Java 18+). + * @throws Exception in case of errors. + */ + @Test + @ResourceLock(Resources.SYSTEM_OUT) + @InjectMojo(goal = "evaluate") + @MojoParameter(name = "forceStdout", value = "true") + @MojoParameter(name = "expression", value = "project.properties") + void testEvaluateWithPropertiesSubclass(EvaluateMojo mojo) throws Exception { + Properties sortedProperties = new AbstractEffectiveMojo.SortedProperties(); + sortedProperties.setProperty("key1", "value1"); + sortedProperties.setProperty("key2", "value2"); + + when(expressionEvaluator.evaluate(anyString())).thenReturn(sortedProperties); + when(log.isInfoEnabled()).thenReturn(false); + + setVariableValueToObject(mojo, "evaluator", expressionEvaluator); + + PrintStream saveOut = System.out; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.setOut(new PrintStream(baos)); + + try { + mojo.execute(); + } finally { + System.setOut(saveOut); + baos.close(); + } + + String stdResult = baos.toString(); + // Verify serialization succeeded without falling through to SerializableConverter + assertTrue(stdResult.contains("key1")); + assertTrue(stdResult.contains("value1")); + assertTrue(stdResult.contains("key2")); + assertTrue(stdResult.contains("value2")); + verify(log, never()).warn(anyString()); + } }