1 /*
2  * Copyright 2015-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15 package com.amazonaws.util;
16
17 import java.util.Arrays;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20
21 /**
22  * @see http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html
23  */

24 public class JavaVersionParser {
25
26     public static final String JAVA_VERSION_PROPERTY = "java.version";
27
28     private static String MAJOR_VERSION_FAMILY_PATTERN = "\\d+";
29     private static String MAJOR_VERSION_PATTERN = "\\d+";
30     private static String MAINTENANCE_NUMBER_PATTERN = "\\d+";
31     private static String UPDATE_NUMBER_PATTERN = "\\d+";
32
33     private static Pattern VERSION_REGEX = Pattern.compile(String.format("(%s)\\.(%s)\\.(%s)(?:_(%s))?.*",
34             MAJOR_VERSION_FAMILY_PATTERN, MAJOR_VERSION_PATTERN, MAINTENANCE_NUMBER_PATTERN, UPDATE_NUMBER_PATTERN));
35
36     private static final JavaVersion currentJavaVersion = parseJavaVersion(System.getProperty(JAVA_VERSION_PROPERTY));
37
38     private JavaVersionParser() {
39     }
40
41     /**
42      * @return The {@link JavaVersion} of this JVM.
43      */

44     public static JavaVersion getCurrentJavaVersion() {
45         return currentJavaVersion;
46     }
47
48     public static JavaVersion parseJavaVersion(final String fullVersionString) {
49         if (!StringUtils.isNullOrEmpty(fullVersionString)) {
50             final Matcher matcher = VERSION_REGEX.matcher(fullVersionString);
51             if (matcher.matches()) {
52                 final Integer majorVersionFamily = NumberUtils.tryParseInt(matcher.group(1));
53                 final Integer majorVersion = NumberUtils.tryParseInt(matcher.group(2));
54                 final Integer maintenanceNumber = NumberUtils.tryParseInt(matcher.group(3));
55                 final Integer updateNumber = NumberUtils.tryParseInt(matcher.group(4));
56                 return new JavaVersion(majorVersionFamily, majorVersion, maintenanceNumber, updateNumber);
57             }
58         }
59         return JavaVersion.UNKNOWN;
60     }
61
62     /**
63      * Struct like class representing a specific version of Java. Contains the major and minor
64      * version identifiers and a descriptive enum identifying which major version this JVM belongs
65      * to if we are able to identify it
66      */

67     public static final class JavaVersion implements Comparable<JavaVersion> {
68
69         public static final JavaVersionParser.JavaVersion UNKNOWN = new JavaVersionParser.JavaVersion(nullnullnull,
70                 null);
71
72         private final Integer[] tokenizedVersion;
73         private final Integer majorVersionFamily;
74         private final Integer majorVersion;
75         private final Integer maintenanceNumber;
76         private final Integer updateNumber;
77         private final KnownJavaVersions knownVersion;
78
79         public JavaVersion(final Integer majorVersionFamily, final Integer majorVersion,
80                 final Integer maintenanceNumber, final Integer updateNumber) {
81             this.majorVersionFamily = majorVersionFamily;
82             this.majorVersion = majorVersion;
83             this.maintenanceNumber = maintenanceNumber;
84             this.updateNumber = updateNumber;
85             this.knownVersion = KnownJavaVersions.fromMajorVersion(majorVersionFamily, majorVersion);
86             this.tokenizedVersion = this.getTokenizedVersion();
87         }
88
89         private Integer[] getTokenizedVersion() {
90             return new Integer[] { this.majorVersionFamily, this.majorVersion, this.maintenanceNumber,
91                     this.updateNumber };
92         }
93
94         /**
95          * @return Major version family if available. I.E. if the major version family string is
96          *         '1.7.0_60' then the major version family will be 1
97          */

98         public Integer getMajorVersionFamily() {
99             return this.majorVersionFamily;
100         }
101
102         /**
103          * @return Major version ordinal if available. Examples include '6', '7', '8'
104          */

105         public Integer getMajorVersion() {
106             return this.majorVersion;
107         }
108
109         /**
110          * @return Major version string if available. Examples include '1.6', '1.7', '1.8'
111          */

112         public String getMajorVersionString() {
113             return String.format("%d.%d"this.majorVersionFamily, this.majorVersion);
114         }
115
116         /**
117          * @return Maintenance number of Java version. If the version is '1.6.1_20' then '1' is the
118          *         maintenance number
119          */

120         public Integer getMaintenanceNumber() {
121             return this.maintenanceNumber;
122         }
123
124         /**
125          * @return Update number of Java version. If the version is '1.6.1_20' then '20' is the
126          *         update number
127          */

128         public Integer getUpdateNumber() {
129             return this.updateNumber;
130         }
131
132         /**
133          * @return {@link KnownJavaVersions} representing the major version of the Java version if
134          *         it's identifiable
135          */

136         public KnownJavaVersions getKnownVersion() {
137             return this.knownVersion;
138         }
139
140         @Override
141         public int compareTo(final JavaVersion other) {
142             for (int i = 0; i < this.tokenizedVersion.length; i++) {
143                 final int tokenComparison = ComparableUtils.safeCompare(this.tokenizedVersion[i],
144                         other.tokenizedVersion[i]);
145                 // If one token is larger return the comparison otherwise proceed to next token
146                 if (tokenComparison != 0) {
147                     return tokenComparison;
148                 }
149             }
150             return 0;
151         }
152
153         @Override
154         public int hashCode() {
155             final int prime = 31;
156             int result = 1;
157             result = (prime * result) + ((this.knownVersion == null) ? 0 : this.knownVersion.hashCode());
158             result = (prime * result) + ((this.maintenanceNumber == null) ? 0 : this.maintenanceNumber.hashCode());
159             result = (prime * result) + ((this.majorVersion == null) ? 0 : this.majorVersion.hashCode());
160             result = (prime * result) + ((this.majorVersionFamily == null) ? 0 : this.majorVersionFamily.hashCode());
161             result = (prime * result) + Arrays.hashCode(this.tokenizedVersion);
162             result = (prime * result) + ((this.updateNumber == null) ? 0 : this.updateNumber.hashCode());
163             return result;
164         }
165
166         @Override
167         public boolean equals(final Object obj) {
168             if (this == obj) {
169                 return true;
170             }
171             if (obj == null) {
172                 return false;
173             }
174             if (this.getClass() != obj.getClass()) {
175                 return false;
176             }
177             final JavaVersion other = (JavaVersion) obj;
178             if (this.knownVersion != other.knownVersion) {
179                 return false;
180             }
181             if (this.maintenanceNumber == null) {
182                 if (other.maintenanceNumber != null) {
183                     return false;
184                 }
185             } else if (!this.maintenanceNumber.equals(other.maintenanceNumber)) {
186                 return false;
187             }
188             if (this.majorVersion == null) {
189                 if (other.majorVersion != null) {
190                     return false;
191                 }
192             } else if (!this.majorVersion.equals(other.majorVersion)) {
193                 return false;
194             }
195             if (this.majorVersionFamily == null) {
196                 if (other.majorVersionFamily != null) {
197                     return false;
198                 }
199             } else if (!this.majorVersionFamily.equals(other.majorVersionFamily)) {
200                 return false;
201             }
202             if (!Arrays.equals(this.tokenizedVersion, other.tokenizedVersion)) {
203                 return false;
204             }
205             if (this.updateNumber == null) {
206                 if (other.updateNumber != null) {
207                     return false;
208                 }
209             } else if (!this.updateNumber.equals(other.updateNumber)) {
210                 return false;
211             }
212             return true;
213         }
214
215     }
216
217     /**
218      * Enum representing all the Java versions we know about and a special enum value
219      * {@link KnownJavaVersions#UNKNOWN} for ones we don't yet know about
220      */

221     public enum KnownJavaVersions {
222         JAVA_6(1, 6),
223         JAVA_7(1, 7),
224         JAVA_8(1, 8),
225         JAVA_9(1, 9),
226         UNKNOWN(0, -1);
227
228         private Integer knownMajorVersionFamily;
229         private Integer knownMajorVersion;
230
231         private KnownJavaVersions(final int majorVersionFamily, final int majorVersion) {
232             this.knownMajorVersionFamily = majorVersionFamily;
233             this.knownMajorVersion = Integer.valueOf(majorVersion);
234         }
235
236         /**
237          * Tries to determine a known version from the parsed major version components
238          *
239          * @param majorVersionFamily
240          *            Major version family of the JVM. Currently only 1 is known (i.e. '1.7')
241          * @param majorVersion
242          *            Major version of JVM (6, 7, 8, etc)
243          * @return A {@link KnownJavaVersions} or {@link KnownJavaVersions#UNKNOWN} if unable to
244          *         determine
245          */

246         public static KnownJavaVersions fromMajorVersion(final Integer majorVersionFamily, final Integer majorVersion) {
247             for (final KnownJavaVersions version : KnownJavaVersions.values()) {
248                 if (version.isMajorVersion(majorVersionFamily, majorVersion)) {
249                     return version;
250                 }
251             }
252             return UNKNOWN;
253         }
254
255         /**
256          * @param majorVersionFamily
257          *            Major version family of the JVM. Currently only 1 is known (i.e. '1.7')
258          * @param majorVersion
259          *            Major version of JVM (6, 7, 8, etc)
260          * @return True if the major version is applicable to this Java Version
261          */

262         private boolean isMajorVersion(final Integer majorVersionFamily, final Integer majorVersion) {
263             return this.knownMajorVersionFamily.equals(majorVersionFamily)
264                     && this.knownMajorVersion.equals(majorVersion);
265         }
266
267     }
268 }
269