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 */ 25 package com.sun.corba.se.impl.encoding; 26 27 import java.util.Map; 28 import java.util.HashMap; 29 import java.nio.ByteBuffer; 30 import java.nio.CharBuffer; 31 import java.nio.charset.Charset; 32 import java.nio.charset.CharsetEncoder; 33 import java.nio.charset.CharsetDecoder; 34 import java.nio.charset.CharacterCodingException; 35 import java.nio.charset.IllegalCharsetNameException; 36 import java.nio.charset.MalformedInputException; 37 import java.nio.charset.UnsupportedCharsetException; 38 import java.nio.charset.UnmappableCharacterException; 39 import com.sun.corba.se.impl.logging.ORBUtilSystemException; 40 import com.sun.corba.se.impl.logging.OMGSystemException; 41 import 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 */ 62 public 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 }