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 }