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 * Enumerates all known versions of the Java specification. This is intended to mirror available values from the <em>java.specification.version</em> System
023 * property.
024 *
025 * @since 3.0
026 */
027public enum JavaVersion {
028
029    /**
030     * The Java version reported by Android. This is not an official Java version number.
031     */
032    JAVA_0_9(1.5f, "0.9"),
033
034    /**
035     * Java 1.1.
036     */
037    JAVA_1_1(1.1f, "1.1"),
038
039    /**
040     * Java 1.2.
041     */
042    JAVA_1_2(1.2f, "1.2"),
043
044    /**
045     * Java 1.3.
046     */
047    JAVA_1_3(1.3f, "1.3"),
048
049    /**
050     * Java 1.4.
051     */
052    JAVA_1_4(1.4f, "1.4"),
053
054    /**
055     * Java 1.5.
056     */
057    JAVA_1_5(1.5f, "1.5"),
058
059    /**
060     * Java 1.6.
061     */
062    JAVA_1_6(1.6f, "1.6"),
063
064    /**
065     * Java 1.7.
066     */
067    JAVA_1_7(1.7f, "1.7"),
068
069    /**
070     * Java 1.8.
071     */
072    JAVA_1_8(1.8f, "1.8"),
073
074    /**
075     * Java 1.9.
076     *
077     * @deprecated As of release 3.5, replaced by {@link #JAVA_9}.
078     */
079    @Deprecated
080    JAVA_1_9(9.0f, "9"),
081
082    /**
083     * Java 9.
084     *
085     * @since 3.5
086     */
087    JAVA_9(9.0f, "9"),
088
089    /**
090     * Java 10.
091     *
092     * @since 3.7
093     */
094    JAVA_10(10.0f, "10"),
095
096    /**
097     * Java 11.
098     *
099     * @since 3.8
100     */
101    JAVA_11(11.0f, "11"),
102
103    /**
104     * Java 12.
105     *
106     * @since 3.9
107     */
108    JAVA_12(12.0f, "12"),
109
110    /**
111     * Java 13.
112     *
113     * @since 3.9
114     */
115    JAVA_13(13.0f, "13"),
116
117    /**
118     * Java 14.
119     *
120     * @since 3.11
121     */
122    JAVA_14(14.0f, "14"),
123
124    /**
125     * Java 15.
126     *
127     * @since 3.11
128     */
129    JAVA_15(15.0f, "15"),
130
131    /**
132     * Java 16.
133     *
134     * @since 3.11
135     */
136    JAVA_16(16.0f, "16"),
137
138    /**
139     * Java 17.
140     *
141     * @since 3.12.0
142     */
143    JAVA_17(17.0f, "17"),
144
145    /**
146     * Java 18.
147     *
148     * @since 3.13.0
149     */
150    JAVA_18(18.0f, "18"),
151
152    /**
153     * Java 19.
154     *
155     * @since 3.13.0
156     */
157    JAVA_19(19.0f, "19"),
158
159    /**
160     * Java 20.
161     *
162     * @since 3.13.0
163     */
164    JAVA_20(20, "20"),
165
166    /**
167     * Java 21.
168     *
169     * @since 3.13.0
170     */
171    JAVA_21(21, "21"),
172
173    /**
174     * Java 22.
175     *
176     * @since 3.15.0
177     */
178    JAVA_22(22, "22"),
179
180    /**
181     * Java 23.
182     *
183     * @since 3.18.0
184     */
185    JAVA_23(23, "23"),
186
187    /**
188     * Java 24.
189     *
190     * @since 3.18.0
191     */
192    JAVA_24(24, "24"),
193
194    /**
195     * Java 25.
196     *
197     * @since 3.20.0
198     */
199    JAVA_25(25, "25"),
200
201    /**
202     * Java 26.
203     *
204     * @since 3.20.0
205     */
206    JAVA_26(26, "26"),
207
208    /**
209     * Java 27.
210     *
211     * @since 3.21.0
212     */
213    JAVA_27(27, "27"),
214
215    /**
216     * Java 28.
217     *
218     * @since 3.21.0
219     */
220    JAVA_28(28, "28"),
221
222    /**
223     * The most recent Java version. Mainly introduced to avoid to break when a new version of Java is used.
224     */
225    JAVA_RECENT(maxVersion(), Float.toString(maxVersion()));
226
227    /**
228     * Gets the enumeration constant corresponding to the given Java version string. This method is used internally.
229     *
230     * @param versionStr The Java version as string.
231     * @return The corresponding enumeration constant or {@code null} if the version is unknown.
232     */
233    static JavaVersion get(final String versionStr) {
234        if (versionStr == null) {
235            return null;
236        }
237        switch (versionStr) {
238        case "0.9":
239            return JAVA_0_9;
240        case "1.1":
241            return JAVA_1_1;
242        case "1.2":
243            return JAVA_1_2;
244        case "1.3":
245            return JAVA_1_3;
246        case "1.4":
247            return JAVA_1_4;
248        case "1.5":
249            return JAVA_1_5;
250        case "1.6":
251            return JAVA_1_6;
252        case "1.7":
253            return JAVA_1_7;
254        case "1.8":
255            return JAVA_1_8;
256        case "9":
257            return JAVA_9;
258        case "10":
259            return JAVA_10;
260        case "11":
261            return JAVA_11;
262        case "12":
263            return JAVA_12;
264        case "13":
265            return JAVA_13;
266        case "14":
267            return JAVA_14;
268        case "15":
269            return JAVA_15;
270        case "16":
271            return JAVA_16;
272        case "17":
273            return JAVA_17;
274        case "18":
275            return JAVA_18;
276        case "19":
277            return JAVA_19;
278        case "20":
279            return JAVA_20;
280        case "21":
281            return JAVA_21;
282        case "22":
283            return JAVA_22;
284        case "23":
285            return JAVA_23;
286        case "24":
287            return JAVA_24;
288        case "25":
289            return JAVA_25;
290        case "26":
291            return JAVA_26;
292        case "27":
293            return JAVA_27;
294        case "28":
295            return JAVA_28;
296        default:
297            final float v = toFloatVersion(versionStr);
298            if (v - 1. < 1.) { // then we need to check decimals > .9
299                final int firstComma = Math.max(versionStr.indexOf('.'), versionStr.indexOf(','));
300                // read up to the next separator if present, otherwise to the end of the string
301                // (this was previously an inverted Math.max that always selected the full string)
302                int end = versionStr.indexOf(',', firstComma + 1);
303                if (end == -1) {
304                    end = versionStr.length();
305                }
306                try {
307                    if (Float.parseFloat(versionStr.substring(firstComma + 1, end)) > .9f) {
308                        return JAVA_RECENT;
309                    }
310                } catch (final NumberFormatException e) {
311                    // malformed version string ("1.", "bogus"): the documented contract is to return null
312                    // for unknown versions rather than propagate an exception, which would otherwise poison
313                    // SystemUtils' static initializer for the class loader lifetime.
314                    return null;
315                }
316            } else if (v > 10) {
317                return JAVA_RECENT;
318            }
319            return null;
320        }
321    }
322
323    /**
324     * Gets the enumeration constant corresponding to the given Java version string. This method is used internally.
325     *
326     * @param versionStr The Java version as string.
327     * @return The corresponding enumeration constant or {@code null} if the version is unknown.
328     */
329    static JavaVersion getJavaVersion(final String versionStr) {
330        return get(versionStr);
331    }
332
333    /**
334     * Gets the Java Version from the system or 99.0 if the {@code java.specification.version} system property is not set.
335     *
336     * @return The value of {@code java.specification.version} system property or 99.0 if it is not set.
337     */
338    private static float maxVersion() {
339        final float v = toFloatVersion(SystemProperties.getJavaSpecificationVersion("99.0"));
340        return v > 0 ? v : 99f;
341    }
342
343    static String[] split(final String value) {
344        return RegExUtils.VERSION_SPLIT_PATTERN.split(value);
345    }
346
347    /**
348     * Parses a float value from a String.
349     *
350     * @param value The String to parse.
351     * @return The float value represented by the string or -1 if the given String cannot be parsed.
352     */
353    private static float toFloatVersion(final String value) {
354        final int defaultReturnValue = -1;
355        if (!value.contains(".")) {
356            return NumberUtils.toFloat(value, defaultReturnValue);
357        }
358        final String[] toParse = split(value);
359        if (toParse.length >= 2) {
360            return NumberUtils.toFloat(toParse[0] + '.' + toParse[1], defaultReturnValue);
361        }
362        return defaultReturnValue;
363    }
364
365    /**
366     * The float value.
367     */
368    private final float value;
369
370    /**
371     * The standard name.
372     */
373    private final String name;
374
375    /**
376     * Constructs a new instance.
377     *
378     * @param value The float value.
379     * @param name  The standard name, not null.
380     */
381    JavaVersion(final float value, final String name) {
382        this.value = value;
383        this.name = name;
384    }
385
386    /**
387     * Tests whether this version of Java is at least the version of Java passed in.
388     *
389     * <p>
390     * For example:
391     * </p>
392     *
393     * <pre>
394     *  {@code
395     * myVersion.atLeast(JavaVersion.JAVA_1_8)
396     * }</pre>
397     *
398     * @param requiredVersion The version to check against, not null.
399     * @return true if this version is equal to or greater than the specified version.
400     */
401    public boolean atLeast(final JavaVersion requiredVersion) {
402        return this.value >= requiredVersion.value;
403    }
404
405    /**
406     * Tests whether this version of Java is at most the version of Java passed in.
407     *
408     * <p>
409     * For example:
410     * </p>
411     *
412     * <pre>
413     *  {@code
414     * myVersion.atMost(JavaVersion.JAVA_1_4)
415     * }</pre>
416     *
417     * @param requiredVersion The version to check against, not null.
418     * @return true if this version is equal to or less than the specified version.
419     * @since 3.9
420     */
421    public boolean atMost(final JavaVersion requiredVersion) {
422        return this.value <= requiredVersion.value;
423    }
424
425    /**
426     * The string value is overridden to return the standard name.
427     *
428     * <p>
429     * For example, {@code "1.5"}.
430     * </p>
431     *
432     * @return The name, not null.
433     */
434    @Override
435    public String toString() {
436        return name;
437    }
438}