001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v2.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014
015package ch.qos.logback.core.util;
016
017import ch.qos.logback.core.Context;
018import ch.qos.logback.core.status.InfoStatus;
019import ch.qos.logback.core.status.WarnStatus;
020
021import java.lang.module.ModuleDescriptor;
022import java.util.Optional;
023
024import static ch.qos.logback.core.CoreConstants.CODES_URL;
025import static ch.qos.logback.core.CoreConstants.NA;
026
027// depender depends on dependency
028
029// dependency synonym dependee (only use dependency)
030// depender synonym dependent (only use depender)
031
032/**
033 * Utility class for handling and validating version information of various artifacts.
034 *
035 * <p>It is used by logback-classic, logback-access-common, logback-access-jetty11, logback-access-tomcat, etc.
036 * to alert users about version discrepancies between depender and dependency artifacts.
037 * </p>
038 *
039 * @since 1.5.25
040 */
041public class VersionUtil {
042
043    static final String DEPENDER_S_WAS_EXPECTING_PATTERN = "Depender [%s] was expecting version %s for dependency [%s] but found version %s";
044    /**
045     * @since 1.5.30
046     */
047    Context context;
048
049    /**
050     * Instance methods allow for polymorphism, static methods do not.
051     *
052     *
053     * @param context
054     * @since 1.5.30
055     */
056    protected VersionUtil(Context context) {
057        this.context = context;
058    }
059
060    static public String nonNull(String input) {
061        if (input == null) {
062            return NA;
063        } else {
064            return input;
065        }
066    }
067
068    /**
069     * Retrieves the version of an artifact from the artifact's module metadata.
070     *
071     * <p>If the module or its descriptor does not provide a version, the method returns null.
072     * </p>
073     *
074     * @param aClass a class from which to retrieve the version information
075     * @return the version of class' module as a string, or null if the version cannot be determined
076     */
077    static private String getVersionOfClassByModule(Class<?> aClass) {
078        Module module = aClass.getModule();
079        if (module == null)
080            return null;
081
082        ModuleDescriptor md = module.getDescriptor();
083        if (md == null)
084            return null;
085        Optional<String> opt = md.rawVersion();
086        return opt.orElse(null);
087    }
088
089   protected String getExpectedVersionOfDependencyByProperties(Class<?> dependerClass, String propertiesFileName, String dependencyNameAsKey) {
090        // derived classes should override
091        return null;
092    }
093
094
095    /**
096     * Compares the versions of a depender and a dependency to determine if they are equal.
097     * Updates the context's status manager with version information and logs a warning
098     * if the versions differ.
099     *
100     * @since 1.5.26
101     */
102    static public void checkForVersionEquality(Context context, String dependerVersion, String dependencyVersion, String dependerName, String dependencyName) {
103        // the depender depends on the dependency
104        addFoundVersionStatus(context, dependerName, dependerVersion);
105
106        dependerVersion = nonNull(dependerVersion);
107
108        if (dependerVersion.equals(NA) || !dependerVersion.equals(dependencyVersion)) {
109            addFoundVersionStatus(context, dependencyName, dependencyVersion);
110            String discrepancyMsg = String.format("Versions of %s and %s are different or unknown.", dependencyName, dependerVersion);
111            context.getStatusManager().add(new WarnStatus(discrepancyMsg, context));
112        }
113    }
114
115
116    protected static void addFoundVersionStatus(Context context, String name, String version) {
117        String foundDependent = String.format("Found %s version %s", name, nonNull(version));
118        context.getStatusManager().add(new InfoStatus(foundDependent, context));
119    }
120
121    protected static String nameToPropertiesFilename(String name) {
122        return name + "-dependencies.properties";
123    }
124
125    /**
126     * Compares the expected version of a dependency with the actual version found and updates the status context.
127     * If the versions do not match, a warning is added to the context's status manager.
128     *
129     * <p>Note: This method is used be logback-access-jetty11/12 and logback-access-tomcat.</p>
130     *
131     */
132    public void compareExpectedAndFoundVersion(String actualDependencyVersion, Class<?> dependerClass, String dependerVersion,
133                                                      String dependerName, String dependencyName) {
134
135        String propertiesFileName = nameToPropertiesFilename(dependerName);
136
137        String expectedDependencyVersion = this.getExpectedVersionOfDependencyByProperties(dependerClass, propertiesFileName, dependencyName);
138        String safeExpectedDependencyVersion = nonNull(expectedDependencyVersion);
139
140        addFoundVersionStatus(context, dependencyName, actualDependencyVersion);
141        addFoundVersionStatus(context, dependerName, dependerVersion);
142
143        if (!safeExpectedDependencyVersion.equals(actualDependencyVersion)) {
144            String discrepancyMsg = String.format(DEPENDER_S_WAS_EXPECTING_PATTERN, dependerName,
145                    safeExpectedDependencyVersion, dependencyName, actualDependencyVersion);
146            context.getStatusManager().add(new WarnStatus(discrepancyMsg, context));
147
148            String seeMsg = "See also "+CODES_URL+"#versionMismatch";
149            context.getStatusManager().add(new WarnStatus(seeMsg, context));
150
151        }
152    }
153}