DefaultModelObjectPoolTest.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.util.Map;
import java.util.Objects;

import org.apache.maven.api.Constants;
import org.apache.maven.api.model.Dependency;
import org.apache.maven.api.model.ModelObjectProcessor;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Test for DefaultModelObjectPool.
 */
class DefaultModelObjectPoolTest {

    @Test
    void testServiceLoading() {
        // Test that the static method works
        String testString = "test";
        String result = ModelObjectProcessor.processObject(testString);
        assertNotNull(result);
        assertEquals(testString, result);
    }

    @Test
    void testDependencyPooling() {
        ModelObjectProcessor processor = new DefaultModelObjectPool();

        // Create two identical dependencies
        // Note: Due to the static processor being active, these may already be pooled
        Dependency dep1 = Dependency.newBuilder()
                .groupId("org.apache.maven")
                .artifactId("maven-core")
                .version("4.0.0")
                .build();

        Dependency dep2 = Dependency.newBuilder()
                .groupId("org.apache.maven")
                .artifactId("maven-core")
                .version("4.0.0")
                .build();

        // Due to static processing, they may already be the same instance
        // This is actually the expected behavior - pooling is working!

        // Process them through our specific processor instance
        Dependency pooled1 = processor.process(dep1);
        Dependency pooled2 = processor.process(dep2);

        // They should be the same instance after processing
        assertSame(pooled1, pooled2);

        // The pooled instances should be semantically equal to the originals
        assertTrue(dependenciesEqual(dep1, pooled1));
        assertTrue(dependenciesEqual(dep2, pooled2));
    }

    /**
     * Helper method to check complete equality of dependencies.
     */
    private boolean dependenciesEqual(Dependency dep1, Dependency dep2) {
        return Objects.equals(dep1.getGroupId(), dep2.getGroupId())
                && Objects.equals(dep1.getArtifactId(), dep2.getArtifactId())
                && Objects.equals(dep1.getVersion(), dep2.getVersion())
                && Objects.equals(dep1.getType(), dep2.getType())
                && Objects.equals(dep1.getClassifier(), dep2.getClassifier())
                && Objects.equals(dep1.getScope(), dep2.getScope())
                && Objects.equals(dep1.getSystemPath(), dep2.getSystemPath())
                && Objects.equals(dep1.getExclusions(), dep2.getExclusions())
                && Objects.equals(dep1.getOptional(), dep2.getOptional())
                && Objects.equals(dep1.getLocationKeys(), dep2.getLocationKeys())
                && locationsEqual(dep1, dep2)
                && Objects.equals(dep1.getImportedFrom(), dep2.getImportedFrom());
    }

    /**
     * Helper method to check locations equality.
     */
    private boolean locationsEqual(Dependency dep1, Dependency dep2) {
        var keys1 = dep1.getLocationKeys();
        var keys2 = dep2.getLocationKeys();

        if (!Objects.equals(keys1, keys2)) {
            return false;
        }

        for (Object key : keys1) {
            if (!Objects.equals(dep1.getLocation(key), dep2.getLocation(key))) {
                return false;
            }
        }
        return true;
    }

    @Test
    void testNonDependencyObjects() {
        ModelObjectProcessor processor = new DefaultModelObjectPool();

        String testString = "test";
        String result = processor.process(testString);

        // Non-dependency objects should be returned as-is
        assertSame(testString, result);
    }

    @Test
    void testConfigurableReferenceType() {
        // Test that the reference type can be configured via system property
        String originalValue = System.getProperty(Constants.MAVEN_MODEL_PROCESSOR_REFERENCE_TYPE);

        try {
            // Set a different reference type
            System.setProperty(Constants.MAVEN_MODEL_PROCESSOR_REFERENCE_TYPE, "SOFT");

            // Create a new processor (this would use the new setting in a real scenario)
            ModelObjectProcessor processor = new DefaultModelObjectPool();

            // Test that it still works (the actual reference type is used internally)
            Dependency dep = Dependency.newBuilder()
                    .groupId("test")
                    .artifactId("test")
                    .version("1.0")
                    .build();

            Dependency result = processor.process(dep);
            assertNotNull(result);
            assertEquals(dep, result);

        } finally {
            // Restore original value
            if (originalValue != null) {
                System.setProperty(Constants.MAVEN_MODEL_PROCESSOR_REFERENCE_TYPE, originalValue);
            } else {
                System.clearProperty(Constants.MAVEN_MODEL_PROCESSOR_REFERENCE_TYPE);
            }
        }
    }

    @Test
    void testConfigurablePooledTypes() {
        // Configure to only pool Dependencies
        ModelObjectProcessor processor =
                new DefaultModelObjectPool(Map.of(Constants.MAVEN_MODEL_PROCESSOR_POOLED_TYPES, "Dependency"));

        // Dependencies should be pooled
        Dependency dep1 = Dependency.newBuilder()
                .groupId("test")
                .artifactId("test")
                .version("1.0")
                .build();

        Dependency dep2 = Dependency.newBuilder()
                .groupId("test")
                .artifactId("test")
                .version("1.0")
                .build();

        Dependency result1 = processor.process(dep1);
        Dependency result2 = processor.process(dep2);

        // Should be the same instance due to pooling
        assertSame(result1, result2);

        // Non-dependency objects should not be pooled (pass through)
        String str1 = "test";
        String str2 = processor.process(str1);
        assertSame(str1, str2); // Same instance because it's not pooled
    }

    @Test
    void testPerTypeReferenceType() {
        // Set default to WEAK and Dependency-specific to HARD
        ModelObjectProcessor processor = new DefaultModelObjectPool(Map.of(
                Constants.MAVEN_MODEL_PROCESSOR_REFERENCE_TYPE,
                "WEAK",
                Constants.MAVEN_MODEL_PROCESSOR_REFERENCE_TYPE_PREFIX + "Dependency",
                "HARD"));

        // Test that dependencies still work with per-type configuration
        Dependency dep = Dependency.newBuilder()
                .groupId("test")
                .artifactId("test")
                .version("1.0")
                .build();

        Dependency result = processor.process(dep);
        assertNotNull(result);
        assertEquals(dep, result);
    }

    @Test
    void testStatistics() {
        ModelObjectProcessor processor = new DefaultModelObjectPool();

        // Process some dependencies
        for (int i = 0; i < 5; i++) {
            Dependency dep = Dependency.newBuilder()
                    .groupId("test")
                    .artifactId("test-" + (i % 2)) // Create some duplicates
                    .version("1.0")
                    .build();
            processor.process(dep);
        }

        // Check that statistics are available
        String stats = DefaultModelObjectPool.getStatistics(Dependency.class);
        assertNotNull(stats);
        assertTrue(stats.contains("Dependency"));

        String allStats = DefaultModelObjectPool.getAllStatistics();
        assertNotNull(allStats);
        assertTrue(allStats.contains("ModelObjectPool Statistics"));
    }
}