1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  
15  package ch.qos.logback.core.util;
16  
17  import ch.qos.logback.core.Context;
18  import ch.qos.logback.core.status.InfoStatus;
19  import ch.qos.logback.core.status.WarnStatus;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.lang.module.ModuleDescriptor;
24  import java.util.Optional;
25  import java.util.Properties;
26  
27  import static ch.qos.logback.core.CoreConstants.NA;
28  
29  // depender depends on dependency
30  
31  // dependency synonym dependee (only use dependency)
32  // depender synonym dependent (only use depender)
33  
34  /**
35   * Utility class for handling and validating version information of various artifacts.
36   *
37   * <p>It is used by logback-classic, logback-access-common, logback-access-jetty11, logback-access-tomcat, etc.
38   * to alert users about version discrepancies between depender and dependency artifacts.
39   * </p>
40   *
41   * @since 1.5.25
42   */
43  public class VersionUtil {
44  
45      /**
46       * Retrieves the version of an artifact, such as logback-core.jar, logback-access-common.jar etc.
47       *
48       * <p>The aClass parameter is assumed to be part of the artifact.
49       * </p>
50       *
51       * <p>The method first attempts to get the version from the module information. If the module version
52       * is not available, it falls back to retrieving the implementation version from the package.
53       * </p>
54       *
55       * @param aClass the class from which to retrieve the version information
56       * @return the version of the artifact where aClass is found, or null if the version cannot be determined
57       * @deprecated
58       */
59      static public String getVersionOfArtifact(Class<?> aClass) {
60          String moduleVersion = getVersionOfClassByModule(aClass);
61          if (moduleVersion != null)
62              return moduleVersion;
63  
64          Package pkg = aClass.getPackage();
65          if (pkg == null) {
66              return null;
67          }
68          return pkg.getImplementationVersion();
69      }
70  
71      static public String nonNull(String input) {
72          if (input == null) {
73              return NA;
74          } else {
75              return input;
76          }
77      }
78  
79      /**
80       * Retrieves the version of an artifact from the artifact's module metadata.
81       *
82       * <p>If the module or its descriptor does not provide a version, the method returns null.
83       * </p>
84       *
85       * @param aClass a class from which to retrieve the version information
86       * @return the version of class' module as a string, or null if the version cannot be determined
87       */
88      static private String getVersionOfClassByModule(Class<?> aClass) {
89          Module module = aClass.getModule();
90          if (module == null)
91              return null;
92  
93          ModuleDescriptor md = module.getDescriptor();
94          if (md == null)
95              return null;
96          Optional<String> opt = md.rawVersion();
97          return opt.orElse(null);
98      }
99  
100     /**
101      * Retrieves the version of a module using a properties file associated with the module.
102      *
103      * <p>Unfortunately, this code cannot be called by other modules. It needs to be duplicated.</p>
104      *
105      * <p>The method looks for a properties file with a name derived from the <code>moduleName</code> parameter,
106      * in the same location, e.g. package, as the <code>aClass</code> parameter. It attempts to load the properties file
107      * and fetch the version information using a specific key.
108      * </p>
109      *
110      * <p>The properties file is expected to be in the same package as the class provided, and named
111      * <code>moduleName-version.properties</code>. The properties file should contain a single key-value pair,
112      * where the key is <code>moduleName-version</code>, and the value is the module version.
113      *
114      * @param aClass     the class used to locate the resource file, the properties file is expected to be in the same package
115      * @param moduleName the name of the module, which is used to construct the properties file name and the key
116      * @return the version of the module as a string, or null if the version cannot be determined
117      * @since 1.5.26
118      * @deprecated (this code cannot be shared and is useless here)
119      */
120     static public String getArtifactVersionBySelfDeclaredProperties(Class<?> aClass, String moduleName) {
121         Properties props = new Properties();
122         // example propertiesFileName: logback-core-version.properties
123         //
124         String propertiesFileName = moduleName + "-version.properties";
125         String propertyKey = moduleName + "-version";
126         try (InputStream is = aClass.getResourceAsStream(propertiesFileName)) {
127             if (is != null) {
128                 props.load(is);
129                 return props.getProperty(propertyKey);
130             } else {
131                 return null;
132             }
133         } catch (IOException e) {
134             return null;
135         }
136     }
137 
138 
139 
140 
141     static String getExpectedVersionOfDependencyByProperties(Class<?> dependerClass, String propertiesFileName, String dependencyNameAsKey) {
142         Properties props = new Properties();
143         // propertiesFileName : logback-access-common-dependencies.properties
144         try (InputStream is = dependerClass.getClassLoader()
145                 .getResourceAsStream(propertiesFileName)) {
146             if (is != null) {
147                 props.load(is);
148                 return props.getProperty(dependencyNameAsKey);
149             } else {
150                 return null;
151             }
152         } catch (IOException e) {
153             return null;
154         }
155     }
156 
157 
158     static public void checkForVersionEquality(Context context, Class<?> dependerClass, Class<?> dependencyClass, String dependerName, String dependencyName) {
159         // the depender depends on the dependency
160         String dependerVersion = nonNull(getVersionOfArtifact(dependerClass));
161         String dependencyVersion = nonNull(getVersionOfArtifact(dependencyClass));
162 
163         checkForVersionEquality(context, dependerVersion, dependencyVersion, dependerName, dependencyName);
164     }
165 
166     static public void checkForVersionEquality(Context context, Class<?> dependerClass, String dependencyVersion, String dependerName, String dependencyName) {
167         String dependerVersion = nonNull(getVersionOfArtifact(dependerClass));
168         checkForVersionEquality(context, dependerVersion, dependencyVersion, dependerName, dependencyName);
169     }
170 
171 
172     /**
173      * Compares the versions of a depender and a dependency to determine if they are equal.
174      * Updates the context's status manager with version information and logs a warning
175      * if the versions differ.
176      *
177      * @since 1.5.26
178      */
179     static public void checkForVersionEquality(Context context, String dependerVersion, String dependencyVersion, String dependerName, String dependencyName) {
180         // the depender depends on the dependency
181         addFoundVersionStatus(context, dependerName, dependerVersion);
182 
183         dependerVersion = nonNull(dependerVersion);
184 
185         if (dependerVersion.equals(NA) || !dependerVersion.equals(dependencyVersion)) {
186             addFoundVersionStatus(context, dependencyName, dependencyVersion);
187             String discrepancyMsg = String.format("Versions of %s and %s are different or unknown.", dependencyName, dependerVersion);
188             context.getStatusManager().add(new WarnStatus(discrepancyMsg, context));
189         }
190     }
191 
192 
193     private static void addFoundVersionStatus(Context context, String name, String version) {
194         String foundDependent = String.format("Found %s version %s", name, nonNull(version));
195         context.getStatusManager().add(new InfoStatus(foundDependent, context));
196     }
197 
198     private static String nameToPropertiesFilename(String name) {
199         return name + "-dependencies.properties";
200     }
201 
202     /**
203      * Compares the expected version of a dependency with the actual version found and updates the status context.
204      * If the versions do not match, a warning is added to the context's status manager.
205      *
206      * <p>Note: This method is used be logback-access-jetty11/12 and logback-access-tomcat.</p>
207      *
208      */
209     static public void compareExpectedAndFoundVersion(Context context, String actualDependencyVersion, Class<?> dependerClass, String dependerVersion,
210                                                       String dependerName, String dependencyName) {
211 
212         String expectedDependencyVersion = nonNull(getExpectedVersionOfDependencyByProperties(dependerClass, nameToPropertiesFilename(dependerName), dependencyName));
213 
214         addFoundVersionStatus(context, dependencyName, actualDependencyVersion);
215         addFoundVersionStatus(context, dependerName, dependerVersion);
216 
217         if (!expectedDependencyVersion.equals(actualDependencyVersion)) {
218             String discrepancyMsg = String.format("Expected version of %s is %s but found %s", dependencyName, expectedDependencyVersion, actualDependencyVersion);
219             context.getStatusManager().add(new WarnStatus(discrepancyMsg, context));
220         }
221     }
222 }