InterningTransformerTest.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.impl.model;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.api.Constants;
import org.apache.maven.api.Session;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.xml.XmlReaderRequest;
import org.apache.maven.impl.DefaultModelXmlFactory;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
/**
* Test class for {@link DefaultModelBuilder.InterningTransformer}.
* Verifies that the transformer correctly interns commonly used string values
* to reduce memory usage during Maven POM parsing.
*/
class InterningTransformerTest {
@Test
void testTransformerInternsCorrectContexts() {
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer();
// Test that contexts in the CONTEXTS set are interned
String groupId1 = transformer.transform("org.apache.maven", "groupId");
String groupId2 = transformer.transform("org.apache.maven", "groupId");
assertSame(groupId1, groupId2, "groupId should be interned");
String type1 = transformer.transform("jar", "type");
String type2 = transformer.transform("jar", "type");
assertSame(type1, type2, "type should be interned");
String scope1 = transformer.transform("compile", "scope");
String scope2 = transformer.transform("compile", "scope");
assertSame(scope1, scope2, "scope should be interned");
String classifier1 = transformer.transform("sources", "classifier");
String classifier2 = transformer.transform("sources", "classifier");
assertSame(classifier1, classifier2, "classifier should be interned");
String goal1 = transformer.transform("compile", "goal");
String goal2 = transformer.transform("compile", "goal");
assertSame(goal1, goal2, "goal should be interned");
String modelVersion1 = transformer.transform("4.0.0", "modelVersion");
String modelVersion2 = transformer.transform("4.0.0", "modelVersion");
assertSame(modelVersion1, modelVersion2, "modelVersion should be interned");
// Test that contexts not in the CONTEXTS set are not interned
// Use new String() to avoid automatic interning by JVM
String value1 = new String("some-value");
String value2 = new String("some-value");
String nonInterned1 = transformer.transform(value1, "nonInterned");
String nonInterned2 = transformer.transform(value2, "nonInterned");
assertSame(value1, nonInterned1, "non-interned context should return same instance");
assertSame(value2, nonInterned2, "non-interned context should return same instance");
assertNotSame(nonInterned1, nonInterned2, "different input instances should remain different");
assertEquals(nonInterned1, nonInterned2, "but values should still be equal");
}
@Test
void testTransformerContainsExpectedContexts() {
// Verify that the DEFAULT_CONTEXTS set contains all the expected fields
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("groupId"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "groupId");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("artifactId"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain "
+ "artifactId");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("version"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "version");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("packaging"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "packaging");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("scope"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "scope");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("type"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "type");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("classifier"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain "
+ "classifier");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("goal"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "goal");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("execution"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "execution");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("phase"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "phase");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("modelVersion"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain "
+ "modelVersion");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("name"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "name");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("url"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "url");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("system"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "system");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("distribution"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain "
+ "distribution");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("status"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "status");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("connection"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain "
+ "connection");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("developerConnection"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain "
+ "developerConnection");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("tag"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "tag");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("id"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "id");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("inherited"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "inherited");
assertTrue(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("optional"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to contain " + "optional");
// Verify that non-interned contexts are not in the set
assertFalse(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("nonInterned"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to not contain "
+ "nonInterned");
assertFalse(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("description"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to not contain "
+ "description");
assertFalse(
DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS.contains("randomField"),
"Expected " + DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS + " to not contain "
+ "randomField");
}
@Test
void testTransformerWithNullAndEmptyValues() {
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer();
// Test with null value
String result1 = transformer.transform(null, "groupId");
String result2 = transformer.transform(null, "groupId");
assertNull(result1);
assertNull(result2);
// Test with empty string
String empty1 = transformer.transform("", "artifactId");
String empty2 = transformer.transform("", "artifactId");
assertSame(empty1, empty2, "empty strings should be interned");
// Test with whitespace
String whitespace1 = transformer.transform(" ", "version");
String whitespace2 = transformer.transform(" ", "version");
assertSame(whitespace1, whitespace2, "whitespace strings should be interned");
}
@Test
void testTransformerIsUsedDuringPomParsing() throws Exception {
// Create a test transformer that tracks what contexts are called
List<String> calledContexts = new ArrayList<>();
XmlReaderRequest.Transformer trackingTransformer = (value, context) -> {
calledContexts.add(context);
return value;
};
String pomXml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>test-project</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.9.0</version>
<scope>compile</scope>
<type>jar</type>
<classifier>sources</classifier>
</dependency>
</dependencies>
</project>
""";
DefaultModelXmlFactory factory = new DefaultModelXmlFactory();
XmlReaderRequest request = XmlReaderRequest.builder()
.reader(new StringReader(pomXml))
.transformer(trackingTransformer)
.build();
Model model = factory.read(request);
// Verify the model was parsed correctly
assertEquals("com.example", model.getGroupId());
assertEquals("test-project", model.getArtifactId());
assertEquals("1.0.0", model.getVersion());
assertEquals("jar", model.getPackaging());
// Verify that the transformer was called for the expected contexts
assertTrue(calledContexts.contains("groupId"), "groupId context should be called");
assertTrue(calledContexts.contains("artifactId"), "artifactId context should be called");
assertTrue(calledContexts.contains("version"), "version context should be called");
assertTrue(calledContexts.contains("packaging"), "packaging context should be called");
assertTrue(calledContexts.contains("scope"), "scope context should be called");
assertTrue(calledContexts.contains("type"), "type context should be called");
assertTrue(calledContexts.contains("classifier"), "classifier context should be called");
// Verify specific paths are called correctly
long groupIdCount = calledContexts.stream().filter("groupId"::equals).count();
assertTrue(groupIdCount >= 2, "groupId should be called at least 2 times (project, dependency)");
}
@Test
void testInterningTransformerWithRealPomParsing() throws Exception {
String pomXml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>4.0.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api</artifactId>
<version>4.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
""";
DefaultModelXmlFactory factory = new DefaultModelXmlFactory();
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer();
XmlReaderRequest request = XmlReaderRequest.builder()
.reader(new StringReader(pomXml))
.transformer(transformer)
.build();
Model model = factory.read(request);
// Verify the model was parsed correctly
assertEquals("org.apache.maven", model.getGroupId());
assertEquals("maven-core", model.getArtifactId());
assertEquals("4.0.0", model.getVersion());
assertEquals("jar", model.getPackaging());
// Verify dependency was parsed
assertEquals(1, model.getDependencies().size());
assertEquals("org.apache.maven", model.getDependencies().get(0).getGroupId());
assertEquals("maven-api", model.getDependencies().get(0).getArtifactId());
assertEquals("4.0.0", model.getDependencies().get(0).getVersion());
assertEquals("compile", model.getDependencies().get(0).getScope());
}
@Test
void testTransformerWithSessionPropertyUserProperties() {
// Test with custom contexts from user properties
Map<String, String> userProperties = new HashMap<>();
userProperties.put(Constants.MAVEN_MODEL_BUILDER_INTERNS, "groupId,artifactId,customField");
Session session = Mockito.mock(Session.class);
when(session.getUserProperties()).thenReturn(userProperties);
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer(session);
// Test that custom contexts are used
assertTrue(transformer.getContexts().contains("groupId"));
assertTrue(transformer.getContexts().contains("artifactId"));
assertTrue(transformer.getContexts().contains("customField"));
// Test that default contexts not in the custom list are not used
assertFalse(transformer.getContexts().contains("version"));
assertFalse(transformer.getContexts().contains("scope"));
// Test interning behavior
String groupId1 = transformer.transform("org.apache.maven", "groupId");
String groupId2 = transformer.transform("org.apache.maven", "groupId");
assertSame(groupId1, groupId2, "groupId should be interned");
String custom1 = transformer.transform("test-value", "customField");
String custom2 = transformer.transform("test-value", "customField");
assertSame(custom1, custom2, "customField should be interned");
// Test that non-custom contexts are not interned
String version1 = new String("1.0.0");
String version2 = new String("1.0.0");
String nonInterned1 = transformer.transform(version1, "version");
String nonInterned2 = transformer.transform(version2, "version");
assertSame(version1, nonInterned1, "version should not be interned");
assertSame(version2, nonInterned2, "version should not be interned");
assertNotSame(nonInterned1, nonInterned2, "different input instances should remain different");
}
@Test
void testTransformerWithSessionPropertySystemProperties() {
// Test with custom contexts from system properties
Map<String, String> systemProperties = new HashMap<>();
systemProperties.put(Constants.MAVEN_MODEL_BUILDER_INTERNS, "scope,type");
Session session = Mockito.mock(Session.class);
when(session.getSystemProperties()).thenReturn(systemProperties);
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer(session);
// Test that custom contexts are used
assertTrue(transformer.getContexts().contains("scope"));
assertTrue(transformer.getContexts().contains("type"));
assertEquals(2, transformer.getContexts().size());
// Test interning behavior
String scope1 = transformer.transform("compile", "scope");
String scope2 = transformer.transform("compile", "scope");
assertSame(scope1, scope2, "scope should be interned");
}
@Test
void testTransformerUserPropertiesOverrideSystemProperties() {
// Test that user properties take precedence over system properties
Map<String, String> systemProperties = new HashMap<>();
systemProperties.put(Constants.MAVEN_MODEL_BUILDER_INTERNS, "scope,type");
Map<String, String> userProperties = new HashMap<>();
userProperties.put(Constants.MAVEN_MODEL_BUILDER_INTERNS, "groupId,artifactId");
Session session = Mockito.mock(Session.class);
when(session.getUserProperties()).thenReturn(userProperties);
when(session.getSystemProperties()).thenReturn(systemProperties);
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer(session);
// Test that user properties are used, not system properties
assertTrue(transformer.getContexts().contains("groupId"));
assertTrue(transformer.getContexts().contains("artifactId"));
assertFalse(transformer.getContexts().contains("scope"));
assertFalse(transformer.getContexts().contains("type"));
assertEquals(2, transformer.getContexts().size());
}
@Test
void testTransformerWithEmptySessionProperty() {
// Test with empty property value - should use defaults
Map<String, String> userProperties = new HashMap<>();
userProperties.put(Constants.MAVEN_MODEL_BUILDER_INTERNS, "");
Session session = Mockito.mock(Session.class);
when(session.getUserProperties()).thenReturn(userProperties);
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer(session);
// Should use default contexts
assertEquals(DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS, transformer.getContexts());
}
@Test
void testTransformerWithWhitespaceOnlySessionProperty() {
// Test with whitespace-only property value - should use defaults
Map<String, String> userProperties = new HashMap<>();
userProperties.put(Constants.MAVEN_MODEL_BUILDER_INTERNS, " ");
Session session = Mockito.mock(Session.class);
when(session.getUserProperties()).thenReturn(userProperties);
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer(session);
// Should use default contexts
assertEquals(DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS, transformer.getContexts());
}
@Test
void testTransformerWithNoSessionProperty() {
// Test with no property set - should use defaults
Session session = Mockito.mock(Session.class);
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer(session);
// Should use default contexts
assertEquals(DefaultModelBuilder.InterningTransformer.DEFAULT_CONTEXTS, transformer.getContexts());
}
@Test
void testTransformerWithCommaSeparatedValues() {
// Test parsing of comma-separated values with various whitespace
Map<String, String> userProperties = new HashMap<>();
userProperties.put(Constants.MAVEN_MODEL_BUILDER_INTERNS, "groupId, artifactId , version, scope ,type");
Session session = Mockito.mock(Session.class);
when(session.getUserProperties()).thenReturn(userProperties);
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer(session);
// Test that all values are parsed correctly (whitespace trimmed)
assertTrue(transformer.getContexts().contains("groupId"));
assertTrue(transformer.getContexts().contains("artifactId"));
assertTrue(transformer.getContexts().contains("version"));
assertTrue(transformer.getContexts().contains("scope"));
assertTrue(transformer.getContexts().contains("type"));
assertEquals(5, transformer.getContexts().size());
}
@Test
void testTransformerWithEmptyCommaSeparatedValues() {
// Test parsing with empty values in comma-separated list
Map<String, String> userProperties = new HashMap<>();
userProperties.put(Constants.MAVEN_MODEL_BUILDER_INTERNS, "groupId,,artifactId, ,version");
Session session = Mockito.mock(Session.class);
when(session.getUserProperties()).thenReturn(userProperties);
DefaultModelBuilder.InterningTransformer transformer = new DefaultModelBuilder.InterningTransformer(session);
// Test that empty values are filtered out
assertTrue(transformer.getContexts().contains("groupId"));
assertTrue(transformer.getContexts().contains("artifactId"));
assertTrue(transformer.getContexts().contains("version"));
assertEquals(3, transformer.getContexts().size());
}
}