001/* Charset.java --
002   Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.nio.charset;
040
041import gnu.classpath.ServiceFactory;
042import gnu.classpath.SystemProperties;
043import gnu.java.nio.charset.Provider;
044import gnu.java.nio.charset.iconv.IconvProvider;
045
046import java.nio.ByteBuffer;
047import java.nio.CharBuffer;
048import java.nio.charset.spi.CharsetProvider;
049import java.util.Collections;
050import java.util.HashSet;
051import java.util.Iterator;
052import java.util.LinkedHashSet;
053import java.util.Locale;
054import java.util.Set;
055import java.util.SortedMap;
056import java.util.TreeMap;
057
058/**
059 * @author Jesse Rosenstock
060 * @since 1.4
061 * @status updated to 1.5
062 */
063public abstract class Charset implements Comparable<Charset>
064{
065  private CharsetEncoder cachedEncoder;
066  private CharsetDecoder cachedDecoder;
067
068  /**
069   * Extra Charset providers.
070   */
071  private static CharsetProvider[] providers;
072
073  private final String canonicalName;
074  private final String[] aliases;
075
076  protected Charset (String canonicalName, String[] aliases)
077  {
078    checkName (canonicalName);
079    if (aliases != null)
080      {
081        int n = aliases.length;
082        for (int i = 0; i < n; ++i)
083            checkName (aliases[i]);
084      }
085
086    cachedEncoder = null;
087    cachedDecoder = null;
088    this.canonicalName = canonicalName;
089    this.aliases = aliases;
090  }
091
092  /**
093   * @throws IllegalCharsetNameException  if the name is illegal
094   */
095  private static void checkName (String name)
096  {
097    int n = name.length ();
098
099    if (n == 0)
100      throw new IllegalCharsetNameException (name);
101
102    char ch = name.charAt (0);
103    if (!(('A' <= ch && ch <= 'Z')
104          || ('a' <= ch && ch <= 'z')
105          || ('0' <= ch && ch <= '9')))
106      throw new IllegalCharsetNameException (name);
107
108    for (int i = 1; i < n; ++i)
109      {
110        ch = name.charAt (i);
111        if (!(('A' <= ch && ch <= 'Z')
112              || ('a' <= ch && ch <= 'z')
113              || ('0' <= ch && ch <= '9')
114              || ch == '-' || ch == '.' || ch == ':' || ch == '_'))
115          throw new IllegalCharsetNameException (name);
116      }
117  }
118
119  /**
120   * Returns the system default charset.
121   *
122   * This may be set by the user or VM with the file.encoding
123   * property.
124   *
125   * @since 1.5
126   */
127  public static Charset defaultCharset()
128  {
129    String encoding;
130
131    try
132      {
133        encoding = SystemProperties.getProperty("file.encoding");
134      }
135    catch(SecurityException e)
136      {
137        // Use fallback.
138        encoding = "ISO-8859-1";
139      }
140    catch(IllegalArgumentException e)
141      {
142        // Use fallback.
143        encoding = "ISO-8859-1";
144      }
145
146    try
147      {
148        return forName(encoding);
149      }
150    catch(UnsupportedCharsetException e)
151      {
152        // Ignore.
153      }
154    catch(IllegalCharsetNameException e)
155      {
156        // Ignore.
157      }
158    catch(IllegalArgumentException e)
159      {
160        // Ignore.
161      }
162
163    throw new IllegalStateException("Can't get default charset!");
164  }
165
166  public static boolean isSupported (String charsetName)
167  {
168    return charsetForName (charsetName) != null;
169  }
170
171  /**
172   * Returns the Charset instance for the charset of the given name.
173   *
174   * @param charsetName
175   * @return the Charset instance for the indicated charset
176   * @throws UnsupportedCharsetException if this VM does not support
177   * the charset of the given name.
178   * @throws IllegalCharsetNameException if the given charset name is
179   * legal.
180   * @throws IllegalArgumentException if <code>charsetName</code> is null.
181   */
182  public static Charset forName (String charsetName)
183  {
184    // Throws IllegalArgumentException as the JDK does.
185    if(charsetName == null)
186        throw new IllegalArgumentException("Charset name must not be null.");
187
188    Charset cs = charsetForName (charsetName);
189    if (cs == null)
190      throw new UnsupportedCharsetException (charsetName);
191    return cs;
192  }
193
194  /**
195   * Retrieves a charset for the given charset name.
196   *
197   * @return A charset object for the charset with the specified name, or
198   * <code>null</code> if no such charset exists.
199   *
200   * @throws IllegalCharsetNameException  if the name is illegal
201   */
202  private static Charset charsetForName(String charsetName)
203  {
204    checkName (charsetName);
205    // Try the default provider first
206    // (so we don't need to load external providers unless really necessary)
207    // if it is an exotic charset try loading the external providers.
208    Charset cs = provider().charsetForName(charsetName);
209    if (cs == null)
210      {
211        CharsetProvider[] providers = providers2();
212        for (int i = 0; i < providers.length; i++)
213          {
214            cs = providers[i].charsetForName(charsetName);
215            if (cs != null)
216              break;
217          }
218      }
219    return cs;
220  }
221
222  public static SortedMap<String, Charset> availableCharsets()
223  {
224    TreeMap<String, Charset> charsets
225      = new TreeMap(String.CASE_INSENSITIVE_ORDER);
226    for (Iterator<Charset> i = provider().charsets(); i.hasNext(); )
227      {
228        Charset cs = i.next();
229        charsets.put(cs.name(), cs);
230      }
231
232    CharsetProvider[] providers = providers2();
233    for (int j = 0; j < providers.length; j++)
234      {
235        for (Iterator<Charset> i = providers[j].charsets(); i.hasNext(); )
236          {
237            Charset cs = i.next();
238            charsets.put(cs.name(), cs);
239          }
240      }
241
242    return Collections.unmodifiableSortedMap(charsets);
243  }
244
245  private static CharsetProvider provider()
246  {
247    String useIconv = SystemProperties.getProperty
248      ("gnu.classpath.nio.charset.provider.iconv");
249
250    if (useIconv != null)
251      return IconvProvider.provider();
252
253    return Provider.provider();
254  }
255
256  /**
257   * We need to support multiple providers, reading them from
258   * java.nio.charset.spi.CharsetProvider in the resource directory
259   * META-INF/services. This returns the "extra" charset providers.
260   */
261  private static CharsetProvider[] providers2()
262  {
263    if (providers == null)
264      {
265        try
266          {
267            Iterator i = ServiceFactory.lookupProviders(CharsetProvider.class);
268            LinkedHashSet set = new LinkedHashSet();
269            while (i.hasNext())
270              set.add(i.next());
271
272            providers = new CharsetProvider[set.size()];
273            set.toArray(providers);
274          }
275        catch (Exception e)
276          {
277            throw new RuntimeException(e);
278          }
279      }
280    return providers;
281  }
282
283  public final String name ()
284  {
285    return canonicalName;
286  }
287
288  public final Set<String> aliases ()
289  {
290    if (aliases == null)
291      return Collections.<String>emptySet();
292
293    // should we cache the aliasSet instead?
294    int n = aliases.length;
295    HashSet<String> aliasSet = new HashSet<String> (n);
296    for (int i = 0; i < n; ++i)
297        aliasSet.add (aliases[i]);
298    return Collections.unmodifiableSet (aliasSet);
299  }
300
301  public String displayName ()
302  {
303    return canonicalName;
304  }
305
306  public String displayName (Locale locale)
307  {
308    return canonicalName;
309  }
310
311  public final boolean isRegistered ()
312  {
313    return (!canonicalName.startsWith ("x-")
314            && !canonicalName.startsWith ("X-"));
315  }
316
317  public abstract boolean contains (Charset cs);
318
319  public abstract CharsetDecoder newDecoder ();
320
321  public abstract CharsetEncoder newEncoder ();
322
323  public boolean canEncode ()
324  {
325    return true;
326  }
327
328  // NB: This implementation serializes different threads calling
329  // Charset.encode(), a potential performance problem.  It might
330  // be better to remove the cache, or use ThreadLocal to cache on
331  // a per-thread basis.
332  public final synchronized ByteBuffer encode (CharBuffer cb)
333  {
334    try
335      {
336        if (cachedEncoder == null)
337          {
338            cachedEncoder = newEncoder ()
339              .onMalformedInput (CodingErrorAction.REPLACE)
340              .onUnmappableCharacter (CodingErrorAction.REPLACE);
341          } else
342          cachedEncoder.reset();
343        return cachedEncoder.encode (cb);
344      }
345    catch (CharacterCodingException e)
346      {
347        throw new AssertionError (e);
348      }
349  }
350
351  public final ByteBuffer encode (String str)
352  {
353    return encode (CharBuffer.wrap (str));
354  }
355
356  // NB: This implementation serializes different threads calling
357  // Charset.decode(), a potential performance problem.  It might
358  // be better to remove the cache, or use ThreadLocal to cache on
359  // a per-thread basis.
360  public final synchronized CharBuffer decode (ByteBuffer bb)
361  {
362    try
363      {
364        if (cachedDecoder == null)
365          {
366            cachedDecoder = newDecoder ()
367              .onMalformedInput (CodingErrorAction.REPLACE)
368              .onUnmappableCharacter (CodingErrorAction.REPLACE);
369          } else
370          cachedDecoder.reset();
371
372        return cachedDecoder.decode (bb);
373      }
374    catch (CharacterCodingException e)
375      {
376        throw new AssertionError (e);
377      }
378  }
379
380  public final int compareTo (Charset other)
381  {
382    return canonicalName.compareToIgnoreCase (other.canonicalName);
383  }
384
385  public final int hashCode ()
386  {
387    return canonicalName.hashCode ();
388  }
389
390  public final boolean equals (Object ob)
391  {
392    if (ob instanceof Charset)
393      return canonicalName.equalsIgnoreCase (((Charset) ob).canonicalName);
394    else
395      return false;
396  }
397
398  public final String toString ()
399  {
400    return canonicalName;
401  }
402}