View Javadoc
1   /*
2    * Copyright (c) 2012, 2013, 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.Arrays;
66  import java.util.Collections;
67  import java.util.HashSet;
68  import java.util.Locale;
69  import java.util.Objects;
70  import java.util.Set;
71  import java.util.concurrent.ConcurrentHashMap;
72  import java.util.concurrent.ConcurrentMap;
73  
74  /**
75   * Localized decimal style used in date and time formatting.
76   * <p>
77   * A significant part of dealing with dates and times is the localization.
78   * This class acts as a central point for accessing the information.
79   *
80   * @implSpec
81   * This class is immutable and thread-safe.
82   *
83   * @since 1.8
84   */
85  public final class DecimalStyle {
86  
87      /**
88       * The standard set of non-localized decimal style symbols.
89       * <p>
90       * This uses standard ASCII characters for zero, positive, negative and a dot for the decimal point.
91       */
92      public static final DecimalStyle STANDARD = new DecimalStyle('0', '+', '-', '.');
93      /**
94       * The cache of DecimalStyle instances.
95       */
96      private static final ConcurrentMap<Locale, DecimalStyle> CACHE = new ConcurrentHashMap<>(16, 0.75f, 2);
97  
98      /**
99       * The zero digit.
100      */
101     private final char zeroDigit;
102     /**
103      * The positive sign.
104      */
105     private final char positiveSign;
106     /**
107      * The negative sign.
108      */
109     private final char negativeSign;
110     /**
111      * The decimal separator.
112      */
113     private final char decimalSeparator;
114 
115     //-----------------------------------------------------------------------
116     /**
117      * Lists all the locales that are supported.
118      * <p>
119      * The locale 'en_US' will always be present.
120      *
121      * @return a Set of Locales for which localization is supported
122      */
123     public static Set<Locale> getAvailableLocales() {
124         Locale[] l = DecimalFormatSymbols.getAvailableLocales();
125         Set<Locale> locales = new HashSet<>(l.length);
126         Collections.addAll(locales, l);
127         return locales;
128     }
129 
130     /**
131      * Obtains the DecimalStyle for the default
132      * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
133      * <p>
134      * This method provides access to locale sensitive decimal style symbols.
135      * <p>
136      * This is equivalent to calling
137      * {@link #of(Locale)
138      *     of(Locale.getDefault(Locale.Category.FORMAT))}.
139      *
140      * @see java.util.Locale.Category#FORMAT
141      * @return the info, not null
142      */
143     public static DecimalStyle ofDefaultLocale() {
144         return of(Locale.getDefault(Locale.Category.FORMAT));
145     }
146 
147     /**
148      * Obtains the DecimalStyle for the specified locale.
149      * <p>
150      * This method provides access to locale sensitive decimal style symbols.
151      *
152      * @param locale  the locale, not null
153      * @return the info, not null
154      */
155     public static DecimalStyle of(Locale locale) {
156         Objects.requireNonNull(locale, "locale");
157         DecimalStyle info = CACHE.get(locale);
158         if (info == null) {
159             info = create(locale);
160             CACHE.putIfAbsent(locale, info);
161             info = CACHE.get(locale);
162         }
163         return info;
164     }
165 
166     private static DecimalStyle create(Locale locale) {
167         DecimalFormatSymbols oldSymbols = DecimalFormatSymbols.getInstance(locale);
168         char zeroDigit = oldSymbols.getZeroDigit();
169         char positiveSign = '+';
170         char negativeSign = oldSymbols.getMinusSign();
171         char decimalSeparator = oldSymbols.getDecimalSeparator();
172         if (zeroDigit == '0' && negativeSign == '-' && decimalSeparator == '.') {
173             return STANDARD;
174         }
175         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
176     }
177 
178     //-----------------------------------------------------------------------
179     /**
180      * Restricted constructor.
181      *
182      * @param zeroChar  the character to use for the digit of zero
183      * @param positiveSignChar  the character to use for the positive sign
184      * @param negativeSignChar  the character to use for the negative sign
185      * @param decimalPointChar  the character to use for the decimal point
186      */
187     private DecimalStyle(char zeroChar, char positiveSignChar, char negativeSignChar, char decimalPointChar) {
188         this.zeroDigit = zeroChar;
189         this.positiveSign = positiveSignChar;
190         this.negativeSign = negativeSignChar;
191         this.decimalSeparator = decimalPointChar;
192     }
193 
194     //-----------------------------------------------------------------------
195     /**
196      * Gets the character that represents zero.
197      * <p>
198      * The character used to represent digits may vary by culture.
199      * This method specifies the zero character to use, which implies the characters for one to nine.
200      *
201      * @return the character for zero
202      */
203     public char getZeroDigit() {
204         return zeroDigit;
205     }
206 
207     /**
208      * Returns a copy of the info with a new character that represents zero.
209      * <p>
210      * The character used to represent digits may vary by culture.
211      * This method specifies the zero character to use, which implies the characters for one to nine.
212      *
213      * @param zeroDigit  the character for zero
214      * @return  a copy with a new character that represents zero, not null
215 
216      */
217     public DecimalStyle withZeroDigit(char zeroDigit) {
218         if (zeroDigit == this.zeroDigit) {
219             return this;
220         }
221         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
222     }
223 
224     //-----------------------------------------------------------------------
225     /**
226      * Gets the character that represents the positive sign.
227      * <p>
228      * The character used to represent a positive number may vary by culture.
229      * This method specifies the character to use.
230      *
231      * @return the character for the positive sign
232      */
233     public char getPositiveSign() {
234         return positiveSign;
235     }
236 
237     /**
238      * Returns a copy of the info with a new character that represents the positive sign.
239      * <p>
240      * The character used to represent a positive number may vary by culture.
241      * This method specifies the character to use.
242      *
243      * @param positiveSign  the character for the positive sign
244      * @return  a copy with a new character that represents the positive sign, not null
245      */
246     public DecimalStyle withPositiveSign(char positiveSign) {
247         if (positiveSign == this.positiveSign) {
248             return this;
249         }
250         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
251     }
252 
253     //-----------------------------------------------------------------------
254     /**
255      * Gets the character that represents the negative sign.
256      * <p>
257      * The character used to represent a negative number may vary by culture.
258      * This method specifies the character to use.
259      *
260      * @return the character for the negative sign
261      */
262     public char getNegativeSign() {
263         return negativeSign;
264     }
265 
266     /**
267      * Returns a copy of the info with a new character that represents the negative sign.
268      * <p>
269      * The character used to represent a negative number may vary by culture.
270      * This method specifies the character to use.
271      *
272      * @param negativeSign  the character for the negative sign
273      * @return  a copy with a new character that represents the negative sign, not null
274      */
275     public DecimalStyle withNegativeSign(char negativeSign) {
276         if (negativeSign == this.negativeSign) {
277             return this;
278         }
279         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
280     }
281 
282     //-----------------------------------------------------------------------
283     /**
284      * Gets the character that represents the decimal point.
285      * <p>
286      * The character used to represent a decimal point may vary by culture.
287      * This method specifies the character to use.
288      *
289      * @return the character for the decimal point
290      */
291     public char getDecimalSeparator() {
292         return decimalSeparator;
293     }
294 
295     /**
296      * Returns a copy of the info with a new character that represents the decimal point.
297      * <p>
298      * The character used to represent a decimal point may vary by culture.
299      * This method specifies the character to use.
300      *
301      * @param decimalSeparator  the character for the decimal point
302      * @return  a copy with a new character that represents the decimal point, not null
303      */
304     public DecimalStyle withDecimalSeparator(char decimalSeparator) {
305         if (decimalSeparator == this.decimalSeparator) {
306             return this;
307         }
308         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
309     }
310 
311     //-----------------------------------------------------------------------
312     /**
313      * Checks whether the character is a digit, based on the currently set zero character.
314      *
315      * @param ch  the character to check
316      * @return the value, 0 to 9, of the character, or -1 if not a digit
317      */
318     int convertToDigit(char ch) {
319         int val = ch - zeroDigit;
320         return (val >= 0 && val <= 9) ? val : -1;
321     }
322 
323     /**
324      * Converts the input numeric text to the internationalized form using the zero character.
325      *
326      * @param numericText  the text, consisting of digits 0 to 9, to convert, not null
327      * @return the internationalized text, not null
328      */
329     String convertNumberToI18N(String numericText) {
330         if (zeroDigit == '0') {
331             return numericText;
332         }
333         int diff = zeroDigit - '0';
334         char[] array = numericText.toCharArray();
335         for (int i = 0; i < array.length; i++) {
336             array[i] = (char) (array[i] + diff);
337         }
338         return new String(array);
339     }
340 
341     //-----------------------------------------------------------------------
342     /**
343      * Checks if this DecimalStyle is equal another DecimalStyle.
344      *
345      * @param obj  the object to check, null returns false
346      * @return true if this is equal to the other date
347      */
348     @Override
349     public boolean equals(Object obj) {
350         if (this == obj) {
351             return true;
352         }
353         if (obj instanceof DecimalStyle) {
354             DecimalStyle other = (DecimalStyle) obj;
355             return (zeroDigit == other.zeroDigit && positiveSign == other.positiveSign &&
356                     negativeSign == other.negativeSign && decimalSeparator == other.decimalSeparator);
357         }
358         return false;
359     }
360 
361     /**
362      * A hash code for this DecimalStyle.
363      *
364      * @return a suitable hash code
365      */
366     @Override
367     public int hashCode() {
368         return zeroDigit + positiveSign + negativeSign + decimalSeparator;
369     }
370 
371     //-----------------------------------------------------------------------
372     /**
373      * Returns a string describing this DecimalStyle.
374      *
375      * @return a string description, not null
376      */
377     @Override
378     public String toString() {
379         return "DecimalStyle[" + zeroDigit + positiveSign + negativeSign + decimalSeparator + "]";
380     }
381 
382 }