001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3;
018
019import org.apache.commons.lang3.math.NumberUtils;
020
021/**
022 * An enum representing all the versions of the Java specification.
023 * This is intended to mirror available values from the
024 * <em>java.specification.version</em> System property.
025 *
026 * @since 3.0
027 */
028public enum JavaVersion {
029
030    /**
031     * The Java version reported by Android. This is not an official Java version number.
032     */
033    JAVA_0_9(1.5f, "0.9"),
034
035    /**
036     * Java 1.1.
037     */
038    JAVA_1_1(1.1f, "1.1"),
039
040    /**
041     * Java 1.2.
042     */
043    JAVA_1_2(1.2f, "1.2"),
044
045    /**
046     * Java 1.3.
047     */
048    JAVA_1_3(1.3f, "1.3"),
049
050    /**
051     * Java 1.4.
052     */
053    JAVA_1_4(1.4f, "1.4"),
054
055    /**
056     * Java 1.5.
057     */
058    JAVA_1_5(1.5f, "1.5"),
059
060    /**
061     * Java 1.6.
062     */
063    JAVA_1_6(1.6f, "1.6"),
064
065    /**
066     * Java 1.7.
067     */
068    JAVA_1_7(1.7f, "1.7"),
069
070    /**
071     * Java 1.8.
072     */
073    JAVA_1_8(1.8f, "1.8"),
074
075    /**
076     * Java 1.9.
077     *
078     * @deprecated As of release 3.5, replaced by {@link #JAVA_9}
079     */
080    @Deprecated
081    JAVA_1_9(9.0f, "9"),
082
083    /**
084     * Java 9.
085     *
086     * @since 3.5
087     */
088    JAVA_9(9.0f, "9"),
089
090    /**
091     * Java 10.
092     *
093     * @since 3.7
094     */
095    JAVA_10(10.0f, "10"),
096
097    /**
098     * Java 11.
099     *
100     * @since 3.8
101     */
102    JAVA_11(11.0f, "11"),
103
104    /**
105     * Java 12.
106     *
107     * @since 3.9
108     */
109    JAVA_12(12.0f, "12"),
110
111    /**
112     * Java 13.
113     *
114     * @since 3.9
115     */
116    JAVA_13(13.0f, "13"),
117
118    /**
119     * Java 14.
120     *
121     * @since 3.11
122     */
123    JAVA_14(14.0f, "14"),
124
125    /**
126     * Java 15.
127     *
128     * @since 3.11
129     */
130    JAVA_15(15.0f, "15"),
131
132    /**
133     * Java 16.
134     *
135     * @since 3.11
136     */
137    JAVA_16(16.0f, "16"),
138
139    /**
140     * Java 17.
141     *
142     * @since 3.12.0
143     */
144    JAVA_17(17.0f, "17"),
145
146    /**
147     * Java 18.
148     *
149     * @since 3.13.0
150     */
151    JAVA_18(18.0f, "18"),
152
153    /**
154     * Java 19.
155     *
156     * @since 3.13.0
157     */
158    JAVA_19(19.0f, "19"),
159
160    /**
161     * Java 20.
162     *
163     * @since 3.13.0
164     */
165    JAVA_20(20, "20"),
166
167    /**
168     * Java 21.
169     *
170     * @since 3.13.0
171     */
172    JAVA_21(21, "21"),
173
174    /**
175     * Java 22.
176     *
177     * @since 3.15.0
178     */
179    JAVA_22(22, "22"),
180
181    /**
182     * Java 23.
183     *
184     * @since 3.18.0
185     */
186    JAVA_23(23, "23"),
187
188    /**
189     * Java 24.
190     *
191     * @since 3.18.0
192     */
193    JAVA_24(24, "24"),
194
195    /**
196     * The most recent Java version. Mainly introduced to avoid to break when a new version of Java is used.
197     */
198    JAVA_RECENT(maxVersion(), Float.toString(maxVersion()));
199
200    /**
201     * The regex to split version strings.
202     */
203    private static final String VERSION_SPLIT_REGEX = "\\.";
204
205    /**
206     * Transforms the given string with a Java version number to the
207     * corresponding constant of this enumeration class. This method is used
208     * internally.
209     *
210     * @param versionStr the Java version as string
211     * @return the corresponding enumeration constant or <strong>null</strong> if the
212     * version is unknown
213     */
214    static JavaVersion get(final String versionStr) {
215        if (versionStr == null) {
216            return null;
217        }
218        switch (versionStr) {
219        case "0.9":
220            return JAVA_0_9;
221        case "1.1":
222            return JAVA_1_1;
223        case "1.2":
224            return JAVA_1_2;
225        case "1.3":
226            return JAVA_1_3;
227        case "1.4":
228            return JAVA_1_4;
229        case "1.5":
230            return JAVA_1_5;
231        case "1.6":
232            return JAVA_1_6;
233        case "1.7":
234            return JAVA_1_7;
235        case "1.8":
236            return JAVA_1_8;
237        case "9":
238            return JAVA_9;
239        case "10":
240            return JAVA_10;
241        case "11":
242            return JAVA_11;
243        case "12":
244            return JAVA_12;
245        case "13":
246            return JAVA_13;
247        case "14":
248            return JAVA_14;
249        case "15":
250            return JAVA_15;
251        case "16":
252            return JAVA_16;
253        case "17":
254            return JAVA_17;
255        case "18":
256            return JAVA_18;
257        case "19":
258            return JAVA_19;
259        case "20":
260            return JAVA_20;
261        case "21":
262            return JAVA_21;
263        case "22":
264            return JAVA_22;
265        case "23":
266            return JAVA_23;
267        case "24":
268            return JAVA_24;
269        default:
270            final float v = toFloatVersion(versionStr);
271            if (v - 1. < 1.) { // then we need to check decimals > .9
272                final int firstComma = Math.max(versionStr.indexOf('.'), versionStr.indexOf(','));
273                final int end = Math.max(versionStr.length(), versionStr.indexOf(',', firstComma));
274                if (Float.parseFloat(versionStr.substring(firstComma + 1, end)) > .9f) {
275                    return JAVA_RECENT;
276                }
277            } else if (v > 10) {
278                return JAVA_RECENT;
279            }
280            return null;
281        }
282    }
283
284    /**
285     * Transforms the given string with a Java version number to the
286     * corresponding constant of this enumeration class. This method is used
287     * internally.
288     *
289     * @param versionStr the Java version as string
290     * @return the corresponding enumeration constant or <strong>null</strong> if the
291     * version is unknown
292     */
293    static JavaVersion getJavaVersion(final String versionStr) {
294        return get(versionStr);
295    }
296
297    /**
298     * Gets the Java Version from the system or 99.0 if the {@code java.specification.version} system property is not set.
299     *
300     * @return the value of {@code java.specification.version} system property or 99.0 if it is not set.
301     */
302    private static float maxVersion() {
303        final float v = toFloatVersion(SystemProperties.getJavaSpecificationVersion("99.0"));
304        return v > 0 ? v : 99f;
305    }
306
307    static String[] split(final String value) {
308        return value.split(VERSION_SPLIT_REGEX);
309    }
310
311    /**
312     * Parses a float value from a String.
313     *
314     * @param value the String to parse.
315     * @return the float value represented by the string or -1 if the given String cannot be parsed.
316     */
317    private static float toFloatVersion(final String value) {
318        final int defaultReturnValue = -1;
319        if (!value.contains(".")) {
320            return NumberUtils.toFloat(value, defaultReturnValue);
321        }
322        final String[] toParse = split(value);
323        if (toParse.length >= 2) {
324            return NumberUtils.toFloat(toParse[0] + '.' + toParse[1], defaultReturnValue);
325        }
326        return defaultReturnValue;
327    }
328
329    /**
330     * The float value.
331     */
332    private final float value;
333
334    /**
335     * The standard name.
336     */
337    private final String name;
338
339    /**
340     * Constructs a new instance.
341     *
342     * @param value  the float value
343     * @param name  the standard name, not null
344     */
345    JavaVersion(final float value, final String name) {
346        this.value = value;
347        this.name = name;
348    }
349
350    /**
351     * Tests whether this version of Java is at least the version of Java passed in.
352     *
353     * <p>For example:<br>
354     *  {@code myVersion.atLeast(JavaVersion.JAVA_1_4)}</p>
355     *
356     * @param requiredVersion  the version to check against, not null
357     * @return true if this version is equal to or greater than the specified version
358     */
359    public boolean atLeast(final JavaVersion requiredVersion) {
360        return this.value >= requiredVersion.value;
361    }
362
363    /**
364     * Tests whether this version of Java is at most the version of Java passed in.
365     *
366     * <p>For example:<br>
367     *  {@code myVersion.atMost(JavaVersion.JAVA_1_4)}</p>
368     *
369     * @param requiredVersion  the version to check against, not null
370     * @return true if this version is equal to or greater than the specified version
371     * @since 3.9
372     */
373    public boolean atMost(final JavaVersion requiredVersion) {
374        return this.value <= requiredVersion.value;
375    }
376
377    /**
378     * The string value is overridden to return the standard name.
379     *
380     * <p>For example, {@code "1.5"}.</p>
381     *
382     * @return the name, not null
383     */
384    @Override
385    public String toString() {
386        return name;
387    }
388}