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