VersionUtil.java

/*
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v2.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */

package ch.qos.logback.core.util;

import ch.qos.logback.core.Context;
import ch.qos.logback.core.status.InfoStatus;
import ch.qos.logback.core.status.WarnStatus;

import java.lang.module.ModuleDescriptor;
import java.util.Optional;

import static ch.qos.logback.core.CoreConstants.CODES_URL;
import static ch.qos.logback.core.CoreConstants.NA;

// depender depends on dependency

// dependency synonym dependee (only use dependency)
// depender synonym dependent (only use depender)

/**
 * Utility class for handling and validating version information of various artifacts.
 *
 * <p>It is used by logback-classic, logback-access-common, logback-access-jetty11, logback-access-tomcat, etc.
 * to alert users about version discrepancies between depender and dependency artifacts.
 * </p>
 *
 * @since 1.5.25
 */
public class VersionUtil {

    static final String DEPENDER_S_WAS_EXPECTING_PATTERN = "Depender [%s] was expecting version %s for dependency [%s] but found version %s";
    /**
     * @since 1.5.30
     */
    Context context;

    /**
     * Instance methods allow for polymorphism, static methods do not.
     *
     *
     * @param context
     * @since 1.5.30
     */
    protected VersionUtil(Context context) {
        this.context = context;
    }

    static public String nonNull(String input) {
        if (input == null) {
            return NA;
        } else {
            return input;
        }
    }

    /**
     * Retrieves the version of an artifact from the artifact's module metadata.
     *
     * <p>If the module or its descriptor does not provide a version, the method returns null.
     * </p>
     *
     * @param aClass a class from which to retrieve the version information
     * @return the version of class' module as a string, or null if the version cannot be determined
     */
    static private String getVersionOfClassByModule(Class<?> aClass) {
        Module module = aClass.getModule();
        if (module == null)
            return null;

        ModuleDescriptor md = module.getDescriptor();
        if (md == null)
            return null;
        Optional<String> opt = md.rawVersion();
        return opt.orElse(null);
    }

   protected String getExpectedVersionOfDependencyByProperties(Class<?> dependerClass, String propertiesFileName, String dependencyNameAsKey) {
        // derived classes should override
        return null;
    }


    /**
     * Compares the versions of a depender and a dependency to determine if they are equal.
     * Updates the context's status manager with version information and logs a warning
     * if the versions differ.
     *
     * @since 1.5.26
     */
    static public void checkForVersionEquality(Context context, String dependerVersion, String dependencyVersion, String dependerName, String dependencyName) {
        // the depender depends on the dependency
        addFoundVersionStatus(context, dependerName, dependerVersion);

        dependerVersion = nonNull(dependerVersion);

        if (dependerVersion.equals(NA) || !dependerVersion.equals(dependencyVersion)) {
            addFoundVersionStatus(context, dependencyName, dependencyVersion);
            String discrepancyMsg = String.format("Versions of %s and %s are different or unknown.", dependencyName, dependerVersion);
            context.getStatusManager().add(new WarnStatus(discrepancyMsg, context));
        }
    }


    protected static void addFoundVersionStatus(Context context, String name, String version) {
        String foundDependent = String.format("Found %s version %s", name, nonNull(version));
        context.getStatusManager().add(new InfoStatus(foundDependent, context));
    }

    protected static String nameToPropertiesFilename(String name) {
        return name + "-dependencies.properties";
    }

    /**
     * Compares the expected version of a dependency with the actual version found and updates the status context.
     * If the versions do not match, a warning is added to the context's status manager.
     *
     * <p>Note: This method is used be logback-access-jetty11/12 and logback-access-tomcat.</p>
     *
     */
    public void compareExpectedAndFoundVersion(String actualDependencyVersion, Class<?> dependerClass, String dependerVersion,
                                                      String dependerName, String dependencyName) {

        String propertiesFileName = nameToPropertiesFilename(dependerName);

        String expectedDependencyVersion = this.getExpectedVersionOfDependencyByProperties(dependerClass, propertiesFileName, dependencyName);
        String safeExpectedDependencyVersion = nonNull(expectedDependencyVersion);

        addFoundVersionStatus(context, dependencyName, actualDependencyVersion);
        addFoundVersionStatus(context, dependerName, dependerVersion);

        if (!safeExpectedDependencyVersion.equals(actualDependencyVersion)) {
            String discrepancyMsg = String.format(DEPENDER_S_WAS_EXPECTING_PATTERN, dependerName,
                    safeExpectedDependencyVersion, dependencyName, actualDependencyVersion);
            context.getStatusManager().add(new WarnStatus(discrepancyMsg, context));

            String seeMsg = "See also "+CODES_URL+"#versionMismatch";
            context.getStatusManager().add(new WarnStatus(seeMsg, context));

        }
    }
}