ModelInterningTransformer.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.Arrays;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.maven.api.Constants;
import org.apache.maven.api.Session;
import org.apache.maven.api.services.xml.XmlReaderRequest;

/**
 * A {@link XmlReaderRequest.Transformer} that interns frequently repeated XML text values
 * to reduce memory allocation and garbage collection pressure in large multi-module builds.
 *
 * <p>The set of XML contexts to intern is configurable via the
 * {@link Constants#MAVEN_MODEL_BUILDER_INTERNS} session property. If the property is absent
 * or empty, {@link #DEFAULT_CONTEXTS} is used.
 */
class ModelInterningTransformer implements XmlReaderRequest.Transformer {

    static final Set<String> DEFAULT_CONTEXTS = Set.of(
            // Core Maven coordinates
            "groupId",
            "artifactId",
            "version",
            "namespaceUri",
            "packaging",

            // Dependency-related fields
            "scope",
            "type",
            "classifier",

            // Build and plugin-related fields
            "phase",
            "goal",
            "execution",

            // Repository-related fields
            "layout",
            "policy",
            "checksumPolicy",
            "updatePolicy",

            // Common metadata fields
            "modelVersion",
            "name",
            "url",
            "system",
            "distribution",
            "status",

            // SCM fields
            "connection",
            "developerConnection",
            "tag",

            // Common enum-like values that appear frequently
            "id",
            "inherited",
            "optional");

    private final Set<String> contexts;

    /**
     * Creates a transformer with default contexts.
     */
    ModelInterningTransformer() {
        this.contexts = DEFAULT_CONTEXTS;
    }

    /**
     * Creates a transformer with contexts from session properties.
     * Reads {@link Constants#MAVEN_MODEL_BUILDER_INTERNS} from user then system properties.
     *
     * @param session the Maven session to read properties from
     */
    ModelInterningTransformer(Session session) {
        this.contexts = parseContextsFromSession(session);
    }

    private static Set<String> parseContextsFromSession(Session session) {
        String contextsProperty = session.getUserProperties().get(Constants.MAVEN_MODEL_BUILDER_INTERNS);
        if (contextsProperty == null) {
            contextsProperty = session.getSystemProperties().get(Constants.MAVEN_MODEL_BUILDER_INTERNS);
        }
        if (contextsProperty == null || contextsProperty.trim().isEmpty()) {
            return DEFAULT_CONTEXTS;
        }
        return Arrays.stream(contextsProperty.split(","))
                .map(String::trim)
                .filter(s -> !s.isEmpty())
                .collect(Collectors.toSet());
    }

    @Override
    public String transform(String input, String context) {
        return input != null && contexts.contains(context) ? input.intern() : input;
    }

    /**
     * Returns the contexts that will be interned by this transformer.
     * Used for testing purposes.
     */
    Set<String> getContexts() {
        return contexts;
    }
}