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