CompositeBeanHelperPerformanceTest.java
/*
* 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.maven.configuration.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
import org.codehaus.plexus.component.configurator.converters.lookup.DefaultConverterLookup;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.eclipse.sisu.plexus.CompositeBeanHelper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Performance comparison test between original CompositeBeanHelper and OptimizedCompositeBeanHelper.
* This test uses JMH (Java Microbenchmark Harness) for accurate performance measurement.
*
* To run this benchmark:
* mvn test -Dtest=CompositeBeanHelperPerformanceTest -pl impl/maven-core
*
* The main method will execute the JMH benchmarks with the configured parameters.
*
* IMPORTANT: Caches are only cleared between trials (10-second periods), not between individual
* iterations, to properly test the cache benefits within each measurement period.
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 10)
@Fork(1)
@State(Scope.Benchmark)
public class CompositeBeanHelperPerformanceTest {
private ConverterLookup converterLookup;
private ExpressionEvaluator evaluator;
private ConfigurationListener listener;
private CompositeBeanHelper originalHelper;
private EnhancedCompositeBeanHelper optimizedHelper;
@Setup(Level.Trial)
@BeforeEach
public void setUp() throws ExpressionEvaluationException {
converterLookup = new DefaultConverterLookup();
evaluator = mock(ExpressionEvaluator.class);
listener = mock(ConfigurationListener.class);
when(evaluator.evaluate(anyString())).thenReturn("testValue");
for (int i = 0; i < 10; i++) {
when(evaluator.evaluate(Integer.toString(i))).thenReturn(i);
}
when(evaluator.evaluate("123")).thenReturn(123);
when(evaluator.evaluate("456")).thenReturn(456);
when(evaluator.evaluate("true")).thenReturn(true);
originalHelper = new CompositeBeanHelper(converterLookup, getClass().getClassLoader(), evaluator, listener);
optimizedHelper =
new EnhancedCompositeBeanHelper(converterLookup, getClass().getClassLoader(), evaluator, listener);
}
@TearDown(Level.Trial)
@AfterEach
public void tearDown() {
// Clear caches between trials (10-second periods) to allow cache benefits within each trial
EnhancedCompositeBeanHelper.clearCaches();
}
@Benchmark
public void benchmarkOriginalHelper() throws Exception {
RealisticTestBean bean = new RealisticTestBean();
// Set multiple properties to simulate real mojo configuration
// Use direct method calls instead of reflection for fair comparison
PlexusConfiguration nameConfig = new XmlPlexusConfiguration("name");
nameConfig.setValue("testValue");
originalHelper.setProperty(bean, "name", String.class, nameConfig);
PlexusConfiguration countConfig = new XmlPlexusConfiguration("count");
countConfig.setValue("123");
originalHelper.setProperty(bean, "count", Integer.class, countConfig);
PlexusConfiguration enabledConfig = new XmlPlexusConfiguration("enabled");
enabledConfig.setValue("true");
originalHelper.setProperty(bean, "enabled", Boolean.class, enabledConfig);
PlexusConfiguration descConfig = new XmlPlexusConfiguration("description");
descConfig.setValue("testValue");
originalHelper.setProperty(bean, "description", String.class, descConfig);
PlexusConfiguration timeoutConfig = new XmlPlexusConfiguration("timeout");
timeoutConfig.setValue("123");
originalHelper.setProperty(bean, "timeout", Long.class, timeoutConfig);
}
@Benchmark
public void benchmarkOptimizedHelper() throws Exception {
RealisticTestBean bean = new RealisticTestBean();
// Set multiple properties to simulate real mojo configuration
PlexusConfiguration nameConfig = new XmlPlexusConfiguration("name");
nameConfig.setValue("testValue");
optimizedHelper.setProperty(bean, "name", String.class, nameConfig);
PlexusConfiguration countConfig = new XmlPlexusConfiguration("count");
countConfig.setValue("123");
optimizedHelper.setProperty(bean, "count", Integer.class, countConfig);
PlexusConfiguration enabledConfig = new XmlPlexusConfiguration("enabled");
enabledConfig.setValue("true");
optimizedHelper.setProperty(bean, "enabled", Boolean.class, enabledConfig);
PlexusConfiguration descConfig = new XmlPlexusConfiguration("description");
descConfig.setValue("testValue");
optimizedHelper.setProperty(bean, "description", String.class, descConfig);
PlexusConfiguration timeoutConfig = new XmlPlexusConfiguration("timeout");
timeoutConfig.setValue("123");
optimizedHelper.setProperty(bean, "timeout", Long.class, timeoutConfig);
}
/**
* Benchmark that tests multiple property configurations in a single operation.
* This simulates a more realistic scenario where multiple properties are set on a bean.
*/
@Benchmark
public void benchmarkOriginalHelperMultipleProperties() throws Exception {
RealisticTestBean bean = new RealisticTestBean();
// Set multiple properties in one benchmark iteration
PlexusConfiguration config6 = new XmlPlexusConfiguration("name");
config6.setValue("testValue");
originalHelper.setProperty(bean, "name", String.class, config6);
PlexusConfiguration config5 = new XmlPlexusConfiguration("count");
config5.setValue("123");
originalHelper.setProperty(bean, "count", Integer.class, config5);
PlexusConfiguration config4 = new XmlPlexusConfiguration("enabled");
config4.setValue("true");
originalHelper.setProperty(bean, "enabled", Boolean.class, config4);
PlexusConfiguration config3 = new XmlPlexusConfiguration("description");
config3.setValue("testValue");
originalHelper.setProperty(bean, "description", String.class, config3);
PlexusConfiguration config2 = new XmlPlexusConfiguration("timeout");
config2.setValue("123");
originalHelper.setProperty(bean, "timeout", Long.class, config2);
// Repeat to test caching
PlexusConfiguration config1 = new XmlPlexusConfiguration("name");
config1.setValue("testValue2");
originalHelper.setProperty(bean, "name", String.class, config1);
PlexusConfiguration config = new XmlPlexusConfiguration("count");
config.setValue("456");
originalHelper.setProperty(bean, "count", Integer.class, config);
}
@Benchmark
public void benchmarkOptimizedHelperMultipleProperties() throws Exception {
RealisticTestBean bean = new RealisticTestBean();
// Set multiple properties in one benchmark iteration
PlexusConfiguration nameConfig = new XmlPlexusConfiguration("name");
nameConfig.setValue("testValue");
optimizedHelper.setProperty(bean, "name", String.class, nameConfig);
PlexusConfiguration countConfig = new XmlPlexusConfiguration("count");
countConfig.setValue("123");
optimizedHelper.setProperty(bean, "count", Integer.class, countConfig);
PlexusConfiguration enabledConfig = new XmlPlexusConfiguration("enabled");
enabledConfig.setValue("true");
optimizedHelper.setProperty(bean, "enabled", Boolean.class, enabledConfig);
PlexusConfiguration descConfig = new XmlPlexusConfiguration("description");
descConfig.setValue("testValue");
optimizedHelper.setProperty(bean, "description", String.class, descConfig);
PlexusConfiguration timeoutConfig = new XmlPlexusConfiguration("timeout");
timeoutConfig.setValue("123");
optimizedHelper.setProperty(bean, "timeout", Long.class, timeoutConfig);
// Repeat to test caching benefits
nameConfig.setValue("testValue2");
optimizedHelper.setProperty(bean, "name", String.class, nameConfig);
countConfig.setValue("456");
optimizedHelper.setProperty(bean, "count", Integer.class, countConfig);
}
/**
* Benchmark that tests cache benefits by repeatedly setting properties on the same class.
* This better demonstrates the caching improvements.
*/
@Benchmark
public void benchmarkOriginalHelperRepeatedOperations() throws Exception {
// Test cache benefits by using same class multiple times
for (int i = 0; i < 10; i++) {
RealisticTestBean bean = new RealisticTestBean();
PlexusConfiguration nameConfig = new XmlPlexusConfiguration("name");
nameConfig.setValue("testValue" + i);
originalHelper.setProperty(bean, "name", String.class, nameConfig);
PlexusConfiguration countConfig = new XmlPlexusConfiguration("count");
countConfig.setValue(String.valueOf(i));
originalHelper.setProperty(bean, "count", Integer.class, countConfig);
}
}
@Benchmark
@Test
public void benchmarkOptimizedHelperRepeatedOperations() throws Exception {
// Test cache benefits by using same class multiple times
for (int i = 0; i < 10; i++) {
RealisticTestBean bean = new RealisticTestBean();
PlexusConfiguration nameConfig = new XmlPlexusConfiguration("name");
nameConfig.setValue("testValue" + i);
optimizedHelper.setProperty(bean, "name", String.class, nameConfig);
PlexusConfiguration countConfig = new XmlPlexusConfiguration("count");
countConfig.setValue(String.valueOf(i));
optimizedHelper.setProperty(bean, "count", Integer.class, countConfig);
}
}
/**
* Benchmark with multiple different bean types to test method cache effectiveness.
*/
@Benchmark
public void benchmarkOriginalHelperMultipleTypes() throws Exception {
// Test with different bean types
RealisticTestBean bean1 = new RealisticTestBean();
TestBean bean2 = new TestBean();
PlexusConfiguration config1 = new XmlPlexusConfiguration("name");
config1.setValue("testValue");
originalHelper.setProperty(bean1, "name", String.class, config1);
originalHelper.setProperty(bean2, "name", String.class, config1);
PlexusConfiguration config2 = new XmlPlexusConfiguration("count");
config2.setValue("123");
originalHelper.setProperty(bean1, "count", Integer.class, config2);
originalHelper.setProperty(bean2, "count", Integer.class, config2);
}
@Benchmark
public void benchmarkOptimizedHelperMultipleTypes() throws Exception {
// Test with different bean types
RealisticTestBean bean1 = new RealisticTestBean();
TestBean bean2 = new TestBean();
PlexusConfiguration config1 = new XmlPlexusConfiguration("name");
config1.setValue("testValue");
optimizedHelper.setProperty(bean1, "name", String.class, config1);
optimizedHelper.setProperty(bean2, "name", String.class, config1);
PlexusConfiguration config2 = new XmlPlexusConfiguration("count");
config2.setValue("123");
optimizedHelper.setProperty(bean1, "count", Integer.class, config2);
optimizedHelper.setProperty(bean2, "count", Integer.class, config2);
}
/**
* Main method to run the JMH benchmark.
*
* @param args command line arguments
* @throws RunnerException if the benchmark fails to run
*/
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(CompositeBeanHelperPerformanceTest.class.getSimpleName())
.forks(1)
.warmupIterations(3)
.measurementIterations(5)
.build();
new Runner(opt).run();
}
/**
* Test bean class for performance testing.
*/
public static class TestBean {
private String name;
private String description;
private int count;
private List<String> items = new ArrayList<>();
private boolean enabled;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public List<String> getItems() {
return items;
}
public void addItem(String item) {
this.items.add(item);
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
/**
* A more realistic test bean that simulates typical mojo parameters
*/
public static class RealisticTestBean {
private String name;
private int count;
private boolean enabled;
private String description;
private long timeout;
private List<String> items;
private Map<String, String> properties;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setCount(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return enabled;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public long getTimeout() {
return timeout;
}
public void setItems(List<String> items) {
this.items = items;
}
public List<String> getItems() {
return items;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
public Map<String, String> getProperties() {
return properties;
}
}
}