1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/*
4 *******************************************************************************
5 * Copyright (C) 1996-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
8 */
9
10package com.ibm.icu.util;
11
12import java.util.concurrent.ConcurrentHashMap;
13
14/**
15 * Class to store version numbers of the form major.minor.milli.micro.
16 * @author synwee
17 * @stable ICU 2.6
18 */
19public final class VersionInfo implements Comparable<VersionInfo>
20{
21    // public data members -------------------------------------------------
22
23    /**
24     * Unicode 1.0 version
25     * @stable ICU 2.6
26     */
27    public static final VersionInfo UNICODE_1_0;
28    /**
29     * Unicode 1.0.1 version
30     * @stable ICU 2.6
31     */
32    public static final VersionInfo UNICODE_1_0_1;
33    /**
34     * Unicode 1.1.0 version
35     * @stable ICU 2.6
36     */
37    public static final VersionInfo UNICODE_1_1_0;
38    /**
39     * Unicode 1.1.5 version
40     * @stable ICU 2.6
41     */
42    public static final VersionInfo UNICODE_1_1_5;
43    /**
44     * Unicode 2.0 version
45     * @stable ICU 2.6
46     */
47    public static final VersionInfo UNICODE_2_0;
48    /**
49     * Unicode 2.1.2 version
50     * @stable ICU 2.6
51     */
52    public static final VersionInfo UNICODE_2_1_2;
53    /**
54     * Unicode 2.1.5 version
55     * @stable ICU 2.6
56     */
57    public static final VersionInfo UNICODE_2_1_5;
58    /**
59     * Unicode 2.1.8 version
60     * @stable ICU 2.6
61     */
62    public static final VersionInfo UNICODE_2_1_8;
63    /**
64     * Unicode 2.1.9 version
65     * @stable ICU 2.6
66     */
67    public static final VersionInfo UNICODE_2_1_9;
68    /**
69     * Unicode 3.0 version
70     * @stable ICU 2.6
71     */
72    public static final VersionInfo UNICODE_3_0;
73    /**
74     * Unicode 3.0.1 version
75     * @stable ICU 2.6
76     */
77    public static final VersionInfo UNICODE_3_0_1;
78    /**
79     * Unicode 3.1.0 version
80     * @stable ICU 2.6
81     */
82    public static final VersionInfo UNICODE_3_1_0;
83    /**
84     * Unicode 3.1.1 version
85     * @stable ICU 2.6
86     */
87    public static final VersionInfo UNICODE_3_1_1;
88    /**
89     * Unicode 3.2 version
90     * @stable ICU 2.6
91     */
92    public static final VersionInfo UNICODE_3_2;
93
94    /**
95     * Unicode 4.0 version
96     * @stable ICU 2.6
97     */
98    public static final VersionInfo UNICODE_4_0;
99
100    /**
101     * Unicode 4.0.1 version
102     * @stable ICU 3.4
103     */
104    public static final VersionInfo UNICODE_4_0_1;
105
106    /**
107     * Unicode 4.1 version
108     * @stable ICU 3.4
109     */
110    public static final VersionInfo UNICODE_4_1;
111
112    /**
113     * Unicode 5.0 version
114     * @stable ICU 3.4
115     */
116    public static final VersionInfo UNICODE_5_0;
117
118    /**
119     * Unicode 5.1 version
120     * @stable ICU 4.2
121     */
122    public static final VersionInfo UNICODE_5_1;
123
124    /**
125     * Unicode 5.2 version
126     * @stable ICU 4.4
127     */
128    public static final VersionInfo UNICODE_5_2;
129
130    /**
131     * Unicode 6.0 version
132     * @stable ICU 4.6
133     */
134    public static final VersionInfo UNICODE_6_0;
135
136    /**
137     * Unicode 6.1 version
138     * @stable ICU 49
139     */
140    public static final VersionInfo UNICODE_6_1;
141
142    /**
143     * Unicode 6.2 version
144     * @stable ICU 50
145     */
146    public static final VersionInfo UNICODE_6_2;
147
148    /**
149     * Unicode 6.3 version
150     * @stable ICU 52
151     */
152    public static final VersionInfo UNICODE_6_3;
153
154    /**
155     * Unicode 7.0 version
156     * @stable ICU 54
157     */
158    public static final VersionInfo UNICODE_7_0;
159
160    /**
161     * Unicode 8.0 version
162     * @stable ICU 56
163     */
164    public static final VersionInfo UNICODE_8_0;
165
166    /**
167     * Unicode 9.0 version
168     * @stable ICU 58
169     */
170    public static final VersionInfo UNICODE_9_0;
171
172    /**
173     * Unicode 10.0 version
174     * @stable ICU 60
175     */
176    public static final VersionInfo UNICODE_10_0;
177
178    /**
179     * ICU4J current release version
180     * @stable ICU 2.8
181     */
182    public static final VersionInfo ICU_VERSION;
183
184    /**
185     * Data version string for ICU's internal data.
186     * Used for appending to data path (e.g. icudt43b)
187     * @internal
188     * @deprecated This API is ICU internal only.
189     */
190    @Deprecated
191    public static final String ICU_DATA_VERSION_PATH = "60b";
192
193    /**
194     * Data version in ICU4J.
195     * @internal
196     * @deprecated This API is ICU internal only.
197     */
198    @Deprecated
199    public static final VersionInfo ICU_DATA_VERSION;
200
201    /**
202     * Collation runtime version (sort key generator, string comparisons).
203     * If the version is different, sort keys for the same string could be different.
204     * This value may change in subsequent releases of ICU.
205     * @stable ICU 2.8
206     */
207    public static final VersionInfo UCOL_RUNTIME_VERSION;
208
209    /**
210     * Collation builder code version.
211     * When this is different, the same tailoring might result
212     * in assigning different collation elements to code points.
213     * This value may change in subsequent releases of ICU.
214     * @stable ICU 2.8
215     */
216    public static final VersionInfo UCOL_BUILDER_VERSION;
217
218    /**
219     * Constant version 1.
220     * This was intended to be the version of collation tailorings,
221     * but instead the tailoring data carries a version number.
222     * @deprecated ICU 54
223     */
224    @Deprecated
225    public static final VersionInfo UCOL_TAILORINGS_VERSION;
226
227
228    // public methods ------------------------------------------------------
229
230    /**
231     * Returns an instance of VersionInfo with the argument version.
232     * @param version version String in the format of "major.minor.milli.micro"
233     *                or "major.minor.milli" or "major.minor" or "major",
234     *                where major, minor, milli, micro are non-negative numbers
235     *                &lt;= 255. If the trailing version numbers are
236     *                not specified they are taken as 0s. E.g. Version "3.1" is
237     *                equivalent to "3.1.0.0".
238     * @return an instance of VersionInfo with the argument version.
239     * @exception IllegalArgumentException when the argument version
240     *                is not in the right format
241     * @stable ICU 2.6
242     */
243    public static VersionInfo getInstance(String version)
244    {
245        int length  = version.length();
246        int array[] = {0, 0, 0, 0};
247        int count   = 0;
248        int index   = 0;
249
250        while (count < 4 && index < length) {
251            char c = version.charAt(index);
252            if (c == '.') {
253                count ++;
254            }
255            else {
256                c -= '0';
257                if (c < 0 || c > 9) {
258                    throw new IllegalArgumentException(INVALID_VERSION_NUMBER_);
259                }
260                array[count] *= 10;
261                array[count] += c;
262            }
263            index ++;
264        }
265        if (index != length) {
266            throw new IllegalArgumentException(
267                                               "Invalid version number: String '" + version + "' exceeds version format");
268        }
269        for (int i = 0; i < 4; i ++) {
270            if (array[i] < 0 || array[i] > 255) {
271                throw new IllegalArgumentException(INVALID_VERSION_NUMBER_);
272            }
273        }
274
275        return getInstance(array[0], array[1], array[2], array[3]);
276    }
277
278    /**
279     * Returns an instance of VersionInfo with the argument version.
280     * @param major major version, non-negative number &lt;= 255.
281     * @param minor minor version, non-negative number &lt;= 255.
282     * @param milli milli version, non-negative number &lt;= 255.
283     * @param micro micro version, non-negative number &lt;= 255.
284     * @exception IllegalArgumentException when either arguments are negative or &gt; 255
285     * @stable ICU 2.6
286     */
287    public static VersionInfo getInstance(int major, int minor, int milli,
288                                          int micro)
289    {
290        // checks if it is in the hashmap
291        // else
292        if (major < 0 || major > 255 || minor < 0 || minor > 255 ||
293            milli < 0 || milli > 255 || micro < 0 || micro > 255) {
294            throw new IllegalArgumentException(INVALID_VERSION_NUMBER_);
295        }
296        int     version = getInt(major, minor, milli, micro);
297        Integer key     = Integer.valueOf(version);
298        VersionInfo  result  = MAP_.get(key);
299        if (result == null) {
300            result = new VersionInfo(version);
301            VersionInfo tmpvi = MAP_.putIfAbsent(key, result);
302            if (tmpvi != null) {
303                result = tmpvi;
304            }
305        }
306        return result;
307    }
308
309    /**
310     * Returns an instance of VersionInfo with the argument version.
311     * Equivalent to getInstance(major, minor, milli, 0).
312     * @param major major version, non-negative number &lt;= 255.
313     * @param minor minor version, non-negative number &lt;= 255.
314     * @param milli milli version, non-negative number &lt;= 255.
315     * @exception IllegalArgumentException when either arguments are
316     *                                     negative or &gt; 255
317     * @stable ICU 2.6
318     */
319    public static VersionInfo getInstance(int major, int minor, int milli)
320    {
321        return getInstance(major, minor, milli, 0);
322    }
323
324    /**
325     * Returns an instance of VersionInfo with the argument version.
326     * Equivalent to getInstance(major, minor, 0, 0).
327     * @param major major version, non-negative number &lt;= 255.
328     * @param minor minor version, non-negative number &lt;= 255.
329     * @exception IllegalArgumentException when either arguments are
330     *                                     negative or &gt; 255
331     * @stable ICU 2.6
332     */
333    public static VersionInfo getInstance(int major, int minor)
334    {
335        return getInstance(major, minor, 0, 0);
336    }
337
338    /**
339     * Returns an instance of VersionInfo with the argument version.
340     * Equivalent to getInstance(major, 0, 0, 0).
341     * @param major major version, non-negative number &lt;= 255.
342     * @exception IllegalArgumentException when either arguments are
343     *                                     negative or &gt; 255
344     * @stable ICU 2.6
345     */
346    public static VersionInfo getInstance(int major)
347    {
348        return getInstance(major, 0, 0, 0);
349    }
350
351    private static volatile VersionInfo javaVersion;
352
353    /**
354     * @internal
355     * @deprecated This API is ICU internal only.
356     */
357    @Deprecated
358    public static VersionInfo javaVersion() {
359        if (javaVersion == null) {
360            synchronized(VersionInfo.class) {
361                if (javaVersion == null) {
362                    String s = System.getProperty("java.version");
363                    // clean string
364                    // preserve only digits, separated by single '.'
365                    // ignore over 4 digit sequences
366                    // does not test < 255, very odd...
367
368                    char[] chars = s.toCharArray();
369                    int r = 0, w = 0, count = 0;
370                    boolean numeric = false; // ignore leading non-numerics
371                    while (r < chars.length) {
372                        char c = chars[r++];
373                        if (c < '0' || c > '9') {
374                            if (numeric) {
375                                if (count == 3) {
376                                    // only four digit strings allowed
377                                    break;
378                                }
379                                numeric = false;
380                                chars[w++] = '.';
381                                ++count;
382                            }
383                        } else {
384                            numeric = true;
385                            chars[w++] = c;
386                        }
387                    }
388                    while (w > 0 && chars[w-1] == '.') {
389                        --w;
390                    }
391
392                    String vs = new String(chars, 0, w);
393
394                    javaVersion = VersionInfo.getInstance(vs);
395                }
396            }
397        }
398        return javaVersion;
399    }
400
401    /**
402     * Returns the String representative of VersionInfo in the format of
403     * "major.minor.milli.micro"
404     * @return String representative of VersionInfo
405     * @stable ICU 2.6
406     */
407    @Override
408    public String toString()
409    {
410        StringBuilder result = new StringBuilder(7);
411        result.append(getMajor());
412        result.append('.');
413        result.append(getMinor());
414        result.append('.');
415        result.append(getMilli());
416        result.append('.');
417        result.append(getMicro());
418        return result.toString();
419    }
420
421    /**
422     * Returns the major version number
423     * @return the major version number
424     * @stable ICU 2.6
425     */
426    public int getMajor()
427    {
428        return (m_version_ >> 24) & LAST_BYTE_MASK_ ;
429    }
430
431    /**
432     * Returns the minor version number
433     * @return the minor version number
434     * @stable ICU 2.6
435     */
436    public int getMinor()
437    {
438        return (m_version_ >> 16) & LAST_BYTE_MASK_ ;
439    }
440
441    /**
442     * Returns the milli version number
443     * @return the milli version number
444     * @stable ICU 2.6
445     */
446    public int getMilli()
447    {
448        return (m_version_ >> 8) & LAST_BYTE_MASK_ ;
449    }
450
451    /**
452     * Returns the micro version number
453     * @return the micro version number
454     * @stable ICU 2.6
455     */
456    public int getMicro()
457    {
458        return m_version_ & LAST_BYTE_MASK_ ;
459    }
460
461    /**
462     * Checks if this version information is equals to the argument version
463     * @param other object to be compared
464     * @return true if other is equals to this object's version information,
465     *         false otherwise
466     * @stable ICU 2.6
467     */
468    @Override
469    public boolean equals(Object other)
470    {
471        return other == this;
472    }
473
474    /**
475     * Returns the hash code value for this set.
476     *
477     * @return the hash code value for this set.
478     * @see java.lang.Object#hashCode()
479     * @stable ICU 58
480     */
481    @Override
482    public int hashCode() {
483        return m_version_;
484    }
485
486    /**
487     * Compares other with this VersionInfo.
488     * @param other VersionInfo to be compared
489     * @return 0 if the argument is a VersionInfo object that has version
490     *           information equals to this object.
491     *           Less than 0 if the argument is a VersionInfo object that has
492     *           version information greater than this object.
493     *           Greater than 0 if the argument is a VersionInfo object that
494     *           has version information less than this object.
495     * @stable ICU 2.6
496     */
497    @Override
498    public int compareTo(VersionInfo other)
499    {
500        return m_version_ - other.m_version_;
501    }
502
503    // private data members ----------------------------------------------
504
505    /**
506     * Unicode data version used by the current release.
507     * Defined here privately for printing by the main() method in this class.
508     * Should be the same as {@link com.ibm.icu.lang.UCharacter#getUnicodeVersion()}
509     * which gets the version number from a data file.
510     * We do not want VersionInfo to have an import dependency on UCharacter.
511     */
512    private static final VersionInfo UNICODE_VERSION;
513
514    /**
515     * Version number stored as a byte for each of the major, minor, milli and
516     * micro numbers in the 32 bit int.
517     * Most significant for the major and the least significant contains the
518     * micro numbers.
519     */
520    private int m_version_;
521    /**
522     * Map of singletons
523     */
524    private static final ConcurrentHashMap<Integer, VersionInfo> MAP_ = new ConcurrentHashMap<Integer, VersionInfo>();
525    /**
526     * Last byte mask
527     */
528    private static final int LAST_BYTE_MASK_ = 0xFF;
529    /**
530     * Error statement string
531     */
532    private static final String INVALID_VERSION_NUMBER_ =
533        "Invalid version number: Version number may be negative or greater than 255";
534
535    // static declaration ------------------------------------------------
536
537    /**
538     * Initialize versions only after MAP_ has been created
539     */
540    static {
541        UNICODE_1_0   = getInstance(1, 0, 0, 0);
542        UNICODE_1_0_1 = getInstance(1, 0, 1, 0);
543        UNICODE_1_1_0 = getInstance(1, 1, 0, 0);
544        UNICODE_1_1_5 = getInstance(1, 1, 5, 0);
545        UNICODE_2_0   = getInstance(2, 0, 0, 0);
546        UNICODE_2_1_2 = getInstance(2, 1, 2, 0);
547        UNICODE_2_1_5 = getInstance(2, 1, 5, 0);
548        UNICODE_2_1_8 = getInstance(2, 1, 8, 0);
549        UNICODE_2_1_9 = getInstance(2, 1, 9, 0);
550        UNICODE_3_0   = getInstance(3, 0, 0, 0);
551        UNICODE_3_0_1 = getInstance(3, 0, 1, 0);
552        UNICODE_3_1_0 = getInstance(3, 1, 0, 0);
553        UNICODE_3_1_1 = getInstance(3, 1, 1, 0);
554        UNICODE_3_2   = getInstance(3, 2, 0, 0);
555        UNICODE_4_0   = getInstance(4, 0, 0, 0);
556        UNICODE_4_0_1 = getInstance(4, 0, 1, 0);
557        UNICODE_4_1   = getInstance(4, 1, 0, 0);
558        UNICODE_5_0   = getInstance(5, 0, 0, 0);
559        UNICODE_5_1   = getInstance(5, 1, 0, 0);
560        UNICODE_5_2   = getInstance(5, 2, 0, 0);
561        UNICODE_6_0   = getInstance(6, 0, 0, 0);
562        UNICODE_6_1   = getInstance(6, 1, 0, 0);
563        UNICODE_6_2   = getInstance(6, 2, 0, 0);
564        UNICODE_6_3   = getInstance(6, 3, 0, 0);
565        UNICODE_7_0   = getInstance(7, 0, 0, 0);
566        UNICODE_8_0   = getInstance(8, 0, 0, 0);
567        UNICODE_9_0   = getInstance(9, 0, 0, 0);
568        UNICODE_10_0   = getInstance(10, 0, 0, 0);
569
570        ICU_VERSION   = getInstance(60, 2, 0, 0);
571        ICU_DATA_VERSION = getInstance(60, 2, 0, 0);
572        UNICODE_VERSION = UNICODE_10_0;
573
574        UCOL_RUNTIME_VERSION = getInstance(9);
575        UCOL_BUILDER_VERSION = getInstance(9);
576        UCOL_TAILORINGS_VERSION = getInstance(1);
577    }
578
579    // private constructor -----------------------------------------------
580
581    /**
582     * Constructor with int
583     * @param compactversion a 32 bit int with each byte representing a number
584     */
585    private VersionInfo(int compactversion)
586    {
587        m_version_ = compactversion;
588    }
589
590    /**
591     * Gets the int from the version numbers
592     * @param major non-negative version number
593     * @param minor non-negative version number
594     * @param milli non-negative version number
595     * @param micro non-negative version number
596     */
597    private static int getInt(int major, int minor, int milli, int micro)
598    {
599        return (major << 24) | (minor << 16) | (milli << 8) | micro;
600    }
601    ///CLOVER:OFF
602    /**
603     * Main method prints out ICU version information
604     * @param args arguments (currently not used)
605     * @stable ICU 4.6
606     */
607    public static void main(String[] args) {
608        String icuApiVer;
609
610        if (ICU_VERSION.getMajor() <= 4) {
611            if (ICU_VERSION.getMinor() % 2 != 0) {
612                // Development mile stone
613                int major = ICU_VERSION.getMajor();
614                int minor = ICU_VERSION.getMinor() + 1;
615                if (minor >= 10) {
616                    minor -= 10;
617                    major++;
618                }
619                icuApiVer = "" + major + "." + minor + "M" + ICU_VERSION.getMilli();
620            } else {
621                icuApiVer = ICU_VERSION.getVersionString(2, 2);
622            }
623        } else {
624            if (ICU_VERSION.getMinor() == 0) {
625                // Development mile stone
626                icuApiVer = "" + ICU_VERSION.getMajor() + "M" + ICU_VERSION.getMilli();
627            } else {
628                icuApiVer = ICU_VERSION.getVersionString(2, 2);
629            }
630        }
631
632
633        System.out.println("International Components for Unicode for Java " + icuApiVer);
634
635        System.out.println("");
636        System.out.println("Implementation Version: " + ICU_VERSION.getVersionString(2, 4));
637        System.out.println("Unicode Data Version:   " + UNICODE_VERSION.getVersionString(2, 4));
638        System.out.println("CLDR Data Version:      " + LocaleData.getCLDRVersion().getVersionString(2, 4));
639        System.out.println("Time Zone Data Version: " + getTZDataVersion());
640    }
641
642    /**
643     * Generate version string separated by dots with
644     * the specified digit width.  Version digit 0
645     * after <code>minDigits</code> will be trimmed off.
646     * @param minDigits Minimum number of version digits
647     * @param maxDigits Maximum number of version digits
648     * @return A tailored version string
649     * @internal
650     * @deprecated This API is ICU internal only. (For use in CLDR, etc.)
651     */
652    @Deprecated
653    public String getVersionString(int minDigits, int maxDigits) {
654        if (minDigits < 1 || maxDigits < 1
655                || minDigits > 4 || maxDigits > 4 || minDigits > maxDigits) {
656            throw new IllegalArgumentException("Invalid min/maxDigits range");
657        }
658
659        int[] digits = new int[4];
660        digits[0] = getMajor();
661        digits[1] = getMinor();
662        digits[2] = getMilli();
663        digits[3] = getMicro();
664
665        int numDigits = maxDigits;
666        while (numDigits > minDigits) {
667            if (digits[numDigits - 1] != 0) {
668                break;
669            }
670            numDigits--;
671        }
672
673        StringBuilder verStr = new StringBuilder(7);
674        verStr.append(digits[0]);
675        for (int i = 1; i < numDigits; i++) {
676            verStr.append(".");
677            verStr.append(digits[i]);
678        }
679
680        return verStr.toString();
681    }
682    ///CLOVER:ON
683
684
685    // Moved from TimeZone class
686    private static volatile String TZDATA_VERSION = null;
687
688    static String getTZDataVersion() {
689        if (TZDATA_VERSION == null) {
690            synchronized (VersionInfo.class) {
691                if (TZDATA_VERSION == null) {
692                    UResourceBundle tzbundle = UResourceBundle.getBundleInstance("com/ibm/icu/impl/data/icudt"
693                            + VersionInfo.ICU_DATA_VERSION_PATH, "zoneinfo64");
694                    TZDATA_VERSION = tzbundle.getString("TZVersion");
695                }
696            }
697        }
698        return TZDATA_VERSION;
699    }
700}
701