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 }