001/* 002 * jDTAUS Core Utilities 003 * Copyright (C) 2005 Christian Schulte 004 * <cs@schulte.it> 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License, or any later version. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 019 * 020 */ 021package org.jdtaus.core.nio.util; 022 023import java.io.BufferedReader; 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.InputStreamReader; 027import java.net.URL; 028import java.nio.ByteBuffer; 029import java.nio.CharBuffer; 030import java.nio.charset.Charset; 031import java.nio.charset.spi.CharsetProvider; 032import java.util.Enumeration; 033import java.util.HashMap; 034import java.util.Iterator; 035import java.util.LinkedList; 036import java.util.List; 037import java.util.Map; 038 039/** 040 * Charset coder and decoder utility. 041 * <p>This class extends the former charset provider implementations which 042 * cannot be used in every environment (e.g. WebStart, Maven) without 043 * installation in the JRE extensions directory where they are available to the 044 * system classloader. It uses the same service provider files as the 045 * platform implementation ({@code java.nio.charset.spi.CharsetProvider}) but 046 * is capable of using the current thread's classloader before falling back 047 * to the system classloader for loading {@code CharsetProvider} classes.</p> 048 * 049 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 050 * @version $JDTAUS: Charsets.java 8641 2012-09-27 06:45:17Z schulte $ 051 */ 052public class Charsets 053{ 054 //--Charsets---------------------------------------------------------------- 055 056 /** Cached {@code CharsetProvider} instances. */ 057 private static final List providers = new LinkedList(); 058 059 /** Cached {@code Charset} instances by name. */ 060 private static final Map charsets = new HashMap( 100 ); 061 062 /** Private constructor. */ 063 private Charsets() 064 { 065 super(); 066 } 067 068 /** 069 * Gets a charset for the given name. 070 * 071 * @param name the name of the charset to return. 072 * 073 * @return a {@code Charset} corresponding to {@code name} or {@code null} 074 * if no such {@code Charset} is available. 075 * 076 * @throws IOException if reading the service provider files fails. 077 * @throws ClassNotFoundException if a service provider file defines 078 * a class which cannot be loaded. 079 * @throws InstantiationException if creating an instance of a 080 * {@code CharsetProvider} fails. 081 * @throws IllegalAccessException if a {@code CharsetProvider} class 082 * does not define a public no-arg constructor. 083 * @throws java.nio.charset.IllegalCharsetNameException if {@code name} is 084 * no valid charset name. 085 * @throws java.nio.charset.UnsupportedCharsetException if {@code name} is 086 * not supported. 087 */ 088 private static Charset getCharset( final String name ) 089 throws IOException, ClassNotFoundException, InstantiationException, 090 IllegalAccessException 091 { 092 // Populate the provider list with available providers if it is empty. 093 if ( providers.size() == 0 ) 094 { 095 synchronized ( Charsets.class ) 096 { 097 // Use the current thread's context classloader if available or 098 // fall back to the system classloader. 099 ClassLoader classLoader = Thread.currentThread(). 100 getContextClassLoader(); 101 102 if ( classLoader == null ) 103 { 104 classLoader = ClassLoader.getSystemClassLoader(); 105 } 106 107 assert classLoader != null : 108 "Expected system classloader to always be available."; 109 110 // Read all service provider files and load all defined 111 // provider classes. 112 final Enumeration providerFiles = classLoader.getResources( 113 "META-INF/services/java.nio.charset.spi.CharsetProvider" ); 114 115 if ( providerFiles != null ) 116 { 117 for (; providerFiles.hasMoreElements();) 118 { 119 final URL url = ( URL ) providerFiles.nextElement(); 120 final InputStream in = url.openStream(); 121 122 try 123 { 124 String line; 125 final BufferedReader reader = new BufferedReader( 126 new InputStreamReader( in, "UTF-8" ) ); 127 128 while ( ( line = reader.readLine() ) != null ) 129 { 130 // Check that the line denotes a valid Java 131 // classname and load that class using 132 // reflection. 133 if ( line.indexOf( '#' ) < 0 ) 134 { 135 providers.add( classLoader.loadClass( line ). 136 newInstance() ); 137 138 } 139 } 140 } 141 finally 142 { 143 in.close(); 144 } 145 } 146 } 147 } 148 } 149 150 // Search cached charsets. 151 Charset charset = ( Charset ) charsets.get( name ); 152 if ( charset == null ) 153 { 154 synchronized ( Charsets.class ) 155 { 156 // Search all available providers for a charset matching "name". 157 for ( Iterator it = providers.iterator(); it.hasNext();) 158 { 159 charset = 160 ( ( CharsetProvider ) it.next() ).charsetForName( name ); 161 162 if ( charset != null ) 163 { 164 charsets.put( name, charset ); 165 break; 166 } 167 } 168 } 169 } 170 171 // Fall back to platform charsets if nothing is found so far. 172 if ( charset == null ) 173 { 174 synchronized ( Charsets.class ) 175 { 176 charset = Charset.forName( name ); 177 charsets.put( name, charset ); 178 } 179 } 180 181 return charset; 182 } 183 184 /** 185 * Encodes a given string to an array of bytes representing the characters 186 * of the string in a given charset. 187 * 188 * @param str the string to encode. 189 * @param charset the name of the charset to use. 190 * 191 * @throws NullPointerException if {@code str} or {@code charset} is 192 * {@code null}. 193 * @throws java.nio.charset.IllegalCharsetNameException if {@code charset} 194 * is no valid charset name. 195 * @throws java.nio.charset.UnsupportedCharsetException if {@code charset} 196 * is not supported. 197 */ 198 public static byte[] encode( final String str, final String charset ) 199 { 200 if ( str == null ) 201 { 202 throw new NullPointerException( "str" ); 203 } 204 if ( charset == null ) 205 { 206 throw new NullPointerException( "charset" ); 207 } 208 209 final byte[] ret; 210 try 211 { 212 final Charset cset = Charsets.getCharset( charset ); 213 final ByteBuffer buf = cset.encode( str ); 214 215 if ( buf.hasArray() ) 216 { 217 if ( buf.array().length == buf.limit() ) 218 { 219 ret = buf.array(); 220 } 221 else 222 { 223 ret = new byte[ buf.limit() ]; 224 System.arraycopy( buf.array(), buf.arrayOffset(), 225 ret, 0, ret.length ); 226 227 } 228 } 229 else 230 { 231 ret = new byte[ buf.limit() ]; 232 buf.rewind(); 233 buf.get( ret ); 234 } 235 } 236 catch ( ClassNotFoundException e ) 237 { 238 throw new AssertionError( e ); 239 } 240 catch ( InstantiationException e ) 241 { 242 throw new AssertionError( e ); 243 } 244 catch ( IllegalAccessException e ) 245 { 246 throw new AssertionError( e ); 247 } 248 catch ( IOException e ) 249 { 250 throw new AssertionError( e ); 251 } 252 253 return ret; 254 } 255 256 /** 257 * Decodes the bytes of a given array to a string. 258 * 259 * @param bytes the bytes to decode. 260 * @param charset the name of the charset to use. 261 * 262 * @throws NullPointerException if {@code bytes} or {@code charset} is 263 * {@code null}. 264 * @throws java.nio.charset.IllegalCharsetNameException if {@code charset} 265 * is no valid charset name. 266 * @throws java.nio.charset.UnsupportedCharsetException if {@code charset} 267 * is not supported. 268 */ 269 public static String decode( final byte[] bytes, final String charset ) 270 { 271 if ( bytes == null ) 272 { 273 throw new NullPointerException( "bytes" ); 274 } 275 if ( charset == null ) 276 { 277 throw new NullPointerException( "charset" ); 278 } 279 280 final String ret; 281 try 282 { 283 final Charset cset = Charsets.getCharset( charset ); 284 final CharBuffer buf = cset.decode( ByteBuffer.wrap( bytes ) ); 285 286 if ( buf.hasArray() ) 287 { 288 ret = String.valueOf( buf.array(), buf.arrayOffset(), 289 buf.length() ); 290 291 } 292 else 293 { 294 final char[] c = new char[ buf.length() ]; 295 buf.rewind(); 296 buf.get( c ); 297 ret = String.valueOf( c ); 298 } 299 } 300 catch ( ClassNotFoundException e ) 301 { 302 throw new AssertionError( e ); 303 } 304 catch ( InstantiationException e ) 305 { 306 throw new AssertionError( e ); 307 } 308 catch ( IllegalAccessException e ) 309 { 310 throw new AssertionError( e ); 311 } 312 catch ( IOException e ) 313 { 314 throw new AssertionError( e ); 315 } 316 317 return ret; 318 } 319 320 /** 321 * Decodes the bytes of a given array to a string. 322 * 323 * @param bytes the bytes to decode. 324 * @param off the offset from where to start decoding. 325 * @param count the number of bytes to decode starting at {@code offset}. 326 * @param charset the name of the charset to use. 327 * 328 * @throws NullPointerException if {@code bytes} or {@code charset} is 329 * {@code null}. 330 * @throws IndexOutOfBoundsException if {@code off} is negative or greater 331 * than the length of {@code bytes} or {@code off + count} is negative or 332 * greater than the length of {@code bytes}. 333 * @throws java.nio.charset.IllegalCharsetNameException if {@code charset} 334 * is no valid charset name. 335 * @throws java.nio.charset.UnsupportedCharsetException if {@code charset} 336 * is not supported. 337 */ 338 public static String decode( final byte[] bytes, final int off, 339 final int count, final String charset ) 340 { 341 if ( bytes == null ) 342 { 343 throw new NullPointerException( "bytes" ); 344 } 345 if ( charset == null ) 346 { 347 throw new NullPointerException( "charset" ); 348 } 349 if ( off < 0 || off >= bytes.length ) 350 { 351 throw new ArrayIndexOutOfBoundsException( off ); 352 } 353 if ( count < 0 || off + count >= bytes.length ) 354 { 355 throw new ArrayIndexOutOfBoundsException( count + off ); 356 } 357 358 final String ret; 359 try 360 { 361 final Charset cset = Charsets.getCharset( charset ); 362 final CharBuffer buf = cset.decode( 363 ByteBuffer.wrap( bytes, off, count ) ); 364 365 if ( buf.hasArray() ) 366 { 367 ret = String.valueOf( buf.array(), buf.arrayOffset(), 368 buf.length() ); 369 370 } 371 else 372 { 373 final char[] c = new char[ buf.length() ]; 374 buf.rewind(); 375 buf.get( c ); 376 ret = String.valueOf( c ); 377 } 378 } 379 catch ( ClassNotFoundException e ) 380 { 381 throw new AssertionError( e ); 382 } 383 catch ( InstantiationException e ) 384 { 385 throw new AssertionError( e ); 386 } 387 catch ( IllegalAccessException e ) 388 { 389 throw new AssertionError( e ); 390 } 391 catch ( IOException e ) 392 { 393 throw new AssertionError( e ); 394 } 395 396 return ret; 397 } 398 399 //----------------------------------------------------------------Charsets-- 400}