001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.cldc.i18n;
028:
029: import java.io.*;
030:
031: /**
032: * This class provides general helper functions for the J2ME environment.
033: * <p>
034: * <em>No application code should reference this class directly.</em>
035: *
036: * @version CLDC 1.1 03/29/02
037: */
038: public class Helper {
039:
040: /**
041: * The name of the default character encoding
042: */
043: private static String defaultEncoding;
044:
045: /**
046: * Default path to the J2ME classes
047: */
048: private static String defaultMEPath;
049:
050: /**
051: * Class initializer
052: */
053: static {
054: /* Get the default encoding name */
055: defaultEncoding = System.getProperty("microedition.encoding");
056: if (defaultEncoding == null) {
057: defaultEncoding = "ISO-8859-1";
058: }
059:
060: /* Set the default system library path */
061: defaultMEPath = "com.sun.cldc.i18n.j2me";
062: }
063:
064: /*------------------------------------------------------------------------------*/
065: /* Character encoding */
066: /*------------------------------------------------------------------------------*/
067:
068: /**
069: * Get a reader for an InputStream
070: *
071: * @param is The input stream the reader is for
072: * @return A new reader for the stream
073: */
074: public static Reader getStreamReader(InputStream is) {
075: try {
076: return getStreamReader(is, defaultEncoding);
077: } catch (UnsupportedEncodingException x) {
078: try {
079: defaultEncoding = "ISO8859_1";
080: return getStreamReader(is, defaultEncoding);
081: } catch (UnsupportedEncodingException e) {
082: throw new RuntimeException(
083: /* #ifdef VERBOSE_EXCEPTIONS */
084: /// skipped "Missing default encoding "+defaultEncoding
085: /* #endif */
086: );
087: }
088: }
089: }
090:
091: /**
092: * Get a reader for an InputStream
093: *
094: * @param is The input stream the reader is for
095: * @param name The name of the decoder
096: * @return A new reader for the stream
097: * @exception UnsupportedEncodingException If the encoding is not known
098: */
099: public static Reader getStreamReader(InputStream is, String name)
100: throws UnsupportedEncodingException {
101:
102: /* Test for null arguments */
103: if (is == null || name == null) {
104: throw new NullPointerException();
105: }
106:
107: /* Get the reader from the encoding */
108: StreamReader fr = getStreamReaderPrim(name);
109:
110: /* Open the connection and return*/
111: return fr.open(is, name);
112: }
113:
114: /**
115: * Get a reader for an InputStream
116: *
117: * @param is The input stream the reader is for
118: * @param name The name of the decoder
119: * @return A new reader for the stream
120: * @exception UnsupportedEncodingException If the encoding is not known
121: */
122: private static StreamReader getStreamReaderPrim(String name)
123: throws UnsupportedEncodingException {
124: try {
125: return (StreamReader) getStreamReaderOrWriter(name,
126: "_Reader");
127: } catch (ClassCastException x) {
128: throw new RuntimeException(
129: /* #ifdef VERBOSE_EXCEPTIONS */
130: /// skipped "ClassCastException "+x.getMessage()
131: /* #endif */
132: );
133: }
134: }
135:
136: private static Object getStreamReaderOrWriter(String name,
137: String suffix) throws UnsupportedEncodingException {
138: if (name == null) {
139: throw new NullPointerException();
140: }
141:
142: name = internalNameForEncoding(name);
143:
144: try {
145: String className;
146:
147: /* Get the reader class name */
148: className = defaultMEPath + '.' + name + suffix;
149:
150: /* Using the decoder names lookup the implementation class */
151: Class clazz = Class.forName(className);
152:
153: /* Return a new instance */
154: return clazz.newInstance();
155: } catch (ClassNotFoundException x) {
156: throw new UnsupportedEncodingException(
157: /* #ifdef VERBOSE_EXCEPTIONS */
158: /// skipped "Encoding "+name+" not found"
159: /* #endif */
160: );
161: } catch (InstantiationException x) {
162: throw new RuntimeException(
163: /* #ifdef VERBOSE_EXCEPTIONS */
164: /// skipped "InstantiationException "+x.getMessage()
165: /* #endif */
166: );
167: } catch (IllegalAccessException x) {
168: throw new RuntimeException(
169: /* #ifdef VERBOSE_EXCEPTIONS */
170: /// skipped "IllegalAccessException "+x.getMessage()
171: /* #endif */
172: );
173: }
174: }
175:
176: /**
177: * Get a writer for an OutputStream
178: *
179: * @param os The output stream the reader is for
180: * @return A new writer for the stream
181: */
182: public static Writer getStreamWriter(OutputStream os) {
183: try {
184: return getStreamWriter(os, defaultEncoding);
185: } catch (UnsupportedEncodingException x) {
186: try {
187: defaultEncoding = "ISO8859_1";
188: return getStreamWriter(os, defaultEncoding);
189: } catch (UnsupportedEncodingException e) {
190: throw new RuntimeException(
191: /* #ifdef VERBOSE_EXCEPTIONS */
192: /// skipped "Missing default encoding " +
193: /// skipped defaultEncoding
194: /* #endif */
195: );
196: }
197: }
198: }
199:
200: /**
201: * Get a writer for an OutputStream
202: *
203: * @param os The output stream the reader is for
204: * @param name The name of the decoder
205: * @return A new writer for the stream
206: * @exception UnsupportedEncodingException If the encoding is not known
207: */
208: public static Writer getStreamWriter(OutputStream os, String name)
209: throws UnsupportedEncodingException {
210:
211: /* Test for null arguments */
212: if (os == null || name == null) {
213: throw new NullPointerException();
214: }
215:
216: /* Get the writer from the encoding */
217: StreamWriter sw = getStreamWriterPrim(name);
218:
219: /* Open it on the output stream and return */
220: return sw.open(os, name);
221: }
222:
223: /**
224: * Get a writer for an OutputStream
225: *
226: * @param os The output stream the reader is for
227: * @param name The name of the decoder
228: * @return A new writer for the stream
229: * @exception UnsupportedEncodingException If the encoding is not known
230: */
231: private static StreamWriter getStreamWriterPrim(String name)
232: throws UnsupportedEncodingException {
233: try {
234: return (StreamWriter) getStreamReaderOrWriter(name,
235: "_Writer");
236: } catch (ClassCastException x) {
237: throw new RuntimeException(
238: /* #ifdef VERBOSE_EXCEPTIONS */
239: /// skipped "ClassCastException "+x.getMessage()
240: /* #endif */
241: );
242: }
243: }
244:
245: /**
246: * Convert a byte array to a char array
247: *
248: * @param buffer The byte array buffer
249: * @param offset The offset
250: * @param length The length
251: * @return A new char array
252: */
253: public static char[] byteToCharArray(byte[] buffer, int offset,
254: int length) {
255: try {
256: return byteToCharArray(buffer, offset, length,
257: defaultEncoding);
258: } catch (UnsupportedEncodingException x) {
259: throw new RuntimeException(
260: /* #ifdef VERBOSE_EXCEPTIONS */
261: /// skipped "Missing default encoding " + defaultEncoding
262: /* #endif */
263: );
264: }
265: }
266:
267: /**
268: * Convert a char array to a byte array
269: *
270: * @param buffer The char array buffer
271: * @param offset The offset
272: * @param length The length
273: * @return A new byte array
274: */
275: public static byte[] charToByteArray(char[] buffer, int offset,
276: int length) {
277: try {
278: return charToByteArray(buffer, offset, length,
279: defaultEncoding);
280: } catch (UnsupportedEncodingException x) {
281: throw new RuntimeException(
282: /* #ifdef VERBOSE_EXCEPTIONS */
283: /// skipped "Missing default encoding "+defaultEncoding
284: /* #endif */
285: );
286: }
287: }
288:
289: /*
290: * Cached variables for byteToCharArray
291: */
292: private static String lastReaderEncoding;
293: private static StreamReader lastReader;
294:
295: /**
296: * Convert a byte array to a char array
297: *
298: * @param buffer The byte array buffer
299: * @param offset The offset
300: * @param length The length
301: * @param enc The character encoding
302: * @return A new char array
303: * @exception UnsupportedEncodingException If the encoding is not known
304: */
305: public static synchronized char[] byteToCharArray(byte[] buffer,
306: int offset, int length, String enc)
307: throws UnsupportedEncodingException {
308:
309: if (offset < 0) {
310: throw new IndexOutOfBoundsException(
311: /* #ifdef VERBOSE_EXCEPTIONS */
312: /// skipped Integer.toString(offset)
313: /* #endif */
314: );
315: }
316:
317: if (length < 0) {
318: throw new IndexOutOfBoundsException(
319: /* #ifdef VERBOSE_EXCEPTIONS */
320: /// skipped Integer.toString(length)
321: /* #endif */
322: );
323: }
324:
325: /* Note: offset or length might be near -1>>>1 */
326: if (offset > buffer.length - length) {
327: throw new IndexOutOfBoundsException(
328: /* #ifdef VERBOSE_EXCEPTIONS */
329: /// skipped Integer.toString(offset + length)
330: /* #endif */
331: );
332: }
333:
334: //Because most cases use ISO8859_1 encoding, we can optimize this case.
335: if (enc.compareTo("ISO8859_1") == 0) {
336: char[] value = new char[length];
337: for (int i = 0; i < length; i++) {
338: value[i] = (char) (buffer[i + offset] & 0xff);
339: }
340: return value;
341: }
342:
343: /* If we don't have a cached reader then make one */
344: if (lastReaderEncoding == null
345: || !lastReaderEncoding.equals(enc)) {
346: lastReader = getStreamReaderPrim(enc);
347: lastReaderEncoding = enc;
348: }
349:
350: /* Ask the reader for the size the output will be */
351: int size = lastReader.sizeOf(buffer, offset, length);
352:
353: /* Allocate a buffer of that size */
354: char[] outbuf = new char[size];
355:
356: /* Open the reader on a ByteArrayInputStream */
357: lastReader.open(
358: new ByteArrayInputStream(buffer, offset, length), enc);
359:
360: try {
361: /* Read the input */
362: int numread = lastReader.read(outbuf, 0, size);
363: if (numread < size) {
364: // this may happen only if the last character is truncated
365: // (say, it should be of 3 bytes, but there are only 2).
366: lastReader.read(outbuf, numread, size - numread);
367: }
368: /* Close the reader */
369: lastReader.close();
370: } catch (IOException x) {
371: throw new RuntimeException(
372: /* #ifdef VERBOSE_EXCEPTIONS */
373: /// skipped "IOException reading reader " +x.getMessage()
374: /* #endif */
375: );
376: }
377:
378: /* And return the buffer */
379: return outbuf;
380: }
381:
382: /*
383: * Cached variables for charToByteArray
384: */
385: private static String lastWriterEncoding;
386: private static StreamWriter lastWriter;
387:
388: /**
389: * Convert a char array to a byte array
390: *
391: * @param buffer The char array buffer
392: * @param offset The offset
393: * @param length The length
394: * @param enc The character encoding
395: * @return A new byte array
396: * @exception UnsupportedEncodingException If the encoding is not known
397: */
398: public static synchronized byte[] charToByteArray(char[] buffer,
399: int offset, int length, String enc)
400: throws UnsupportedEncodingException {
401:
402: //Because most cases use ISO8859_1 encoding, we can optimize this case.
403: if (enc.compareTo("ISO8859_1") == 0) {
404: char c;
405: byte[] value = new byte[length];
406: for (int i = 0; i < length; i++) {
407: c = buffer[i + offset];
408: value[i] = (c <= 255) ? (byte) c : (byte) '?';
409: }
410: return value;
411: }
412:
413: /* If we don't have a cached writer then make one */
414: if (lastWriterEncoding == null
415: || !lastWriterEncoding.equals(enc)) {
416: lastWriter = getStreamWriterPrim(enc);
417: lastWriterEncoding = enc;
418: }
419:
420: /* Ask the writer for the size the output will be */
421: int size = lastWriter.sizeOf(buffer, offset, length);
422:
423: /* Get the output stream */
424: ByteArrayOutputStream os = new ByteArrayOutputStream(size);
425:
426: /* Open the writer */
427: lastWriter.open(os, enc);
428:
429: try {
430: /* Convert */
431: lastWriter.write(buffer, offset, length);
432: /* Close the writer */
433: lastWriter.close();
434: } catch (IOException x) {
435: throw new RuntimeException(
436: /* #ifdef VERBOSE_EXCEPTIONS */
437: /// skipped "IOException writing writer "
438: /// skipped +x.getMessage()
439: /* #endif */
440: );
441: }
442:
443: /* Close the output stream */
444: try {
445: os.close();
446: } catch (IOException x) {
447: }
448: ;
449:
450: /* Return the array */
451: return os.toByteArray();
452: }
453:
454: /**
455: * Get the internal name for an encoding.
456: *
457: * @param encodingName encoding name
458: *
459: * @return internal name for this encoding
460: */
461: private static String internalNameForEncoding(String encodingName) {
462: String internalName;
463: String property;
464:
465: internalName = normalizeEncodingName(encodingName);
466:
467: // The preferred MIME name according to the IANA Charset Registry.
468: if (internalName.equals("US_ASCII")) {
469: /*
470: * US-ASCII is subclass of ISO-8859-1 so we do not need a
471: * separate reader for it.
472: */
473: return "ISO8859_1";
474: }
475:
476: // The preferred MIME name according to the IANA Charset Registry.
477: if (internalName.equals("ISO_8859_1")) {
478: return "ISO8859_1";
479: }
480:
481: /*
482: * Since IANA character encoding names can start with a digit
483: * and that some Reader class names that do not match the standard
484: * name, we have a way to configure alternate names for encodings.
485: *
486: * Note: The names must normalized, digits, upper case only with "_"
487: * and "_" substituted for ":" and "-".
488: *
489: * Use the code below only if your system really needs it:
490: *
491: * property = System.getProperty(internalName + "_InternalEncodingName");
492: * if (property != null) {
493: * return property;
494: * }
495: */
496:
497: return internalName;
498: }
499:
500: /**
501: * Converts "-" and ":" in a string to "_" and converts the name
502: * to upper case.
503: * This is needed because the names of IANA character encodings have
504: * characters that are not allowed for java class names and
505: * IANA encoding names are not case sensitive.
506: *
507: * @param encodingName encoding name
508: *
509: * @return normalized name
510: */
511: private static String normalizeEncodingName(String encodingName) {
512: StringBuffer normalizedName;
513: char currentChar;
514:
515: normalizedName = new StringBuffer(encodingName);
516:
517: for (int i = 0; i < normalizedName.length(); i++) {
518: currentChar = normalizedName.charAt(i);
519:
520: if (currentChar == '-' || currentChar == ':') {
521: normalizedName.setCharAt(i, '_');
522: } else {
523: normalizedName.setCharAt(i, Character
524: .toUpperCase(currentChar));
525: }
526: }
527:
528: return normalizedName.toString();
529: }
530: }
|