001/*
002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos
003 *
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions are met:
008 *
009 *  * Redistributions of source code must retain the above copyright notice,
010 *    this list of conditions and the following disclaimer.
011 *
012 *  * Redistributions in binary form must reproduce the above copyright notice,
013 *    this list of conditions and the following disclaimer in the documentation
014 *    and/or other materials provided with the distribution.
015 *
016 *  * Neither the name of JSR-310 nor the names of its contributors
017 *    may be used to endorse or promote products derived from this software
018 *    without specific prior written permission.
019 *
020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031 */
032package org.threeten.bp.format;
033
034import java.text.DecimalFormatSymbols;
035import java.util.Locale;
036import java.util.Objects;
037import java.util.concurrent.ConcurrentHashMap;
038import java.util.concurrent.ConcurrentMap;
039
040/**
041 * Localized symbols used in date and time formatting.
042 * <p>
043 * A significant part of dealing with dates and times is the localization.
044 * This class acts as a central point for accessing the information.
045 *
046 * <h3>Specification for implementors</h3>
047 * This class is immutable and thread-safe.
048 */
049public final class DateTimeFormatSymbols {
050
051    /**
052     * The standard set of non-localized symbols.
053     * <p>
054     * This uses standard ASCII characters for zero, positive, negative and a dot for the decimal point.
055     */
056    public static final DateTimeFormatSymbols STANDARD = new DateTimeFormatSymbols('0', '+', '-', '.');
057    /**
058     * The cache of symbols instances.
059     */
060    private static final ConcurrentMap<Locale, DateTimeFormatSymbols> CACHE = new ConcurrentHashMap<>(16, 0.75f, 2);
061
062    /**
063     * The zero digit.
064     */
065    private final char zeroDigit;
066    /**
067     * The positive sign.
068     */
069    private final char positiveSign;
070    /**
071     * The negative sign.
072     */
073    private final char negativeSign;
074    /**
075     * The decimal separator.
076     */
077    private final char decimalSeparator;
078
079    //-----------------------------------------------------------------------
080    /**
081     * Lists all the locales that are supported.
082     * <p>
083     * The locale 'en_US' will always be present.
084     *
085     * @return an array of locales for which localization is supported
086     */
087    public static Locale[] getAvailableLocales() {
088        return DecimalFormatSymbols.getAvailableLocales();
089    }
090
091    /**
092     * Obtains symbols for the default locale.
093     * <p>
094     * This method provides access to locale sensitive symbols.
095     *
096     * @return the info, not null
097     */
098    public static DateTimeFormatSymbols ofDefaultLocale() {
099        return of(Locale.getDefault());
100    }
101
102    /**
103     * Obtains symbols for the specified locale.
104     * <p>
105     * This method provides access to locale sensitive symbols.
106     *
107     * @param locale  the locale, not null
108     * @return the info, not null
109     */
110    public static DateTimeFormatSymbols of(Locale locale) {
111        Objects.requireNonNull(locale, "locale");
112        DateTimeFormatSymbols info = CACHE.get(locale);
113        if (info == null) {
114            info = create(locale);
115            CACHE.putIfAbsent(locale, info);
116            info = CACHE.get(locale);
117        }
118        return info;
119    }
120
121    private static DateTimeFormatSymbols create(Locale locale) {
122        DecimalFormatSymbols oldSymbols = DecimalFormatSymbols.getInstance(locale);
123        char zeroDigit = oldSymbols.getZeroDigit();
124        char positiveSign = '+';
125        char negativeSign = oldSymbols.getMinusSign();
126        char decimalSeparator = oldSymbols.getDecimalSeparator();
127        if (zeroDigit == '0' && negativeSign == '-' && decimalSeparator == '.') {
128            return STANDARD;
129        }
130        return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator);
131    }
132
133    //-----------------------------------------------------------------------
134    /**
135     * Restricted constructor.
136     *
137     * @param zeroChar  the character to use for the digit of zero
138     * @param positiveSignChar  the character to use for the positive sign
139     * @param negativeSignChar  the character to use for the negative sign
140     * @param decimalPointChar  the character to use for the decimal point
141     */
142    private DateTimeFormatSymbols(char zeroChar, char positiveSignChar, char negativeSignChar, char decimalPointChar) {
143        this.zeroDigit = zeroChar;
144        this.positiveSign = positiveSignChar;
145        this.negativeSign = negativeSignChar;
146        this.decimalSeparator = decimalPointChar;
147    }
148
149    //-----------------------------------------------------------------------
150    /**
151     * Gets the character that represents zero.
152     * <p>
153     * The character used to represent digits may vary by culture.
154     * This method specifies the zero character to use, which implies the characters for one to nine.
155     *
156     * @return the character for zero
157     */
158    public char getZeroDigit() {
159        return zeroDigit;
160    }
161
162    /**
163     * Returns a copy of the info with a new character that represents zero.
164     * <p>
165     * The character used to represent digits may vary by culture.
166     * This method specifies the zero character to use, which implies the characters for one to nine.
167     *
168     * @param zeroDigit  the character for zero
169     * @return  a copy with a new character that represents zero, not null
170
171     */
172    public DateTimeFormatSymbols withZeroDigit(char zeroDigit) {
173        if (zeroDigit == this.zeroDigit) {
174            return this;
175        }
176        return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator);
177    }
178
179    //-----------------------------------------------------------------------
180    /**
181     * Gets the character that represents the positive sign.
182     * <p>
183     * The character used to represent a positive number may vary by culture.
184     * This method specifies the character to use.
185     *
186     * @return the character for the positive sign
187     */
188    public char getPositiveSign() {
189        return positiveSign;
190    }
191
192    /**
193     * Returns a copy of the info with a new character that represents the positive sign.
194     * <p>
195     * The character used to represent a positive number may vary by culture.
196     * This method specifies the character to use.
197     *
198     * @param positiveSign  the character for the positive sign
199     * @return  a copy with a new character that represents the positive sign, not null
200     */
201    public DateTimeFormatSymbols withPositiveSign(char positiveSign) {
202        if (positiveSign == this.positiveSign) {
203            return this;
204        }
205        return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator);
206    }
207
208    //-----------------------------------------------------------------------
209    /**
210     * Gets the character that represents the negative sign.
211     * <p>
212     * The character used to represent a negative number may vary by culture.
213     * This method specifies the character to use.
214     *
215     * @return the character for the negative sign
216     */
217    public char getNegativeSign() {
218        return negativeSign;
219    }
220
221    /**
222     * Returns a copy of the info with a new character that represents the negative sign.
223     * <p>
224     * The character used to represent a negative number may vary by culture.
225     * This method specifies the character to use.
226     *
227     * @param negativeSign  the character for the negative sign
228     * @return  a copy with a new character that represents the negative sign, not null
229     */
230    public DateTimeFormatSymbols withNegativeSign(char negativeSign) {
231        if (negativeSign == this.negativeSign) {
232            return this;
233        }
234        return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator);
235    }
236
237    //-----------------------------------------------------------------------
238    /**
239     * Gets the character that represents the decimal point.
240     * <p>
241     * The character used to represent a decimal point may vary by culture.
242     * This method specifies the character to use.
243     *
244     * @return the character for the decimal point
245     */
246    public char getDecimalSeparator() {
247        return decimalSeparator;
248    }
249
250    /**
251     * Returns a copy of the info with a new character that represents the decimal point.
252     * <p>
253     * The character used to represent a decimal point may vary by culture.
254     * This method specifies the character to use.
255     *
256     * @param decimalSeparator  the character for the decimal point
257     * @return  a copy with a new character that represents the decimal point, not null
258     */
259    public DateTimeFormatSymbols withDecimalSeparator(char decimalSeparator) {
260        if (decimalSeparator == this.decimalSeparator) {
261            return this;
262        }
263        return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator);
264    }
265
266    //-----------------------------------------------------------------------
267    /**
268     * Checks whether the character is a digit, based on the currently set zero character.
269     *
270     * @param ch  the character to check
271     * @return the value, 0 to 9, of the character, or -1 if not a digit
272     */
273    int convertToDigit(char ch) {
274        int val = ch - zeroDigit;
275        return (val >= 0 && val <= 9) ? val : -1;
276    }
277
278    /**
279     * Converts the input numeric text to the internationalized form using the zero character.
280     *
281     * @param numericText  the text, consisting of digits 0 to 9, to convert, not null
282     * @return the internationalized text, not null
283     */
284    String convertNumberToI18N(String numericText) {
285        if (zeroDigit == '0') {
286            return numericText;
287        }
288        int diff = zeroDigit - '0';
289        char[] array = numericText.toCharArray();
290        for (int i = 0; i < array.length; i++) {
291            array[i] = (char) (array[i] + diff);
292        }
293        return new String(array);
294    }
295
296    //-----------------------------------------------------------------------
297    /**
298     * Checks if these symbols equal another set of symbols.
299     *
300     * @param obj  the object to check, null returns false
301     * @return true if this is equal to the other date
302     */
303    @Override
304    public boolean equals(Object obj) {
305        if (this == obj) {
306            return true;
307        }
308        if (obj instanceof DateTimeFormatSymbols) {
309            DateTimeFormatSymbols other = (DateTimeFormatSymbols) obj;
310            return (zeroDigit == other.zeroDigit && positiveSign == other.positiveSign &&
311                    negativeSign == other.negativeSign && decimalSeparator == other.decimalSeparator);
312        }
313        return false;
314    }
315
316    /**
317     * A hash code for these symbols.
318     *
319     * @return a suitable hash code
320     */
321    @Override
322    public int hashCode() {
323        return zeroDigit + positiveSign + negativeSign + decimalSeparator;
324    }
325
326    //-----------------------------------------------------------------------
327    /**
328     * Returns a string describing these symbols.
329     *
330     * @return a string description, not null
331     */
332    @Override
333    public String toString() {
334        return "Symbols[" + zeroDigit + positiveSign + negativeSign + decimalSeparator + "]";
335    }
336
337}