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 v1.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.io.IOException;
022import java.io.InputStream;
023import java.lang.module.ModuleDescriptor;
024import java.util.Optional;
025import java.util.Properties;
026
027import static ch.qos.logback.core.CoreConstants.NA;
028
029
030
031/**
032 * Utility class for handling and validating version information of various artifacts.
033 *
034 * <p>It is used by logback-classic, logback-access-common, logback-access-jetty11, logback-access-tomcat, etc.
035 * to alert users about version discrepancies between dependent and dependee artifacts.
036 * </p>
037 *
038 * @since 1.5.25
039 */
040public class VersionUtil {
041
042    /**
043     * Retrieves the version of an artifact, such as logback-core.jar, logback-access-common.jar etc.
044     *
045     * <p>The aClass parameter is assumed to be part of the artifact.
046     * </p>
047     *
048     * <p>The method first attempts to get the version from the module information. If the module version
049     * is not available, it falls back to retrieving the implementation version from the package.
050     * </p>
051     *
052     * @param aClass the class from which to retrieve the version information
053     * @return the version of the artifact where aClass is found, or null if the version cannot be determined
054     * @since 2.0.9
055     */
056    static public String getVersionOfArtifact(Class<?> aClass) {
057        String moduleVersion = getVersionOfClassByModule(aClass);
058        if (moduleVersion != null)
059            return moduleVersion;
060
061        Package pkg = aClass.getPackage();
062        if (pkg == null) {
063            return null;
064        }
065        return pkg.getImplementationVersion();
066    }
067
068    static public String nonNull(String input) {
069        if (input == null) {
070            return NA;
071        } else {
072            return input;
073        }
074    }
075
076    /**
077     * Retrieves the version of an artifact from the artifact's module metadata.
078     *
079     * <p>If the module or its descriptor does not provide a version, the method returns null.
080     * </p>
081     *
082     * @param aClass a class from which to retrieve the version information
083     * @return the version of class' module as a string, or null if the version cannot be determined
084     * @since 2.0.9
085     */
086    static private String getVersionOfClassByModule(Class<?> aClass) {
087        Module module = aClass.getModule();
088        if (module == null)
089            return null;
090
091        ModuleDescriptor md = module.getDescriptor();
092        if (md == null)
093            return null;
094        Optional<String> opt = md.rawVersion();
095        return opt.orElse(null);
096    }
097
098    static String getExpectedVersionOfDependeeByProperties(Class<?> dependentClass, String propertiesFileName, String dependeeNameAsKey) {
099        Properties props = new Properties();
100        // propertiesFileName : logback-access-common-dependees.properties
101        try (InputStream is = dependentClass.getClassLoader()
102                .getResourceAsStream(propertiesFileName)) {
103            if (is != null) {
104                props.load(is);
105                return props.getProperty(dependeeNameAsKey);
106            } else {
107                return null;
108            }
109        } catch (IOException e) {
110            return null;
111        }
112    }
113
114    static public void checkForVersionEquality(Context context, Class<?> dependentClass, Class<?> dependeeClass, String dependentName, String dependeeName) {
115        // the dependent depends on the dependee
116        String dependentVersion = nonNull(getVersionOfArtifact(dependentClass));
117        String dependeeVersion = nonNull(getVersionOfArtifact(dependeeClass));
118
119        addFoundVersionStatus(context, dependentName, dependentVersion);
120
121        if (dependentVersion.equals(NA) || !dependentVersion.equals(dependeeVersion)) {
122            addFoundVersionStatus(context, dependeeName, dependeeVersion);
123            String discrepancyMsg = String.format("Versions of %s and %s are different!", dependeeName, dependentName);
124            context.getStatusManager().add(new WarnStatus(discrepancyMsg, context));
125        }
126    }
127
128    private static void addFoundVersionStatus(Context context, String name, String version) {
129        String foundDependent = String.format("Found %s version %s", name, version);
130        context.getStatusManager().add(new InfoStatus(foundDependent, context));
131    }
132
133
134
135    private static String nameToFilename(String  name) {
136        return name+"-dependees.properties";
137    }
138
139    static public void compareExpectedAndFoundVersion(Context context, Class<?> dependentClass, Class<?> dependeeClass,
140                                               String dependentName, String dependeeName) {
141
142        String expectedDependeeVersion = nonNull(getExpectedVersionOfDependeeByProperties(dependentClass, nameToFilename(dependentName), dependeeName));
143        String actualDependeeVersion = nonNull(getVersionOfArtifact(dependeeClass));
144        String dependentVersion = nonNull(getVersionOfArtifact(dependentClass));
145
146        addFoundVersionStatus(context, dependeeName, actualDependeeVersion);
147        addFoundVersionStatus(context, dependentName, dependentVersion);
148
149        if (!expectedDependeeVersion.equals(actualDependeeVersion)) {
150            String discrepancyMsg = String.format("Expected version of %s is %s but found %s", dependeeName, expectedDependeeVersion, actualDependeeVersion);
151            context.getStatusManager().add(new WarnStatus(discrepancyMsg, context));
152        }
153
154    }
155}