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