OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / java / nio / charset / Charset.java
1 /* Charset.java -- 
2    Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package java.nio.charset;
40
41 import gnu.classpath.ServiceFactory;
42 import gnu.classpath.SystemProperties;
43 import gnu.java.nio.charset.Provider;
44 import gnu.java.nio.charset.iconv.IconvProvider;
45
46 import java.nio.ByteBuffer;
47 import java.nio.CharBuffer;
48 import java.nio.charset.spi.CharsetProvider;
49 import java.util.Collections;
50 import java.util.HashSet;
51 import java.util.Iterator;
52 import java.util.LinkedHashSet;
53 import java.util.Locale;
54 import java.util.Set;
55 import java.util.SortedMap;
56 import java.util.TreeMap;
57
58 /**
59  * @author Jesse Rosenstock
60  * @since 1.4
61  * @status updated to 1.5
62  */
63 public abstract class Charset implements Comparable<Charset>
64 {
65   private CharsetEncoder cachedEncoder;
66   private CharsetDecoder cachedDecoder;
67   
68   /**
69    * Extra Charset providers.
70    */
71   private static CharsetProvider[] providers;
72   
73   private final String canonicalName;
74   private final String[] aliases;
75   
76   protected Charset (String canonicalName, String[] aliases)
77   {
78     checkName (canonicalName);
79     if (aliases != null)
80       {
81         int n = aliases.length;
82         for (int i = 0; i < n; ++i)
83             checkName (aliases[i]);
84       }
85
86     cachedEncoder = null;
87     cachedDecoder = null;
88     this.canonicalName = canonicalName;
89     this.aliases = aliases;
90   }
91
92   /**
93    * @throws IllegalCharsetNameException  if the name is illegal
94    */
95   private static void checkName (String name)
96   {
97     int n = name.length ();
98
99     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 = (Charset) 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 }