CodeSetConversion.java revision 672:2bb058ce572e
1/*
2 * Copyright (c) 2001, 2004, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package com.sun.corba.se.impl.encoding;
26
27import java.util.Map;
28import java.util.HashMap;
29import java.nio.ByteBuffer;
30import java.nio.CharBuffer;
31import java.nio.charset.Charset;
32import java.nio.charset.CharsetEncoder;
33import java.nio.charset.CharsetDecoder;
34import java.nio.charset.CharacterCodingException;
35import java.nio.charset.IllegalCharsetNameException;
36import java.nio.charset.MalformedInputException;
37import java.nio.charset.UnsupportedCharsetException;
38import java.nio.charset.UnmappableCharacterException;
39import com.sun.corba.se.impl.logging.ORBUtilSystemException;
40import com.sun.corba.se.impl.logging.OMGSystemException;
41import com.sun.corba.se.spi.logging.CORBALogDomains;
42
43/**
44 * Collection of classes, interfaces, and factory methods for
45 * CORBA code set conversion.
46 *
47 * This is mainly used to shield other code from the sun.io
48 * converters which might change, as well as provide some basic
49 * translation from conversion to CORBA error exceptions.  Some
50 * extra work is required here to facilitate the way CORBA
51 * says it uses UTF-16 as of the 00-11-03 spec.
52 *
53 * REVISIT - Since the nio.Charset and nio.Charset.Encoder/Decoder
54 *           use NIO ByteBuffer and NIO CharBuffer, the interaction
55 *           and interface between this class and the CDR streams
56 *           should be looked at more closely for optimizations to
57 *           avoid unnecessary copying of data between
58 *           {@code char[] & CharBuffer} and
59 *           {@code byte[] & ByteBuffer}, especially
60 *           DirectByteBuffers.
61 *
62 */
63public class CodeSetConversion
64{
65    /**
66     * Abstraction for char to byte conversion.
67     *
68     * Must be used in the proper sequence:
69     *
70     * 1)  convert
71     * 2)  Optional getNumBytes and/or getAlignment (if necessary)
72     * 3)  getBytes (see warning)
73     */
74    public abstract static class CTBConverter
75    {
76        // Perform the conversion of the provided char or String,
77        // allowing the caller to query for more information
78        // before writing.
79        public abstract void convert(char chToConvert);
80        public abstract void convert(String strToConvert);
81
82        // How many bytes resulted from the conversion?
83        public abstract int getNumBytes();
84
85        // What's the maximum number of bytes per character?
86        public abstract float getMaxBytesPerChar();
87
88        public abstract boolean isFixedWidthEncoding();
89
90        // What byte boundary should the stream align to before
91        // calling writeBytes?  For instance, a fixed width
92        // encoding with 2 bytes per char in a stream which
93        // doesn't encapsulate the char's bytes should align
94        // on a 2 byte boundary.  (Ex:  UTF16 in GIOP1.1)
95        //
96        // Note: This has no effect on the converted bytes.  It
97        // is just information available to the caller.
98        public abstract int getAlignment();
99
100        // Get the resulting bytes.  Warning:  You must use getNumBytes()
101        // to determine the end of the data in the byte array instead
102        // of array.length!  The array may be used internally, so don't
103        // save references.
104        public abstract byte[] getBytes();
105    }
106
107    /**
108     * Abstraction for byte to char conversion.
109     */
110    public abstract static class BTCConverter
111    {
112        // In GIOP 1.1, interoperability can only be achieved with
113        // fixed width encodings like UTF-16.  This is because wstrings
114        // specified how many code points follow rather than specifying
115        // the length in octets.
116        public abstract boolean isFixedWidthEncoding();
117        public abstract int getFixedCharWidth();
118
119        // Called after getChars to determine the true size of the
120        // converted array.
121        public abstract int getNumChars();
122
123        // Perform the conversion using length bytes from the given
124        // input stream.  Warning:  You must use getNumChars() to
125        // determine the correct length of the resulting array.
126        // The same array may be used internally over multiple
127        // calls.
128        public abstract char[] getChars(byte[] bytes, int offset, int length);
129    }
130
131    /**
132     * Implementation of CTBConverter which uses a nio.Charset.CharsetEncoder
133     * to do the real work.  Handles translation of exceptions to the
134     * appropriate CORBA versions.
135     */
136    private class JavaCTBConverter extends CTBConverter
137    {
138        private ORBUtilSystemException wrapper = ORBUtilSystemException.get(
139            CORBALogDomains.RPC_ENCODING ) ;
140
141        private OMGSystemException omgWrapper = OMGSystemException.get(
142            CORBALogDomains.RPC_ENCODING ) ;
143
144        // nio.Charset.CharsetEncoder actually does the work here
145        // have to use it directly rather than through String's interface
146        // because we want to know when errors occur during the conversion.
147        private CharsetEncoder ctb;
148
149        // Proper alignment for this type of converter.  For instance,
150        // ASCII has alignment of 1 (1 byte per char) but UTF16 has
151        // alignment of 2 (2 bytes per char)
152        private int alignment;
153
154        // Char buffer to hold the input.
155        private char[] chars = null;
156
157        // How many bytes are generated from the conversion?
158        private int numBytes = 0;
159
160        // How many characters were converted (temporary variable
161        // for cross method communication)
162        private int numChars = 0;
163
164        // ByteBuffer holding the converted input.  This is necessary
165        // since we have to do calculations that require the conversion
166        // before writing the array to the stream.
167        private ByteBuffer buffer;
168
169        // What code set are we using?
170        private OSFCodeSetRegistry.Entry codeset;
171
172        public JavaCTBConverter(OSFCodeSetRegistry.Entry codeset,
173                                int alignmentForEncoding) {
174
175            try {
176                ctb = cache.getCharToByteConverter(codeset.getName());
177                if (ctb == null) {
178                    Charset tmpCharset = Charset.forName(codeset.getName());
179                    ctb = tmpCharset.newEncoder();
180                    cache.setConverter(codeset.getName(), ctb);
181                }
182            } catch(IllegalCharsetNameException icne) {
183
184                // This can only happen if one of our Entries has
185                // an invalid name.
186                throw wrapper.invalidCtbConverterName(icne,codeset.getName());
187            } catch(UnsupportedCharsetException ucne) {
188
189                // This can only happen if one of our Entries has
190                // an unsupported name.
191                throw wrapper.invalidCtbConverterName(ucne,codeset.getName());
192            }
193
194            this.codeset = codeset;
195            alignment = alignmentForEncoding;
196        }
197
198        public final float getMaxBytesPerChar() {
199            return ctb.maxBytesPerChar();
200        }
201
202        public void convert(char chToConvert) {
203            if (chars == null)
204                chars = new char[1];
205
206            // The CharToByteConverter only takes a char[]
207            chars[0] = chToConvert;
208            numChars = 1;
209
210            convertCharArray();
211        }
212
213        public void convert(String strToConvert) {
214            // Try to save a memory allocation if possible.  Usual
215            // space/time trade off.  If we could get the char[] out of
216            // the String without copying, that would be great, but
217            // it's forbidden since String is immutable.
218            if (chars == null || chars.length < strToConvert.length())
219                chars = new char[strToConvert.length()];
220
221            numChars = strToConvert.length();
222
223            strToConvert.getChars(0, numChars, chars, 0);
224
225            convertCharArray();
226        }
227
228        public final int getNumBytes() {
229            return numBytes;
230        }
231
232        public final int getAlignment() {
233            return alignment;
234        }
235
236        public final boolean isFixedWidthEncoding() {
237            return codeset.isFixedWidth();
238        }
239
240        public byte[] getBytes() {
241            // Note that you can't use buffer.length since the buffer might
242            // be larger than the actual number of converted bytes depending
243            // on the encoding.
244            return buffer.array();
245        }
246
247        private void convertCharArray() {
248            try {
249
250                // Possible optimization of directly converting into the CDR buffer.
251                // However, that means the CDR code would have to reserve
252                // a 4 byte string length ahead of time, and we'd need a
253                // confusing partial conversion scheme for when we couldn't
254                // fit everything in the buffer but needed to know the
255                // converted length before proceeding due to fragmentation.
256                // Then there's the issue of the chunking code.
257                //
258                // For right now, this is less messy and basic tests don't
259                // show more than a 1 ms penalty worst case.  Less than a
260                // factor of 2 increase.
261
262                // Convert the characters
263                buffer = ctb.encode(CharBuffer.wrap(chars,0,numChars));
264
265                // ByteBuffer returned by the encoder will set its limit
266                // to byte immediately after the last written byte.
267                numBytes = buffer.limit();
268
269            } catch (IllegalStateException ise) {
270                // an encoding operation is already in progress
271                throw wrapper.ctbConverterFailure( ise ) ;
272            } catch (MalformedInputException mie) {
273                // There were illegal Unicode char pairs
274                throw wrapper.badUnicodePair( mie ) ;
275            } catch (UnmappableCharacterException uce) {
276                // A character doesn't map to the desired code set
277                // CORBA formal 00-11-03.
278                throw omgWrapper.charNotInCodeset( uce ) ;
279            } catch (CharacterCodingException cce) {
280                // If this happens, then some other encoding error occured
281                throw wrapper.ctbConverterFailure( cce ) ;
282            }
283        }
284    }
285
286    /**
287     * Special UTF16 converter which can either always write a BOM
288     * or use a specified byte order without one.
289     */
290    private class UTF16CTBConverter extends JavaCTBConverter
291    {
292        // Using this constructor, we will always write a BOM
293        public UTF16CTBConverter() {
294            super(OSFCodeSetRegistry.UTF_16, 2);
295        }
296
297        // Using this constructor, we don't use a BOM and use the
298        // byte order specified
299        public UTF16CTBConverter(boolean littleEndian) {
300            super(littleEndian ?
301                  OSFCodeSetRegistry.UTF_16LE :
302                  OSFCodeSetRegistry.UTF_16BE,
303                  2);
304        }
305    }
306
307    /**
308     * Implementation of BTCConverter which uses a sun.io.ByteToCharConverter
309     * for the real work.  Handles translation of exceptions to the
310     * appropriate CORBA versions.
311     */
312    private class JavaBTCConverter extends BTCConverter
313    {
314        private ORBUtilSystemException wrapper = ORBUtilSystemException.get(
315            CORBALogDomains.RPC_ENCODING ) ;
316
317        private OMGSystemException omgWrapper = OMGSystemException.get(
318            CORBALogDomains.RPC_ENCODING ) ;
319
320        protected CharsetDecoder btc;
321        private char[] buffer;
322        private int resultingNumChars;
323        private OSFCodeSetRegistry.Entry codeset;
324
325        public JavaBTCConverter(OSFCodeSetRegistry.Entry codeset) {
326
327            // Obtain a Decoder
328            btc = this.getConverter(codeset.getName());
329
330            this.codeset = codeset;
331        }
332
333        public final boolean isFixedWidthEncoding() {
334            return codeset.isFixedWidth();
335        }
336
337        // Should only be called if isFixedWidthEncoding is true
338        // IMPORTANT: This calls OSFCodeSetRegistry.Entry, not
339        //            CharsetDecoder.maxCharsPerByte().
340        public final int getFixedCharWidth() {
341            return codeset.getMaxBytesPerChar();
342        }
343
344        public final int getNumChars() {
345            return resultingNumChars;
346        }
347
348        public char[] getChars(byte[] bytes, int offset, int numBytes) {
349
350            // Possible optimization of reading directly from the CDR
351            // byte buffer.  The sun.io converter supposedly can handle
352            // incremental conversions in which a char is broken across
353            // two convert calls.
354            //
355            // Basic tests didn't show more than a 1 ms increase
356            // worst case.  It's less than a factor of 2 increase.
357            // Also makes the interface more difficult.
358
359
360            try {
361
362                ByteBuffer byteBuf = ByteBuffer.wrap(bytes, offset, numBytes);
363                CharBuffer charBuf = btc.decode(byteBuf);
364
365                // CharBuffer returned by the decoder will set its limit
366                // to byte immediately after the last written byte.
367                resultingNumChars = charBuf.limit();
368
369                // IMPORTANT - It's possible the underlying char[] in the
370                //             CharBuffer returned by btc.decode(byteBuf)
371                //             is longer in length than the number of characters
372                //             decoded. Hence, the check below to ensure the
373                //             char[] returned contains all the chars that have
374                //             been decoded and no more.
375                if (charBuf.limit() == charBuf.capacity()) {
376                    buffer = charBuf.array();
377                } else {
378                    buffer = new char[charBuf.limit()];
379                    charBuf.get(buffer, 0, charBuf.limit()).position(0);
380                }
381
382                return buffer;
383
384            } catch (IllegalStateException ile) {
385                // There were a decoding operation already in progress
386                throw wrapper.btcConverterFailure( ile ) ;
387            } catch (MalformedInputException mie) {
388                // There were illegal Unicode char pairs
389                throw wrapper.badUnicodePair( mie ) ;
390            } catch (UnmappableCharacterException uce) {
391                // A character doesn't map to the desired code set.
392                // CORBA formal 00-11-03.
393                throw omgWrapper.charNotInCodeset( uce ) ;
394            } catch (CharacterCodingException cce) {
395                // If this happens, then a character decoding error occured.
396                throw wrapper.btcConverterFailure( cce ) ;
397            }
398        }
399
400        /**
401         * Utility method to find a CharsetDecoder in the
402         * cache or create a new one if necessary.  Throws an
403         * INTERNAL if the code set is unknown.
404         */
405        protected CharsetDecoder getConverter(String javaCodeSetName) {
406
407            CharsetDecoder result = null;
408            try {
409                result = cache.getByteToCharConverter(javaCodeSetName);
410
411                if (result == null) {
412                    Charset tmpCharset = Charset.forName(javaCodeSetName);
413                    result = tmpCharset.newDecoder();
414                    cache.setConverter(javaCodeSetName, result);
415                }
416
417            } catch(IllegalCharsetNameException icne) {
418                // This can only happen if one of our charset entries has
419                // an illegal name.
420                throw wrapper.invalidBtcConverterName( icne, javaCodeSetName ) ;
421            }
422
423            return result;
424        }
425    }
426
427    /**
428     * Special converter for UTF16 since it's required to optionally
429     * support a byte order marker while the internal Java converters
430     * either require it or require that it isn't there.
431     *
432     * The solution is to check for the byte order marker, and if we
433     * need to do something differently, switch internal converters.
434     */
435    private class UTF16BTCConverter extends JavaBTCConverter
436    {
437        private boolean defaultToLittleEndian;
438        private boolean converterUsesBOM = true;
439
440        private static final char UTF16_BE_MARKER = (char) 0xfeff;
441        private static final char UTF16_LE_MARKER = (char) 0xfffe;
442
443        // When there isn't a byte order marker, used the byte
444        // order specified.
445        public UTF16BTCConverter(boolean defaultToLittleEndian) {
446            super(OSFCodeSetRegistry.UTF_16);
447
448            this.defaultToLittleEndian = defaultToLittleEndian;
449        }
450
451        public char[] getChars(byte[] bytes, int offset, int numBytes) {
452
453            if (hasUTF16ByteOrderMarker(bytes, offset, numBytes)) {
454                if (!converterUsesBOM)
455                    switchToConverter(OSFCodeSetRegistry.UTF_16);
456
457                converterUsesBOM = true;
458
459                return super.getChars(bytes, offset, numBytes);
460            } else {
461                if (converterUsesBOM) {
462                    if (defaultToLittleEndian)
463                        switchToConverter(OSFCodeSetRegistry.UTF_16LE);
464                    else
465                        switchToConverter(OSFCodeSetRegistry.UTF_16BE);
466
467                    converterUsesBOM = false;
468                }
469
470                return super.getChars(bytes, offset, numBytes);
471            }
472        }
473
474        /**
475         * Utility method for determining if a UTF-16 byte order marker is present.
476         */
477        private boolean hasUTF16ByteOrderMarker(byte[] array, int offset, int length) {
478            // If there aren't enough bytes to represent the marker and data,
479            // return false.
480            if (length >= 4) {
481
482                int b1 = array[offset] & 0x00FF;
483                int b2 = array[offset + 1] & 0x00FF;
484
485                char marker = (char)((b1 << 8) | (b2 << 0));
486
487                return (marker == UTF16_BE_MARKER || marker == UTF16_LE_MARKER);
488            } else
489                return false;
490        }
491
492        /**
493         * The current solution for dealing with UTF-16 in CORBA
494         * is that if our sun.io converter requires byte order markers,
495         * and then we see a CORBA wstring/wchar without them, we
496         * switch to the sun.io converter that doesn't require them.
497         */
498        private void switchToConverter(OSFCodeSetRegistry.Entry newCodeSet) {
499
500            // Use the getConverter method from our superclass.
501            btc = super.getConverter(newCodeSet.getName());
502        }
503    }
504
505    /**
506     * CTB converter factory for single byte or variable length encodings.
507     */
508    public CTBConverter getCTBConverter(OSFCodeSetRegistry.Entry codeset) {
509        int alignment = (!codeset.isFixedWidth() ?
510                         1 :
511                         codeset.getMaxBytesPerChar());
512
513        return new JavaCTBConverter(codeset, alignment);
514    }
515
516    /**
517     * CTB converter factory for multibyte (mainly fixed) encodings.
518     *
519     * Because of the awkwardness with byte order markers and the possibility of
520     * using UCS-2, you must specify both the endianness of the stream as well as
521     * whether or not to use byte order markers if applicable.  UCS-2 has no byte
522     * order markers.  UTF-16 has optional markers.
523     *
524     * If you select useByteOrderMarkers, there is no guarantee that the encoding
525     * will use the endianness specified.
526     *
527     */
528    public CTBConverter getCTBConverter(OSFCodeSetRegistry.Entry codeset,
529                                        boolean littleEndian,
530                                        boolean useByteOrderMarkers) {
531
532        // UCS2 doesn't have byte order markers, and we're encoding it
533        // as UTF-16 since UCS2 isn't available in all Java platforms.
534        // They should be identical with only minor differences in
535        // negative cases.
536        if (codeset == OSFCodeSetRegistry.UCS_2)
537            return new UTF16CTBConverter(littleEndian);
538
539        // We can write UTF-16 with or without a byte order marker.
540        if (codeset == OSFCodeSetRegistry.UTF_16) {
541            if (useByteOrderMarkers)
542                return new UTF16CTBConverter();
543            else
544                return new UTF16CTBConverter(littleEndian);
545        }
546
547        // Everything else uses the generic JavaCTBConverter.
548        //
549        // Variable width encodings are aligned on 1 byte boundaries.
550        // A fixed width encoding with a max. of 4 bytes/char should
551        // align on a 4 byte boundary.  Note that UTF-16 is a special
552        // case because of the optional byte order marker, so it's
553        // handled above.
554        //
555        // This doesn't matter for GIOP 1.2 wchars and wstrings
556        // since the encoded bytes are treated as an encapsulation.
557        int alignment = (!codeset.isFixedWidth() ?
558                         1 :
559                         codeset.getMaxBytesPerChar());
560
561        return new JavaCTBConverter(codeset, alignment);
562    }
563
564    /**
565     * BTCConverter factory for single byte or variable width encodings.
566     */
567    public BTCConverter getBTCConverter(OSFCodeSetRegistry.Entry codeset) {
568        return new JavaBTCConverter(codeset);
569    }
570
571    /**
572     * BTCConverter factory for fixed width multibyte encodings.
573     */
574    public BTCConverter getBTCConverter(OSFCodeSetRegistry.Entry codeset,
575                                        boolean defaultToLittleEndian) {
576
577        if (codeset == OSFCodeSetRegistry.UTF_16 ||
578            codeset == OSFCodeSetRegistry.UCS_2) {
579
580            return new UTF16BTCConverter(defaultToLittleEndian);
581        } else {
582            return new JavaBTCConverter(codeset);
583        }
584    }
585
586    /**
587     * Follows the code set negotiation algorithm in CORBA formal 99-10-07 13.7.2.
588     *
589     * Returns the proper negotiated OSF character encoding number or
590     * CodeSetConversion.FALLBACK_CODESET.
591     */
592    private int selectEncoding(CodeSetComponentInfo.CodeSetComponent client,
593                               CodeSetComponentInfo.CodeSetComponent server) {
594
595        // A "null" value for the server's nativeCodeSet means that
596        // the server desired not to indicate one.  We'll take that
597        // to mean that it wants the first thing in its conversion list.
598        // If it's conversion list is empty, too, then use the fallback
599        // codeset.
600        int serverNative = server.nativeCodeSet;
601
602        if (serverNative == 0) {
603            if (server.conversionCodeSets.length > 0)
604                serverNative = server.conversionCodeSets[0];
605            else
606                return CodeSetConversion.FALLBACK_CODESET;
607        }
608
609        if (client.nativeCodeSet == serverNative) {
610            // Best case -- client and server don't have to convert
611            return serverNative;
612        }
613
614        // Is this client capable of converting to the server's
615        // native code set?
616        for (int i = 0; i < client.conversionCodeSets.length; i++) {
617            if (serverNative == client.conversionCodeSets[i]) {
618                // The client will convert to the server's
619                // native code set.
620                return serverNative;
621            }
622        }
623
624        // Is the server capable of converting to the client's
625        // native code set?
626        for (int i = 0; i < server.conversionCodeSets.length; i++) {
627            if (client.nativeCodeSet == server.conversionCodeSets[i]) {
628                // The server will convert to the client's
629                // native code set.
630                return client.nativeCodeSet;
631            }
632        }
633
634        // See if there are any code sets that both the server and client
635        // support (giving preference to the server).  The order
636        // of conversion sets is from most to least desired.
637        for (int i = 0; i < server.conversionCodeSets.length; i++) {
638            for (int y = 0; y < client.conversionCodeSets.length; y++) {
639                if (server.conversionCodeSets[i] == client.conversionCodeSets[y]) {
640                    return server.conversionCodeSets[i];
641                }
642            }
643        }
644
645        // Before using the fallback codesets, the spec calls for a
646        // compatibility check on the native code sets.  It doesn't make
647        // sense because loss free communication is always possible with
648        // UTF8 and UTF16, the fall back code sets.  It's also a lot
649        // of work to implement.  In the case of incompatibility, the
650        // spec says to throw a CODESET_INCOMPATIBLE exception.
651
652        // Use the fallback
653        return CodeSetConversion.FALLBACK_CODESET;
654    }
655
656    /**
657     * Perform the code set negotiation algorithm and come up with
658     * the two encodings to use.
659     */
660    public CodeSetComponentInfo.CodeSetContext negotiate(CodeSetComponentInfo client,
661                                                         CodeSetComponentInfo server) {
662        int charData
663            = selectEncoding(client.getCharComponent(),
664                             server.getCharComponent());
665
666        if (charData == CodeSetConversion.FALLBACK_CODESET) {
667            charData = OSFCodeSetRegistry.UTF_8.getNumber();
668        }
669
670        int wcharData
671            = selectEncoding(client.getWCharComponent(),
672                             server.getWCharComponent());
673
674        if (wcharData == CodeSetConversion.FALLBACK_CODESET) {
675            wcharData = OSFCodeSetRegistry.UTF_16.getNumber();
676        }
677
678        return new CodeSetComponentInfo.CodeSetContext(charData,
679                                                       wcharData);
680    }
681
682    // No one should instantiate a CodeSetConversion but the singleton
683    // instance method
684    private CodeSetConversion() {}
685
686    // initialize-on-demand holder
687    private static class CodeSetConversionHolder {
688        static final CodeSetConversion csc = new CodeSetConversion() ;
689    }
690
691    /**
692     * CodeSetConversion is a singleton, and this is the access point.
693     */
694    public final static CodeSetConversion impl() {
695        return CodeSetConversionHolder.csc ;
696    }
697
698    // Singleton instance
699    private static CodeSetConversion implementation;
700
701    // Number used internally to indicate the fallback code
702    // set.
703    private static final int FALLBACK_CODESET = 0;
704
705    // Provides a thread local cache for the sun.io
706    // converters.
707    private CodeSetCache cache = new CodeSetCache();
708}
709