View Javadoc

1   /*
2    *  jDTAUS Core Utilities
3    *  Copyright (C) 2005 Christian Schulte
4    *  <cs@schulte.it>
5    *
6    *  This library is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public
8    *  License as published by the Free Software Foundation; either
9    *  version 2.1 of the License, or any later version.
10   *
11   *  This library is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this library; if not, write to the Free Software
18   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19   *
20   */
21  package org.jdtaus.core.nio.util;
22  
23  import java.io.BufferedReader;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.net.URL;
28  import java.nio.ByteBuffer;
29  import java.nio.CharBuffer;
30  import java.nio.charset.Charset;
31  import java.nio.charset.spi.CharsetProvider;
32  import java.util.Enumeration;
33  import java.util.HashMap;
34  import java.util.Iterator;
35  import java.util.LinkedList;
36  import java.util.List;
37  import java.util.Map;
38  
39  /**
40   * Charset coder and decoder utility.
41   * <p>This class extends the former charset provider implementations which
42   * cannot be used in every environment (e.g. WebStart, Maven) without
43   * installation in the JRE extensions directory where they are available to the
44   * system classloader. It uses the same service provider files as the
45   * platform implementation ({@code java.nio.charset.spi.CharsetProvider}) but
46   * is capable of using the current thread's classloader before falling back
47   * to the system classloader for loading {@code CharsetProvider} classes.</p>
48   *
49   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
50   * @version $JDTAUS: Charsets.java 8641 2012-09-27 06:45:17Z schulte $
51   */
52  public class Charsets
53  {
54      //--Charsets----------------------------------------------------------------
55  
56      /** Cached {@code CharsetProvider} instances. */
57      private static final List providers = new LinkedList();
58  
59      /** Cached {@code Charset} instances by name. */
60      private static final Map charsets = new HashMap( 100 );
61  
62      /** Private constructor. */
63      private Charsets()
64      {
65          super();
66      }
67  
68      /**
69       * Gets a charset for the given name.
70       *
71       * @param name the name of the charset to return.
72       *
73       * @return a {@code Charset} corresponding to {@code name} or {@code null}
74       * if no such {@code Charset} is available.
75       *
76       * @throws IOException if reading the service provider files fails.
77       * @throws ClassNotFoundException if a service provider file defines
78       * a class which cannot be loaded.
79       * @throws InstantiationException if creating an instance of a
80       * {@code CharsetProvider} fails.
81       * @throws IllegalAccessException if a {@code CharsetProvider} class
82       * does not define a public no-arg constructor.
83       * @throws java.nio.charset.IllegalCharsetNameException if {@code name} is
84       * no valid charset name.
85       * @throws java.nio.charset.UnsupportedCharsetException if {@code name} is
86       * not supported.
87       */
88      private static Charset getCharset( final String name )
89          throws IOException, ClassNotFoundException, InstantiationException,
90                 IllegalAccessException
91      {
92          // Populate the provider list with available providers if it is empty.
93          if ( providers.size() == 0 )
94          {
95              synchronized ( Charsets.class )
96              {
97                  // Use the current thread's context classloader if available or
98                  // fall back to the system classloader.
99                  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 }