Home | History | Annotate | Download | only in format
      1 /*
      2  * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 /*
     27  * This file is available under and governed by the GNU General Public
     28  * License version 2 only, as published by the Free Software Foundation.
     29  * However, the following notice accompanied the original version of this
     30  * file:
     31  *
     32  * Copyright (c) 2008-2012, Stephen Colebourne & Michael Nascimento Santos
     33  *
     34  * All rights reserved.
     35  *
     36  * Redistribution and use in source and binary forms, with or without
     37  * modification, are permitted provided that the following conditions are met:
     38  *
     39  *  * Redistributions of source code must retain the above copyright notice,
     40  *    this list of conditions and the following disclaimer.
     41  *
     42  *  * Redistributions in binary form must reproduce the above copyright notice,
     43  *    this list of conditions and the following disclaimer in the documentation
     44  *    and/or other materials provided with the distribution.
     45  *
     46  *  * Neither the name of JSR-310 nor the names of its contributors
     47  *    may be used to endorse or promote products derived from this software
     48  *    without specific prior written permission.
     49  *
     50  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     51  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     52  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     53  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     54  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     55  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     56  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     57  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     58  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     59  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     60  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     61  */
     62 package java.time.format;
     63 
     64 import java.text.DecimalFormatSymbols;
     65 import java.util.Collections;
     66 import java.util.HashSet;
     67 import java.util.Locale;
     68 import java.util.Objects;
     69 import java.util.Set;
     70 import java.util.concurrent.ConcurrentHashMap;
     71 import java.util.concurrent.ConcurrentMap;
     72 
     73 /**
     74  * Localized decimal style used in date and time formatting.
     75  * <p>
     76  * A significant part of dealing with dates and times is the localization.
     77  * This class acts as a central point for accessing the information.
     78  *
     79  * @implSpec
     80  * This class is immutable and thread-safe.
     81  *
     82  * @since 1.8
     83  */
     84 public final class DecimalStyle {
     85 
     86     /**
     87      * The standard set of non-localized decimal style symbols.
     88      * <p>
     89      * This uses standard ASCII characters for zero, positive, negative and a dot for the decimal point.
     90      */
     91     public static final DecimalStyle STANDARD = new DecimalStyle('0', '+', '-', '.');
     92     /**
     93      * The cache of DecimalStyle instances.
     94      */
     95     private static final ConcurrentMap<Locale, DecimalStyle> CACHE = new ConcurrentHashMap<>(16, 0.75f, 2);
     96 
     97     /**
     98      * The zero digit.
     99      */
    100     private final char zeroDigit;
    101     /**
    102      * The positive sign.
    103      */
    104     private final char positiveSign;
    105     /**
    106      * The negative sign.
    107      */
    108     private final char negativeSign;
    109     /**
    110      * The decimal separator.
    111      */
    112     private final char decimalSeparator;
    113 
    114     //-----------------------------------------------------------------------
    115     /**
    116      * Lists all the locales that are supported.
    117      * <p>
    118      * The locale 'en_US' will always be present.
    119      *
    120      * @return a Set of Locales for which localization is supported
    121      */
    122     public static Set<Locale> getAvailableLocales() {
    123         Locale[] l = DecimalFormatSymbols.getAvailableLocales();
    124         Set<Locale> locales = new HashSet<>(l.length);
    125         Collections.addAll(locales, l);
    126         return locales;
    127     }
    128 
    129     /**
    130      * Obtains the DecimalStyle for the default
    131      * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
    132      * <p>
    133      * This method provides access to locale sensitive decimal style symbols.
    134      * <p>
    135      * This is equivalent to calling
    136      * {@link #of(Locale)
    137      *     of(Locale.getDefault(Locale.Category.FORMAT))}.
    138      *
    139      * @see java.util.Locale.Category#FORMAT
    140      * @return the decimal style, not null
    141      */
    142     public static DecimalStyle ofDefaultLocale() {
    143         return of(Locale.getDefault(Locale.Category.FORMAT));
    144     }
    145 
    146     /**
    147      * Obtains the DecimalStyle for the specified locale.
    148      * <p>
    149      * This method provides access to locale sensitive decimal style symbols.
    150      *
    151      * @param locale  the locale, not null
    152      * @return the decimal style, not null
    153      */
    154     public static DecimalStyle of(Locale locale) {
    155         Objects.requireNonNull(locale, "locale");
    156         DecimalStyle info = CACHE.get(locale);
    157         if (info == null) {
    158             info = create(locale);
    159             CACHE.putIfAbsent(locale, info);
    160             info = CACHE.get(locale);
    161         }
    162         return info;
    163     }
    164 
    165     private static DecimalStyle create(Locale locale) {
    166         DecimalFormatSymbols oldSymbols = DecimalFormatSymbols.getInstance(locale);
    167         char zeroDigit = oldSymbols.getZeroDigit();
    168         char positiveSign = '+';
    169         char negativeSign = oldSymbols.getMinusSign();
    170         char decimalSeparator = oldSymbols.getDecimalSeparator();
    171         if (zeroDigit == '0' && negativeSign == '-' && decimalSeparator == '.') {
    172             return STANDARD;
    173         }
    174         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
    175     }
    176 
    177     //-----------------------------------------------------------------------
    178     /**
    179      * Restricted constructor.
    180      *
    181      * @param zeroChar  the character to use for the digit of zero
    182      * @param positiveSignChar  the character to use for the positive sign
    183      * @param negativeSignChar  the character to use for the negative sign
    184      * @param decimalPointChar  the character to use for the decimal point
    185      */
    186     private DecimalStyle(char zeroChar, char positiveSignChar, char negativeSignChar, char decimalPointChar) {
    187         this.zeroDigit = zeroChar;
    188         this.positiveSign = positiveSignChar;
    189         this.negativeSign = negativeSignChar;
    190         this.decimalSeparator = decimalPointChar;
    191     }
    192 
    193     //-----------------------------------------------------------------------
    194     /**
    195      * Gets the character that represents zero.
    196      * <p>
    197      * The character used to represent digits may vary by culture.
    198      * This method specifies the zero character to use, which implies the characters for one to nine.
    199      *
    200      * @return the character for zero
    201      */
    202     public char getZeroDigit() {
    203         return zeroDigit;
    204     }
    205 
    206     /**
    207      * Returns a copy of the info with a new character that represents zero.
    208      * <p>
    209      * The character used to represent digits may vary by culture.
    210      * This method specifies the zero character to use, which implies the characters for one to nine.
    211      *
    212      * @param zeroDigit  the character for zero
    213      * @return  a copy with a new character that represents zero, not null
    214 
    215      */
    216     public DecimalStyle withZeroDigit(char zeroDigit) {
    217         if (zeroDigit == this.zeroDigit) {
    218             return this;
    219         }
    220         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
    221     }
    222 
    223     //-----------------------------------------------------------------------
    224     /**
    225      * Gets the character that represents the positive sign.
    226      * <p>
    227      * The character used to represent a positive number may vary by culture.
    228      * This method specifies the character to use.
    229      *
    230      * @return the character for the positive sign
    231      */
    232     public char getPositiveSign() {
    233         return positiveSign;
    234     }
    235 
    236     /**
    237      * Returns a copy of the info with a new character that represents the positive sign.
    238      * <p>
    239      * The character used to represent a positive number may vary by culture.
    240      * This method specifies the character to use.
    241      *
    242      * @param positiveSign  the character for the positive sign
    243      * @return  a copy with a new character that represents the positive sign, not null
    244      */
    245     public DecimalStyle withPositiveSign(char positiveSign) {
    246         if (positiveSign == this.positiveSign) {
    247             return this;
    248         }
    249         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
    250     }
    251 
    252     //-----------------------------------------------------------------------
    253     /**
    254      * Gets the character that represents the negative sign.
    255      * <p>
    256      * The character used to represent a negative number may vary by culture.
    257      * This method specifies the character to use.
    258      *
    259      * @return the character for the negative sign
    260      */
    261     public char getNegativeSign() {
    262         return negativeSign;
    263     }
    264 
    265     /**
    266      * Returns a copy of the info with a new character that represents the negative sign.
    267      * <p>
    268      * The character used to represent a negative number may vary by culture.
    269      * This method specifies the character to use.
    270      *
    271      * @param negativeSign  the character for the negative sign
    272      * @return  a copy with a new character that represents the negative sign, not null
    273      */
    274     public DecimalStyle withNegativeSign(char negativeSign) {
    275         if (negativeSign == this.negativeSign) {
    276             return this;
    277         }
    278         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
    279     }
    280 
    281     //-----------------------------------------------------------------------
    282     /**
    283      * Gets the character that represents the decimal point.
    284      * <p>
    285      * The character used to represent a decimal point may vary by culture.
    286      * This method specifies the character to use.
    287      *
    288      * @return the character for the decimal point
    289      */
    290     public char getDecimalSeparator() {
    291         return decimalSeparator;
    292     }
    293 
    294     /**
    295      * Returns a copy of the info with a new character that represents the decimal point.
    296      * <p>
    297      * The character used to represent a decimal point may vary by culture.
    298      * This method specifies the character to use.
    299      *
    300      * @param decimalSeparator  the character for the decimal point
    301      * @return  a copy with a new character that represents the decimal point, not null
    302      */
    303     public DecimalStyle withDecimalSeparator(char decimalSeparator) {
    304         if (decimalSeparator == this.decimalSeparator) {
    305             return this;
    306         }
    307         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
    308     }
    309 
    310     //-----------------------------------------------------------------------
    311     /**
    312      * Checks whether the character is a digit, based on the currently set zero character.
    313      *
    314      * @param ch  the character to check
    315      * @return the value, 0 to 9, of the character, or -1 if not a digit
    316      */
    317     int convertToDigit(char ch) {
    318         int val = ch - zeroDigit;
    319         return (val >= 0 && val <= 9) ? val : -1;
    320     }
    321 
    322     /**
    323      * Converts the input numeric text to the internationalized form using the zero character.
    324      *
    325      * @param numericText  the text, consisting of digits 0 to 9, to convert, not null
    326      * @return the internationalized text, not null
    327      */
    328     String convertNumberToI18N(String numericText) {
    329         if (zeroDigit == '0') {
    330             return numericText;
    331         }
    332         int diff = zeroDigit - '0';
    333         char[] array = numericText.toCharArray();
    334         for (int i = 0; i < array.length; i++) {
    335             array[i] = (char) (array[i] + diff);
    336         }
    337         return new String(array);
    338     }
    339 
    340     //-----------------------------------------------------------------------
    341     /**
    342      * Checks if this DecimalStyle is equal to another DecimalStyle.
    343      *
    344      * @param obj  the object to check, null returns false
    345      * @return true if this is equal to the other date
    346      */
    347     @Override
    348     public boolean equals(Object obj) {
    349         if (this == obj) {
    350             return true;
    351         }
    352         if (obj instanceof DecimalStyle) {
    353             DecimalStyle other = (DecimalStyle) obj;
    354             return (zeroDigit == other.zeroDigit && positiveSign == other.positiveSign &&
    355                     negativeSign == other.negativeSign && decimalSeparator == other.decimalSeparator);
    356         }
    357         return false;
    358     }
    359 
    360     /**
    361      * A hash code for this DecimalStyle.
    362      *
    363      * @return a suitable hash code
    364      */
    365     @Override
    366     public int hashCode() {
    367         return zeroDigit + positiveSign + negativeSign + decimalSeparator;
    368     }
    369 
    370     //-----------------------------------------------------------------------
    371     /**
    372      * Returns a string describing this DecimalStyle.
    373      *
    374      * @return a string description, not null
    375      */
    376     @Override
    377     public String toString() {
    378         return "DecimalStyle[" + zeroDigit + positiveSign + negativeSign + decimalSeparator + "]";
    379     }
    380 
    381 }
    382