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 | } |