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 8743 2012-10-07 03:06:20Z 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 | while ( providerFiles.hasMoreElements() ) |
118 | { |
119 | final URL url = ( URL ) providerFiles.nextElement(); |
120 | BufferedReader reader = null; |
121 | |
122 | try |
123 | { |
124 | String line; |
125 | reader = new BufferedReader( |
126 | new InputStreamReader( url.openStream(), |
127 | "UTF-8" ) ); |
128 | |
129 | while ( ( line = reader.readLine() ) != null ) |
130 | { |
131 | // Check that the line denotes a valid Java |
132 | // classname and load that class using |
133 | // reflection. |
134 | if ( line.indexOf( '#' ) < 0 ) |
135 | { |
136 | providers.add( |
137 | classLoader.loadClass( line ). |
138 | newInstance() ); |
139 | |
140 | } |
141 | } |
142 | |
143 | reader.close(); |
144 | reader = null; |
145 | } |
146 | finally |
147 | { |
148 | if ( reader != null ) |
149 | { |
150 | reader.close(); |
151 | } |
152 | } |
153 | } |
154 | } |
155 | } |
156 | } |
157 | |
158 | // Search cached charsets. |
159 | Charset charset = ( Charset ) charsets.get( name ); |
160 | if ( charset == null ) |
161 | { |
162 | synchronized ( Charsets.class ) |
163 | { |
164 | // Search all available providers for a charset matching "name". |
165 | for ( final Iterator it = providers.iterator(); it.hasNext();) |
166 | { |
167 | charset = |
168 | ( ( CharsetProvider ) it.next() ).charsetForName( name ); |
169 | |
170 | if ( charset != null ) |
171 | { |
172 | charsets.put( name, charset ); |
173 | break; |
174 | } |
175 | } |
176 | } |
177 | } |
178 | |
179 | // Fall back to platform charsets if nothing is found so far. |
180 | if ( charset == null ) |
181 | { |
182 | synchronized ( Charsets.class ) |
183 | { |
184 | charset = Charset.forName( name ); |
185 | charsets.put( name, charset ); |
186 | } |
187 | } |
188 | |
189 | return charset; |
190 | } |
191 | |
192 | /** |
193 | * Encodes a given string to an array of bytes representing the characters |
194 | * of the string in a given charset. |
195 | * |
196 | * @param str the string to encode. |
197 | * @param charset the name of the charset to use. |
198 | * |
199 | * @throws NullPointerException if {@code str} or {@code charset} is |
200 | * {@code null}. |
201 | * @throws java.nio.charset.IllegalCharsetNameException if {@code charset} |
202 | * is no valid charset name. |
203 | * @throws java.nio.charset.UnsupportedCharsetException if {@code charset} |
204 | * is not supported. |
205 | */ |
206 | public static byte[] encode( final String str, final String charset ) |
207 | { |
208 | if ( str == null ) |
209 | { |
210 | throw new NullPointerException( "str" ); |
211 | } |
212 | if ( charset == null ) |
213 | { |
214 | throw new NullPointerException( "charset" ); |
215 | } |
216 | |
217 | final byte[] ret; |
218 | try |
219 | { |
220 | final Charset cset = Charsets.getCharset( charset ); |
221 | final ByteBuffer buf = cset.encode( str ); |
222 | |
223 | if ( buf.hasArray() ) |
224 | { |
225 | if ( buf.array().length == buf.limit() ) |
226 | { |
227 | ret = buf.array(); |
228 | } |
229 | else |
230 | { |
231 | ret = new byte[ buf.limit() ]; |
232 | System.arraycopy( buf.array(), buf.arrayOffset(), |
233 | ret, 0, ret.length ); |
234 | |
235 | } |
236 | } |
237 | else |
238 | { |
239 | ret = new byte[ buf.limit() ]; |
240 | buf.rewind(); |
241 | buf.get( ret ); |
242 | } |
243 | } |
244 | catch ( final ClassNotFoundException e ) |
245 | { |
246 | throw new AssertionError( e ); |
247 | } |
248 | catch ( final InstantiationException e ) |
249 | { |
250 | throw new AssertionError( e ); |
251 | } |
252 | catch ( final IllegalAccessException e ) |
253 | { |
254 | throw new AssertionError( e ); |
255 | } |
256 | catch ( final IOException e ) |
257 | { |
258 | throw new AssertionError( e ); |
259 | } |
260 | |
261 | return ret; |
262 | } |
263 | |
264 | /** |
265 | * Decodes the bytes of a given array to a string. |
266 | * |
267 | * @param bytes the bytes to decode. |
268 | * @param charset the name of the charset to use. |
269 | * |
270 | * @throws NullPointerException if {@code bytes} or {@code charset} is |
271 | * {@code null}. |
272 | * @throws java.nio.charset.IllegalCharsetNameException if {@code charset} |
273 | * is no valid charset name. |
274 | * @throws java.nio.charset.UnsupportedCharsetException if {@code charset} |
275 | * is not supported. |
276 | */ |
277 | public static String decode( final byte[] bytes, final String charset ) |
278 | { |
279 | if ( bytes == null ) |
280 | { |
281 | throw new NullPointerException( "bytes" ); |
282 | } |
283 | if ( charset == null ) |
284 | { |
285 | throw new NullPointerException( "charset" ); |
286 | } |
287 | |
288 | final String ret; |
289 | try |
290 | { |
291 | final Charset cset = Charsets.getCharset( charset ); |
292 | final CharBuffer buf = cset.decode( ByteBuffer.wrap( bytes ) ); |
293 | |
294 | if ( buf.hasArray() ) |
295 | { |
296 | ret = String.valueOf( buf.array(), buf.arrayOffset(), |
297 | buf.length() ); |
298 | |
299 | } |
300 | else |
301 | { |
302 | final char[] c = new char[ buf.length() ]; |
303 | buf.rewind(); |
304 | buf.get( c ); |
305 | ret = String.valueOf( c ); |
306 | } |
307 | } |
308 | catch ( final ClassNotFoundException e ) |
309 | { |
310 | throw new AssertionError( e ); |
311 | } |
312 | catch ( final InstantiationException e ) |
313 | { |
314 | throw new AssertionError( e ); |
315 | } |
316 | catch ( final IllegalAccessException e ) |
317 | { |
318 | throw new AssertionError( e ); |
319 | } |
320 | catch ( final IOException e ) |
321 | { |
322 | throw new AssertionError( e ); |
323 | } |
324 | |
325 | return ret; |
326 | } |
327 | |
328 | /** |
329 | * Decodes the bytes of a given array to a string. |
330 | * |
331 | * @param bytes the bytes to decode. |
332 | * @param off the offset from where to start decoding. |
333 | * @param count the number of bytes to decode starting at {@code offset}. |
334 | * @param charset the name of the charset to use. |
335 | * |
336 | * @throws NullPointerException if {@code bytes} or {@code charset} is |
337 | * {@code null}. |
338 | * @throws IndexOutOfBoundsException if {@code off} is negative or greater |
339 | * than the length of {@code bytes} or {@code off + count} is negative or |
340 | * greater than the length of {@code bytes}. |
341 | * @throws java.nio.charset.IllegalCharsetNameException if {@code charset} |
342 | * is no valid charset name. |
343 | * @throws java.nio.charset.UnsupportedCharsetException if {@code charset} |
344 | * is not supported. |
345 | */ |
346 | public static String decode( final byte[] bytes, final int off, |
347 | final int count, final String charset ) |
348 | { |
349 | if ( bytes == null ) |
350 | { |
351 | throw new NullPointerException( "bytes" ); |
352 | } |
353 | if ( charset == null ) |
354 | { |
355 | throw new NullPointerException( "charset" ); |
356 | } |
357 | if ( off < 0 || off >= bytes.length ) |
358 | { |
359 | throw new ArrayIndexOutOfBoundsException( off ); |
360 | } |
361 | if ( count < 0 || off + count >= bytes.length ) |
362 | { |
363 | throw new ArrayIndexOutOfBoundsException( count + off ); |
364 | } |
365 | |
366 | final String ret; |
367 | try |
368 | { |
369 | final Charset cset = Charsets.getCharset( charset ); |
370 | final CharBuffer buf = cset.decode( |
371 | ByteBuffer.wrap( bytes, off, count ) ); |
372 | |
373 | if ( buf.hasArray() ) |
374 | { |
375 | ret = String.valueOf( buf.array(), buf.arrayOffset(), |
376 | buf.length() ); |
377 | |
378 | } |
379 | else |
380 | { |
381 | final char[] c = new char[ buf.length() ]; |
382 | buf.rewind(); |
383 | buf.get( c ); |
384 | ret = String.valueOf( c ); |
385 | } |
386 | } |
387 | catch ( final ClassNotFoundException e ) |
388 | { |
389 | throw new AssertionError( e ); |
390 | } |
391 | catch ( final InstantiationException e ) |
392 | { |
393 | throw new AssertionError( e ); |
394 | } |
395 | catch ( final IllegalAccessException e ) |
396 | { |
397 | throw new AssertionError( e ); |
398 | } |
399 | catch ( final IOException e ) |
400 | { |
401 | throw new AssertionError( e ); |
402 | } |
403 | |
404 | return ret; |
405 | } |
406 | |
407 | //----------------------------------------------------------------Charsets-- |
408 | } |