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}