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 58 * {@code char[] & CharBuffer} and 59 * {@code byte[] & ByteBuffer}, especially 60 * DirectByteBuffers. 61 * 62 */ 63 public 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 }