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.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// depender depends on dependency
030
031// dependency synonym dependee (only use dependency)
032// depender synonym dependent (only use depender)
033
034/**
035 * Utility class for handling and validating version information of various artifacts.
036 *
037 * <p>It is used by logback-classic, logback-access-common, logback-access-jetty11, logback-access-tomcat, etc.
038 * to alert users about version discrepancies between depender and dependency artifacts.
039 * </p>
040 *
041 * @since 1.5.25
042 */
043public class VersionUtil {
044
045    /**
046     * @since 1.5.30
047     */
048    Context context;
049
050    /**
051     * Instance methods allow for polymorphism, static methods do not.
052     *
053     *
054     * @param context
055     * @since 1.5.30
056     */
057    protected VersionUtil(Context context) {
058        this.context = context;
059    }
060
061    /**
062     * Retrieves the version of an artifact, such as logback-core.jar, logback-access-common.jar etc.
063     *
064     * <p>The aClass parameter is assumed to be part of the artifact.
065     * </p>
066     *
067     * <p>The method first attempts to get the version from the module information. If the module version
068     * is not available, it falls back to retrieving the implementation version from the package.
069     * </p>
070     *
071     * @param aClass the class from which to retrieve the version information
072     * @return the version of the artifact where aClass is found, or null if the version cannot be determined
073     * @deprecated
074     */
075    static public String getVersionOfArtifact(Class<?> aClass) {
076        String moduleVersion = getVersionOfClassByModule(aClass);
077        if (moduleVersion != null)
078            return moduleVersion;
079
080        Package pkg = aClass.getPackage();
081        if (pkg == null) {
082            return null;
083        }
084        return pkg.getImplementationVersion();
085    }
086
087    static public String nonNull(String input) {
088        if (input == null) {
089            return NA;
090        } else {
091            return input;
092        }
093    }
094
095    /**
096     * Retrieves the version of an artifact from the artifact's module metadata.
097     *
098     * <p>If the module or its descriptor does not provide a version, the method returns null.
099     * </p>
100     *
101     * @param aClass a class from which to retrieve the version information
102     * @return the version of class' module as a string, or null if the version cannot be determined
103     */
104    static private String getVersionOfClassByModule(Class<?> aClass) {
105        Module module = aClass.getModule();
106        if (module == null)
107            return null;
108
109        ModuleDescriptor md = module.getDescriptor();
110        if (md == null)
111            return null;
112        Optional<String> opt = md.rawVersion();
113        return opt.orElse(null);
114    }
115
116   protected String getExpectedVersionOfDependencyByProperties(Class<?> dependerClass, String propertiesFileName, String dependencyNameAsKey) {
117        // derived classes should override
118        return null;
119    }
120
121
122    /**
123     * Compares the versions of a depender and a dependency to determine if they are equal.
124     * Updates the context's status manager with version information and logs a warning
125     * if the versions differ.
126     *
127     * @since 1.5.26
128     */
129    static public void checkForVersionEquality(Context context, String dependerVersion, String dependencyVersion, String dependerName, String dependencyName) {
130        // the depender depends on the dependency
131        addFoundVersionStatus(context, dependerName, dependerVersion);
132
133        dependerVersion = nonNull(dependerVersion);
134
135        if (dependerVersion.equals(NA) || !dependerVersion.equals(dependencyVersion)) {
136            addFoundVersionStatus(context, dependencyName, dependencyVersion);
137            String discrepancyMsg = String.format("Versions of %s and %s are different or unknown.", dependencyName, dependerVersion);
138            context.getStatusManager().add(new WarnStatus(discrepancyMsg, context));
139        }
140    }
141
142
143    protected static void addFoundVersionStatus(Context context, String name, String version) {
144        String foundDependent = String.format("Found %s version %s", name, nonNull(version));
145        context.getStatusManager().add(new InfoStatus(foundDependent, context));
146    }
147
148    protected static String nameToPropertiesFilename(String name) {
149        return name + "-dependencies.properties";
150    }
151
152    /**
153     * Compares the expected version of a dependency with the actual version found and updates the status context.
154     * If the versions do not match, a warning is added to the context's status manager.
155     *
156     * <p>Note: This method is used be logback-access-jetty11/12 and logback-access-tomcat.</p>
157     *
158     */
159    public void compareExpectedAndFoundVersion(String actualDependencyVersion, Class<?> dependerClass, String dependerVersion,
160                                                      String dependerName, String dependencyName) {
161
162        String propertiesFileName = nameToPropertiesFilename(dependerName);
163
164        String expectedDependencyVersion = this.getExpectedVersionOfDependencyByProperties(dependerClass, propertiesFileName, dependencyName);
165        String safeExpectedDependencyVersion = nonNull(expectedDependencyVersion);
166
167        addFoundVersionStatus(context, dependencyName, actualDependencyVersion);
168        addFoundVersionStatus(context, dependerName, dependerVersion);
169
170        if (!safeExpectedDependencyVersion.equals(actualDependencyVersion)) {
171            String discrepancyMsg = String.format("For %s, expected version %s but found %s", dependencyName, safeExpectedDependencyVersion, actualDependencyVersion);
172            context.getStatusManager().add(new WarnStatus(discrepancyMsg, context));
173        }
174    }
175}