1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.nio.charset; 19 20import java.io.BufferedReader; 21import java.io.IOException; 22import java.io.InputStream; 23import java.io.InputStreamReader; 24import java.net.URL; 25import java.nio.ByteBuffer; 26import java.nio.CharBuffer; 27import java.nio.charset.spi.CharsetProvider; 28import java.security.AccessController; 29import java.security.PrivilegedAction; 30import java.util.Collections; 31import java.util.Comparator; 32import java.util.Enumeration; 33import java.util.HashMap; 34import java.util.HashSet; 35import java.util.Iterator; 36import java.util.Locale; 37import java.util.Set; 38import java.util.SortedMap; 39import java.util.TreeMap; 40 41import com.ibm.icu4jni.charset.CharsetProviderICU; 42 43/** 44 * A charset defines a mapping between a Unicode character sequence and a byte 45 * sequence. It facilitates the encoding from a Unicode character sequence into 46 * a byte sequence, and the decoding from a byte sequence into a Unicode 47 * character sequence. 48 * <p> 49 * A charset has a canonical name, which is usually in uppercase. Typically it 50 * also has one or more aliases. The name string can only consist of the 51 * following characters: '0' - '9', 'A' - 'Z', 'a' - 'z', '.', ':'. '-' and '_'. 52 * The first character of the name must be a digit or a letter. 53 * </p> 54 * <p> 55 * The following charsets should be supported by any java platform: US-ASCII, 56 * ISO-8859-1, UTF-8, UTF-16BE, UTF-16LE, UTF-16. 57 * </p> 58 * <p> 59 * Additional charsets can be made available by configuring one or more charset 60 * providers through provider configuration files. Such files are always named 61 * as "java.nio.charset.spi.CharsetProvider" and located in the 62 * "META-INF/services" sub folder of one or more classpaths. The files should be 63 * encoded in "UTF-8". Each line of their content specifies the class name of a 64 * charset provider which extends 65 * <code>java.nio.charset.spi.CharsetProvider</code>. A line should end with 66 * '\r', '\n' or '\r\n'. Leading and trailing whitespaces are trimmed. Blank 67 * lines, and lines (after trimming) starting with "#" which are regarded as 68 * comments, are both ignored. Duplicates of names already found are also 69 * ignored. Both the configuration files and the provider classes will be loaded 70 * using the thread context class loader. 71 * </p> 72 * <p> 73 * This class is thread-safe. 74 * </p> 75 * 76 * @see java.nio.charset.spi.CharsetProvider 77 * @since Android 1.0 78 */ 79public abstract class Charset implements Comparable<Charset> { 80 81 /* 82 * -------------------------------------------------------------------- 83 * Constants 84 * -------------------------------------------------------------------- 85 */ 86 87 /* 88 * the name of configuration files where charset provider class names can be 89 * specified. 90 */ 91 private static final String PROVIDER_CONFIGURATION_FILE_NAME = "META-INF/services/java.nio.charset.spi.CharsetProvider"; //$NON-NLS-1$ 92 93 /* 94 * the encoding of configuration files 95 */ 96 private static final String PROVIDER_CONFIGURATION_FILE_ENCODING = "UTF-8"; //$NON-NLS-1$ 97 98 /* 99 * the comment string used in configuration files 100 */ 101 private static final String PROVIDER_CONFIGURATION_FILE_COMMENT = "#"; //$NON-NLS-1$ 102 103 private static ClassLoader systemClassLoader; 104 105 /* 106 * -------------------------------------------------------------------- 107 * Class variables 108 * -------------------------------------------------------------------- 109 */ 110 111 // built in provider instance, assuming thread-safe 112 private static CharsetProviderICU _builtInProvider = null; 113 114 // cached built in charsets 115 private static TreeMap<String, Charset> _builtInCharsets = null; 116 117 /* 118 * -------------------------------------------------------------------- 119 * Instance variables 120 * -------------------------------------------------------------------- 121 */ 122 123 private final String canonicalName; 124 125 // the aliases set 126 private final HashSet<String> aliasesSet; 127 128 // cached Charset table 129 private static HashMap<String, Charset> cachedCharsetTable = new HashMap<String, Charset>(); 130 131 // cached CharsetDecoder table 132 private static HashMap<String, CharsetDecoder> cachedCharsetDecoderTable = new HashMap<String, CharsetDecoder>(); 133 134 // cached CharsetEncoder table 135 private static HashMap<String, CharsetEncoder> cachedCharsetEncoderTable = new HashMap<String, CharsetEncoder>(); 136 137 /* 138 * ------------------------------------------------------------------- 139 * Global initialization 140 * ------------------------------------------------------------------- 141 */ 142 static { 143 /* 144 * create built-in charset provider even if no privilege to access 145 * charset provider. 146 */ 147 _builtInProvider = AccessController 148 .doPrivileged(new PrivilegedAction<CharsetProviderICU>() { 149 public CharsetProviderICU run() { 150 return new CharsetProviderICU(); 151 } 152 }); 153 } 154 155 /* 156 * ------------------------------------------------------------------- 157 * Constructors 158 * ------------------------------------------------------------------- 159 */ 160 161 /** 162 * Constructs a <code>Charset</code> object. Duplicated aliases are 163 * ignored. 164 * 165 * @param canonicalName 166 * the canonical name of the charset. 167 * @param aliases 168 * an array containing all aliases of the charset. May be null. 169 * @throws IllegalCharsetNameException 170 * on an illegal value being supplied for either 171 * <code>canonicalName</code> or for any element of 172 * <code>aliases</code>. 173 * @since Android 1.0 174 */ 175 protected Charset(String canonicalName, String[] aliases) 176 throws IllegalCharsetNameException { 177 // throw IllegalArgumentException if name is null 178 if (null == canonicalName) { 179 throw new NullPointerException(); 180 } 181 // check whether the given canonical name is legal 182 checkCharsetName(canonicalName); 183 this.canonicalName = canonicalName; 184 // check each alias and put into a set 185 this.aliasesSet = new HashSet<String>(); 186 if (null != aliases) { 187 for (int i = 0; i < aliases.length; i++) { 188 checkCharsetName(aliases[i]); 189 this.aliasesSet.add(aliases[i]); 190 } 191 } 192 } 193 194 /* 195 * ------------------------------------------------------------------- 196 * Methods 197 * ------------------------------------------------------------------- 198 */ 199 200 /* 201 * Checks whether a character is a special character that can be used in 202 * charset names, other than letters and digits. 203 */ 204 private static boolean isSpecial(char c) { 205 return ('-' == c || '.' == c || ':' == c || '_' == c); 206 } 207 208 /* 209 * Checks whether a character is a letter (ascii) which are defined in the 210 * spec. 211 */ 212 private static boolean isLetter(char c) { 213 return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); 214 } 215 216 /* 217 * Checks whether a character is a digit (ascii) which are defined in the 218 * spec. 219 */ 220 private static boolean isDigit(char c) { 221 return ('0' <= c && c <= '9'); 222 } 223 224 /* 225 * Checks whether a given string is a legal charset name. The argument name 226 * should not be null. 227 */ 228 private static void checkCharsetName(String name) { 229 // An empty string is illegal charset name 230 if (name.length() == 0) { 231 throw new IllegalCharsetNameException(name); 232 } 233 // The first character must be a letter or a digit 234 // This is related to HARMONY-68 (won't fix) 235 // char first = name.charAt(0); 236 // if (!isLetter(first) && !isDigit(first)) { 237 // throw new IllegalCharsetNameException(name); 238 // } 239 // Check the remaining characters 240 int length = name.length(); 241 for (int i = 0; i < length; i++) { 242 char c = name.charAt(i); 243 if (!isLetter(c) && !isDigit(c) && !isSpecial(c)) { 244 throw new IllegalCharsetNameException(name); 245 } 246 } 247 } 248 249 /* 250 * Use privileged code to get the context class loader. 251 */ 252 private static ClassLoader getContextClassLoader() { 253 final Thread t = Thread.currentThread(); 254 return AccessController 255 .doPrivileged(new PrivilegedAction<ClassLoader>() { 256 public ClassLoader run() { 257 return t.getContextClassLoader(); 258 } 259 }); 260 } 261 262 /* 263 * Use privileged code to get the system class loader. 264 */ 265 private static void getSystemClassLoader() { 266 if (null == systemClassLoader) { 267 systemClassLoader = AccessController 268 .doPrivileged(new PrivilegedAction<ClassLoader>() { 269 public ClassLoader run() { 270 return ClassLoader.getSystemClassLoader(); 271 } 272 }); 273 } 274 } 275 276 /* 277 * Add the charsets supported by the given provider to the map. 278 */ 279 private static void addCharsets(CharsetProvider cp, 280 TreeMap<String, Charset> charsets) { 281 Iterator<Charset> it = cp.charsets(); 282 while (it.hasNext()) { 283 Charset cs = it.next(); 284 // Only new charsets will be added 285 if (!charsets.containsKey(cs.name())) { 286 charsets.put(cs.name(), cs); 287 } 288 } 289 } 290 291 /* 292 * Trim comment string, and then trim white spaces. 293 */ 294 private static String trimClassName(String name) { 295 String trimedName = name; 296 int index = name.indexOf(PROVIDER_CONFIGURATION_FILE_COMMENT); 297 // Trim comments 298 if (index != -1) { 299 trimedName = name.substring(0, index); 300 } 301 return trimedName.trim(); 302 } 303 304 /* 305 * Read a configuration file and add the charsets supported by the providers 306 * specified by this configuration file to the map. 307 */ 308 private static void loadConfiguredCharsets(URL configFile, 309 ClassLoader contextClassLoader, TreeMap<String, Charset> charsets) { 310 BufferedReader reader = null; 311 try { 312 InputStream is = configFile.openStream(); 313 // Read each line for charset provider class names 314 // BEGIN android-modified 315 reader = new BufferedReader(new InputStreamReader(is, 316 PROVIDER_CONFIGURATION_FILE_ENCODING), 8192); 317 // END android-modified 318 String providerClassName = reader.readLine(); 319 while (null != providerClassName) { 320 providerClassName = trimClassName(providerClassName); 321 // Skip comments and blank lines 322 if (providerClassName.length() > 0) { // Non empty string 323 // Load the charset provider 324 Object cp = null; 325 try { 326 Class<?> c = Class.forName(providerClassName, true, 327 contextClassLoader); 328 cp = c.newInstance(); 329 } catch (Exception ex) { 330 // try to use system classloader when context 331 // classloader failed to load config file. 332 try { 333 getSystemClassLoader(); 334 Class<?> c = Class.forName(providerClassName, true, 335 systemClassLoader); 336 cp = c.newInstance(); 337 } catch (Exception e) { 338 throw new Error(e.getMessage(), e); 339 } 340 } 341 // Put the charsets supported by this provider into the map 342 addCharsets((CharsetProvider) cp, charsets); 343 } 344 // Read the next line of the config file 345 providerClassName = reader.readLine(); 346 } 347 } catch (IOException ex) { 348 // Can't read this configuration file, ignore 349 } finally { 350 try { 351 if (null != reader) { 352 reader.close(); 353 } 354 } catch (IOException ex) { 355 // Ignore closing exception 356 } 357 } 358 } 359 360 /** 361 * Gets a map of all available charsets supported by the runtime. 362 * <p> 363 * The returned map contains mappings from canonical names to corresponding 364 * instances of <code>Charset</code>. The canonical names can be 365 * considered as case-insensitive. 366 * </p> 367 * 368 * @return an unmodifiable map of all available charsets supported by the 369 * runtime. 370 * @since Android 1.0 371 */ 372 @SuppressWarnings("unchecked") 373 public static SortedMap<String, Charset> availableCharsets() { 374 // Initialize the built-in charsets map cache if necessary 375 if (null == _builtInCharsets) { 376 synchronized (Charset.class) { 377 if (null == _builtInCharsets) { 378 _builtInCharsets = new TreeMap<String, Charset>( 379 IgnoreCaseComparator.getInstance()); 380 _builtInProvider.putCharsets(_builtInCharsets); 381 } 382 } 383 } 384 385 // Add built-in charsets 386 TreeMap<String, Charset> charsets = (TreeMap<String, Charset>) _builtInCharsets 387 .clone(); 388 389 // Collect all charsets provided by charset providers 390 ClassLoader contextClassLoader = getContextClassLoader(); 391 Enumeration<URL> e = null; 392 try { 393 if (null != contextClassLoader) { 394 e = contextClassLoader 395 .getResources(PROVIDER_CONFIGURATION_FILE_NAME); 396 } else { 397 getSystemClassLoader(); 398 e = systemClassLoader 399 .getResources(PROVIDER_CONFIGURATION_FILE_NAME); 400 } 401 // Examine each configuration file 402 while (e.hasMoreElements()) { 403 loadConfiguredCharsets(e.nextElement(), contextClassLoader, 404 charsets); 405 } 406 } catch (IOException ex) { 407 // Unexpected ClassLoader exception, ignore 408 } 409 return Collections.unmodifiableSortedMap(charsets); 410 } 411 412 /* 413 * Read a configuration file and try to find the desired charset among those 414 * which are supported by the providers specified in this configuration 415 * file. 416 */ 417 private static Charset searchConfiguredCharsets(String charsetName, 418 ClassLoader contextClassLoader, URL configFile) { 419 BufferedReader reader = null; 420 try { 421 InputStream is = configFile.openStream(); 422 // Read each line for charset provider class names 423 // BEGIN android-modified 424 reader = new BufferedReader(new InputStreamReader(is, 425 PROVIDER_CONFIGURATION_FILE_ENCODING), 8192); 426 // END android-modified 427 String providerClassName = reader.readLine(); 428 while (null != providerClassName) { 429 providerClassName = trimClassName(providerClassName); 430 if (providerClassName.length() > 0) { // Non empty string 431 // Load the charset provider 432 Object cp = null; 433 try { 434 Class<?> c = Class.forName(providerClassName, true, 435 contextClassLoader); 436 cp = c.newInstance(); 437 } catch (Exception ex) { 438 // try to use system classloader when context 439 // classloader failed to load config file. 440 try { 441 getSystemClassLoader(); 442 Class<?> c = Class.forName(providerClassName, true, 443 systemClassLoader); 444 cp = c.newInstance(); 445 } catch (SecurityException e) { 446 // BEGIN android-changed 447 // ignore 448 // END android-changed 449 } catch (Exception e) { 450 throw new Error(e.getMessage(), e); 451 } 452 } 453 // BEGIN android-changed 454 if (cp != null) { 455 // Try to get the desired charset from this provider 456 Charset cs = ((CharsetProvider) cp) 457 .charsetForName(charsetName); 458 if (null != cs) { 459 return cs; 460 } 461 } 462 // END android-changed 463 } 464 // Read the next line of the config file 465 providerClassName = reader.readLine(); 466 } 467 return null; 468 } catch (IOException ex) { 469 // Can't read this configuration file 470 return null; 471 } finally { 472 try { 473 if (null != reader) { 474 reader.close(); 475 } 476 } catch (IOException ex) { 477 // Ignore closing exception 478 } 479 } 480 } 481 482 /* 483 * Gets a <code> Charset </code> instance for the specified charset name. If 484 * the charset is not supported, returns null instead of throwing an 485 * exception. 486 */ 487 private static Charset forNameInternal(String charsetName) 488 throws IllegalCharsetNameException { 489 if (null == charsetName) { 490 throw new IllegalArgumentException(); 491 } 492 checkCharsetName(charsetName); 493 synchronized (Charset.class) { 494 // Try to get Charset from cachedCharsetTable 495 Charset cs = getCachedCharset(charsetName); 496 if (null != cs) { 497 return cs; 498 } 499 // Try built-in charsets 500 cs = _builtInProvider.charsetForName(charsetName); 501 if (null != cs) { 502 cacheCharset(cs); 503 return cs; 504 } 505 506 // Collect all charsets provided by charset providers 507 ClassLoader contextClassLoader = getContextClassLoader(); 508 Enumeration<URL> e = null; 509 try { 510 if (null != contextClassLoader) { 511 e = contextClassLoader 512 .getResources(PROVIDER_CONFIGURATION_FILE_NAME); 513 } else { 514 getSystemClassLoader(); 515 e = systemClassLoader 516 .getResources(PROVIDER_CONFIGURATION_FILE_NAME); 517 } 518 // Examine each configuration file 519 while (e.hasMoreElements()) { 520 cs = searchConfiguredCharsets(charsetName, 521 contextClassLoader, e.nextElement()); 522 if (null != cs) { 523 cacheCharset(cs); 524 return cs; 525 } 526 } 527 } catch (IOException ex) { 528 // Unexpected ClassLoader exception, ignore 529 } 530 } 531 return null; 532 } 533 534 /* 535 * save charset into cachedCharsetTable 536 */ 537 private static void cacheCharset(Charset cs) { 538 cachedCharsetTable.put(cs.name(), cs); 539 Set<String> aliasesSet = cs.aliases(); 540 if (null != aliasesSet) { 541 Iterator<String> iter = aliasesSet.iterator(); 542 while (iter.hasNext()) { 543 String alias = iter.next(); 544 cachedCharsetTable.put(alias, cs); 545 } 546 } 547 } 548 549 /* 550 * get cached charset reference by name 551 */ 552 private static Charset getCachedCharset(String name) { 553 return cachedCharsetTable.get(name); 554 } 555 556 /** 557 * Gets a <code>Charset</code> instance for the specified charset name. 558 * 559 * @param charsetName 560 * the canonical name of the charset or an alias. 561 * @return a <code>Charset</code> instance for the specified charset name. 562 * @throws IllegalCharsetNameException 563 * if the specified charset name is illegal. 564 * @throws UnsupportedCharsetException 565 * if the desired charset is not supported by this runtime. 566 * @since Android 1.0 567 */ 568 public static Charset forName(String charsetName) 569 throws IllegalCharsetNameException, UnsupportedCharsetException { 570 Charset c = forNameInternal(charsetName); 571 if (null == c) { 572 throw new UnsupportedCharsetException(charsetName); 573 } 574 return c; 575 } 576 577 /** 578 * Determines whether the specified charset is supported by this runtime. 579 * 580 * @param charsetName 581 * the name of the charset. 582 * @return true if the specified charset is supported, otherwise false. 583 * @throws IllegalCharsetNameException 584 * if the specified charset name is illegal. 585 * @since Android 1.0 586 */ 587 public static boolean isSupported(String charsetName) 588 throws IllegalCharsetNameException { 589 Charset cs = forNameInternal(charsetName); 590 return (null != cs); 591 } 592 593 /** 594 * Determines whether this charset is a super set of the given charset. 595 * 596 * @param charset 597 * a given charset. 598 * @return true if this charset is a super set of the given charset, 599 * false if it's unknown or this charset is not a superset of 600 * the given charset. 601 * @since Android 1.0 602 */ 603 public abstract boolean contains(Charset charset); 604 605 /** 606 * Gets a new instance of an encoder for this charset. 607 * 608 * @return a new instance of an encoder for this charset. 609 * @since Android 1.0 610 */ 611 public abstract CharsetEncoder newEncoder(); 612 613 /** 614 * Gets a new instance of a decoder for this charset. 615 * 616 * @return a new instance of a decoder for this charset. 617 * @since Android 1.0 618 */ 619 public abstract CharsetDecoder newDecoder(); 620 621 /** 622 * Gets the canonical name of this charset. 623 * 624 * @return this charset's name in canonical form. 625 * @since Android 1.0 626 */ 627 public final String name() { 628 return this.canonicalName; 629 } 630 631 /** 632 * Gets the set of this charset's aliases. 633 * 634 * @return an unmodifiable set of this charset's aliases. 635 * @since Android 1.0 636 */ 637 public final Set<String> aliases() { 638 return Collections.unmodifiableSet(this.aliasesSet); 639 } 640 641 /** 642 * Gets the name of this charset for the default locale. 643 * 644 * This is the default implementation of this method which always returns 645 * the canonical name of this charset. Subclasses overriding this Method 646 * may return a display name that was localized. 647 * 648 * @return the name of this charset for the default locale. 649 * @since Android 1.0 650 */ 651 public String displayName() { 652 return this.canonicalName; 653 } 654 655 /** 656 * Gets the name of this charset for the specified locale. 657 * 658 * This is the default implementation of this method which always returns 659 * the canonical name of this charset. Subclasses overriding this Method 660 * may return a display name that was localized. 661 * 662 * @param l 663 * a certain locale 664 * @return the name of this charset for the specified locale. 665 * @since Android 1.0 666 */ 667 public String displayName(Locale l) { 668 return this.canonicalName; 669 } 670 671 /** 672 * Indicates whether this charset is known to be registered in the IANA 673 * Charset Registry. 674 * 675 * @return true if the charset is known to be registered, otherwise returns 676 * false. 677 * @since Android 1.0 678 */ 679 public final boolean isRegistered() { 680 return !canonicalName.startsWith("x-") //$NON-NLS-1$ 681 && !canonicalName.startsWith("X-"); //$NON-NLS-1$ 682 } 683 684 /** 685 * Returns true if this charset supports encoding, false otherwise. 686 * 687 * @return true if this charset supports encoding, false otherwise. 688 * @since Android 1.0 689 */ 690 public boolean canEncode() { 691 return true; 692 } 693 694 /** 695 * Encodes the content of the give character buffer and outputs to a byte 696 * buffer that is to be returned. 697 * <p> 698 * The default action in case of encoding errors is 699 * <code>CodingErrorAction.REPLACE</code>. 700 * </p> 701 * 702 * @param buffer 703 * the character buffer containing the content to be encoded. 704 * @return the result of the encoding. 705 * @since Android 1.0 706 */ 707 synchronized public final ByteBuffer encode(CharBuffer buffer) { 708 CharsetEncoder e = getCachedCharsetEncoder(canonicalName); 709 try { 710 synchronized (e) { 711 return e.encode(buffer); 712 } 713 } catch (CharacterCodingException ex) { 714 throw new Error(ex.getMessage(), ex); 715 } 716 } 717 718 /* 719 * get cached CharsetEncoder by canonical name 720 */ 721 private CharsetEncoder getCachedCharsetEncoder(String name) { 722 synchronized (cachedCharsetEncoderTable) { 723 CharsetEncoder e = cachedCharsetEncoderTable 724 .get(name); 725 if (null == e) { 726 e = this.newEncoder(); 727 e.onMalformedInput(CodingErrorAction.REPLACE); 728 e.onUnmappableCharacter(CodingErrorAction.REPLACE); 729 cachedCharsetEncoderTable.put(name, e); 730 } 731 return e; 732 } 733 } 734 735 /** 736 * Encodes a string and outputs to a byte buffer that is to be returned. 737 * <p> 738 * The default action in case of encoding errors is 739 * <code>CodingErrorAction.REPLACE</code>. 740 * </p> 741 * 742 * @param s 743 * the string to be encoded. 744 * @return the result of the encoding. 745 * @since Android 1.0 746 */ 747 public final ByteBuffer encode(String s) { 748 return encode(CharBuffer.wrap(s)); 749 } 750 751 /** 752 * Decodes the content of the specified byte buffer and writes it to a 753 * character buffer that is to be returned. 754 * <p> 755 * The default action in case of decoding errors is 756 * <code>CodingErrorAction.REPLACE</code>. 757 * </p> 758 * 759 * @param buffer 760 * the byte buffer containing the content to be decoded. 761 * @return a character buffer containing the output of the decoding. 762 * @since Android 1.0 763 */ 764 public final CharBuffer decode(ByteBuffer buffer) { 765 CharsetDecoder d = getCachedCharsetDecoder(canonicalName); 766 try { 767 synchronized (d) { 768 return d.decode(buffer); 769 } 770 } catch (CharacterCodingException ex) { 771 throw new Error(ex.getMessage(), ex); 772 } 773 } 774 775 /* 776 * get cached CharsetDecoder by canonical name 777 */ 778 private CharsetDecoder getCachedCharsetDecoder(String name) { 779 synchronized (cachedCharsetDecoderTable) { 780 CharsetDecoder d = cachedCharsetDecoderTable 781 .get(name); 782 if (null == d) { 783 d = this.newDecoder(); 784 d.onMalformedInput(CodingErrorAction.REPLACE); 785 d.onUnmappableCharacter(CodingErrorAction.REPLACE); 786 cachedCharsetDecoderTable.put(name, d); 787 } 788 return d; 789 } 790 } 791 792 /* 793 * ------------------------------------------------------------------- 794 * Methods implementing parent interface Comparable 795 * ------------------------------------------------------------------- 796 */ 797 798 /** 799 * Compares this charset with the given charset. This comparation is 800 * based on the case insensitive canonical names of the charsets. 801 * 802 * @param charset 803 * the given object to be compared with. 804 * @return a negative integer if less than the given object, a positive 805 * integer if larger than it, or 0 if equal to it. 806 * @since Android 1.0 807 */ 808 public final int compareTo(Charset charset) { 809 return this.canonicalName.compareToIgnoreCase(charset.canonicalName); 810 } 811 812 /* 813 * ------------------------------------------------------------------- 814 * Methods overriding parent class Object 815 * ------------------------------------------------------------------- 816 */ 817 818 /** 819 * Determines whether this charset equals to the given object. They are 820 * considered to be equal if they have the same canonical name. 821 * 822 * @param obj 823 * the given object to be compared with. 824 * @return true if they have the same canonical name, otherwise false. 825 * @since Android 1.0 826 */ 827 @Override 828 public final boolean equals(Object obj) { 829 if (obj instanceof Charset) { 830 Charset that = (Charset) obj; 831 return this.canonicalName.equals(that.canonicalName); 832 } 833 return false; 834 } 835 836 /** 837 * Gets the hash code of this charset. 838 * 839 * @return the hash code of this charset. 840 * @since Android 1.0 841 */ 842 @Override 843 public final int hashCode() { 844 return this.canonicalName.hashCode(); 845 } 846 847 /** 848 * Gets a string representation of this charset. Usually this contains the 849 * canonical name of the charset. 850 * 851 * @return a string representation of this charset. 852 * @since Android 1.0 853 */ 854 @Override 855 public final String toString() { 856 return "Charset[" + this.canonicalName + "]"; //$NON-NLS-1$//$NON-NLS-2$ 857 } 858 859 /** 860 * Gets the system default charset from the virtual machine. 861 * 862 * @return the default charset. 863 * @since Android 1.0 864 */ 865 public static Charset defaultCharset() { 866 Charset defaultCharset = null; 867 String encoding = AccessController 868 .doPrivileged(new PrivilegedAction<String>() { 869 public String run() { 870 return System.getProperty("file.encoding"); //$NON-NLS-1$ 871 } 872 }); 873 try { 874 defaultCharset = Charset.forName(encoding); 875 } catch (UnsupportedCharsetException e) { 876 defaultCharset = Charset.forName("UTF-8"); //$NON-NLS-1$ 877 } 878 return defaultCharset; 879 } 880 881 /** 882 * A comparator that ignores case. 883 */ 884 static class IgnoreCaseComparator implements Comparator<String> { 885 886 // the singleton 887 private static Comparator<String> c = new IgnoreCaseComparator(); 888 889 /* 890 * Default constructor. 891 */ 892 private IgnoreCaseComparator() { 893 // no action 894 } 895 896 /* 897 * Gets a single instance. 898 */ 899 public static Comparator<String> getInstance() { 900 return c; 901 } 902 903 /* 904 * Compares two strings ignoring case. 905 */ 906 public int compare(String s1, String s2) { 907 return s1.compareToIgnoreCase(s2); 908 } 909 } 910} 911