1 /*
2  * This file is part of ClassGraph.
3  *
4  * Author: Luke Hutchison
5  *
6  * Hosted at: https://github.com/classgraph/classgraph
7  *
8  * --
9  *
10  * The MIT License (MIT)
11  *
12  * Copyright (c) 2019 Luke Hutchison
13  *
14  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
15  * documentation files (the "Software"), to deal in the Software without restriction, including without
16  * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
17  * the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
18  * conditions:
19  *
20  * The above copyright notice and this permission notice shall be included in all copies or substantial
21  * portions of the Software.
22  *
23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
24  * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
25  * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
26  * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
27  * OR OTHER DEALINGS IN THE SOFTWARE.
28  */

29 package nonapi.io.github.classgraph.utils;
30
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.net.URL;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.nio.file.Paths;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Locale;
40 import java.util.Properties;
41
42 import javax.xml.parsers.DocumentBuilderFactory;
43 import javax.xml.xpath.XPathConstants;
44 import javax.xml.xpath.XPathFactory;
45
46 import org.w3c.dom.Document;
47
48 import io.github.classgraph.ClassGraph;
49
50 /** Finds the version number of ClassGraph, and the version of the JDK. */
51 public final class VersionFinder {
52
53     /** The Maven package for ClassGraph. */
54     private static final String MAVEN_PACKAGE = "io.github.classgraph";
55
56     /** The Maven artifact for ClassGraph. */
57     private static final String MAVEN_ARTIFACT = "classgraph";
58
59     /** The operating system type. */
60     public static final OperatingSystem OS;
61
62     /** Java version string. */
63     public static final String JAVA_VERSION = getProperty("java.version");
64
65     /** Java major version -- 7 for "1.7", 8 for "1.8.0_244", 9 for "9", 11 for "11-ea", etc. */
66     public static final int JAVA_MAJOR_VERSION;
67
68     /** Java minor version -- 0 for "11.0.4" */
69     public static final int JAVA_MINOR_VERSION;
70
71     /** Java minor version -- 4 for "11.0.4" */
72     public static final int JAVA_SUB_VERSION;
73
74     /** Java is EA release -- true for "11-ea", etc. */
75     public static final boolean JAVA_IS_EA_VERSION;
76
77     static {
78         int javaMajorVersion = 0;
79         int javaMinorVersion = 0;
80         int javaSubVersion = 0;
81         final List<Integer> versionParts = new ArrayList<>();
82         if (JAVA_VERSION != null) {
83             for (final String versionPart : JAVA_VERSION.split("[^0-9]+")) {
84                 try {
85                     versionParts.add(Integer.parseInt(versionPart));
86                 } catch (final NumberFormatException e) {
87                     // Skip
88                 }
89             }
90             if (!versionParts.isEmpty() && versionParts.get(0) == 1) {
91                 // 1.7 or 1.8 -> 7 or 8
92                 versionParts.remove(0);
93             }
94             if (versionParts.isEmpty()) {
95                 throw new RuntimeException("Could not determine Java version: " + JAVA_VERSION);
96             }
97             javaMajorVersion = versionParts.get(0);
98             if (versionParts.size() > 1) {
99                 javaMinorVersion = versionParts.get(1);
100             }
101             if (versionParts.size() > 2) {
102                 javaSubVersion = versionParts.get(2);
103             }
104         }
105         JAVA_MAJOR_VERSION = javaMajorVersion;
106         JAVA_MINOR_VERSION = javaMinorVersion;
107         JAVA_SUB_VERSION = javaSubVersion;
108         JAVA_IS_EA_VERSION = JAVA_VERSION != null && JAVA_VERSION.endsWith("-ea");
109     }
110
111     /** The operating system type. */
112     public enum OperatingSystem {
113         /** Windows. */
114         Windows,
115
116         /** Mac OS X. */
117         MacOSX,
118
119         /** Linux. */
120         Linux,
121
122         /** Solaris. */
123         Solaris,
124
125         /** BSD. */
126         BSD,
127
128         /** Unix or AIX. */
129         Unix,
130
131         /** Unknown. */
132         Unknown
133     }
134
135     static {
136         final String osName = getProperty("os.name""unknown").toLowerCase(Locale.ENGLISH);
137         if (osName == null) {
138             OS = OperatingSystem.Unknown;
139         } else if (osName.contains("mac") || osName.contains("darwin")) {
140             OS = OperatingSystem.MacOSX;
141         } else if (osName.contains("win")) {
142             OS = OperatingSystem.Windows;
143         } else if (osName.contains("nux")) {
144             OS = OperatingSystem.Linux;
145         } else if (osName.contains("sunos") || osName.contains("solaris")) {
146             OS = OperatingSystem.Solaris;
147         } else if (osName.contains("bsd")) {
148             OS = OperatingSystem.Unix;
149         } else if (osName.contains("nix") || osName.contains("aix")) {
150             OS = OperatingSystem.Unix;
151         } else {
152             OS = OperatingSystem.Unknown;
153         }
154     }
155
156     // -------------------------------------------------------------------------------------------------------------
157
158     /**
159      * Constructor.
160      */

161     private VersionFinder() {
162         // Cannot be constructed
163     }
164
165     // -------------------------------------------------------------------------------------------------------------
166
167     /**
168      * Get a system property (returning null if a SecurityException was thrown).
169      *
170      * @param propName
171      *            the property name
172      * @return the property value
173      */

174     public static String getProperty(final String propName) {
175         try {
176             return System.getProperty(propName);
177         } catch (final SecurityException e) {
178             return null;
179         }
180     }
181
182     /**
183      * Get a system property (returning null if a SecurityException was thrown).
184      *
185      * @param propName
186      *            the property name
187      * @param defaultVal
188      *            the default value for the property
189      * @return the property value, or the default if the property is not defined.
190      */

191     public static String getProperty(final String propName, final String defaultVal) {
192         try {
193             return System.getProperty(propName, defaultVal);
194         } catch (final SecurityException e) {
195             return null;
196         }
197     }
198
199     // -------------------------------------------------------------------------------------------------------------
200
201     /**
202      * Get the version number of ClassGraph.
203      *
204      * @return the version number of ClassGraph.
205      */

206     public static synchronized String getVersion() {
207         // Try to get version number from pom.xml (available when running in Eclipse)
208         final Class<?> cls = ClassGraph.class;
209         try {
210             final String className = cls.getName();
211             final URL classpathResource = cls.getResource("/" + JarUtils.classNameToClassfilePath(className));
212             if (classpathResource != null) {
213                 final Path absolutePackagePath = Paths.get(classpathResource.toURI()).getParent();
214                 final int packagePathSegments = className.length() - className.replace(".""").length();
215                 // Remove package segments from path
216                 Path path = absolutePackagePath;
217                 for (int i = 0; i < packagePathSegments && path != null; i++) {
218                     path = path.getParent();
219                 }
220                 // Remove up to two more levels for "bin" or "target/classes"
221                 for (int i = 0; i < 3 && path != null; i++, path = path.getParent()) {
222                     final Path pom = path.resolve("pom.xml");
223                     try (InputStream is = Files.newInputStream(pom)) {
224                         final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
225                         doc.getDocumentElement().normalize();
226                         String version = (String) XPathFactory.newInstance().newXPath().compile("/project/version")
227                                 .evaluate(doc, XPathConstants.STRING);
228                         if (version != null) {
229                             version = version.trim();
230                             if (!version.isEmpty()) {
231                                 return version;
232                             }
233                         }
234                     } catch (final IOException e) {
235                         // Not found
236                     }
237                 }
238             }
239         } catch (final Exception e) {
240             // Ignore
241         }
242
243         // Try to get version number from maven properties in jar's META-INF directory
244         try (InputStream is = cls.getResourceAsStream(
245                 "/META-INF/maven/" + MAVEN_PACKAGE + "/" + MAVEN_ARTIFACT + "/pom.properties")) {
246             if (is != null) {
247                 final Properties p = new Properties();
248                 p.load(is);
249                 final String version = p.getProperty("version""").trim();
250                 if (!version.isEmpty()) {
251                     return version;
252                 }
253             }
254         } catch (final IOException e) {
255             // Ignore
256         }
257
258         // Fallback to using Java API (version number is obtained from MANIFEST.MF)
259         final Package pkg = cls.getPackage();
260         if (pkg != null) {
261             String version = pkg.getImplementationVersion();
262             if (version == null) {
263                 version = "";
264             }
265             version = version.trim();
266             if (version.isEmpty()) {
267                 version = pkg.getSpecificationVersion();
268                 if (version == null) {
269                     version = "";
270                 }
271                 version = version.trim();
272             }
273             if (!version.isEmpty()) {
274                 return version;
275             }
276         }
277         return "unknown";
278     }
279 }
280