/*
* Copyright (c) 2001, 2004, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.corba.se.impl.encoding;
import java.util.Map;
import java.util.HashMap;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.charset.UnmappableCharacterException;
import com.sun.corba.se.impl.logging.ORBUtilSystemException;
import com.sun.corba.se.impl.logging.OMGSystemException;
import com.sun.corba.se.spi.logging.CORBALogDomains;
Collection of classes, interfaces, and factory methods for CORBA code set conversion. This is mainly used to shield other code from the sun.io converters which might change, as well as provide some basic translation from conversion to CORBA error exceptions. Some extra work is required here to facilitate the way CORBA says it uses UTF-16 as of the 00-11-03 spec. REVISIT - Since the nio.Charset and nio.Charset.Encoder/Decoder use NIO ByteBuffer and NIO CharBuffer, the interaction and interface between this class and the CDR streams should be looked at more closely for optimizations to avoid unnecessary copying of data between char[] & CharBuffer
and byte[] & ByteBuffer
, especially DirectByteBuffers. /**
* Collection of classes, interfaces, and factory methods for
* CORBA code set conversion.
*
* This is mainly used to shield other code from the sun.io
* converters which might change, as well as provide some basic
* translation from conversion to CORBA error exceptions. Some
* extra work is required here to facilitate the way CORBA
* says it uses UTF-16 as of the 00-11-03 spec.
*
* REVISIT - Since the nio.Charset and nio.Charset.Encoder/Decoder
* use NIO ByteBuffer and NIO CharBuffer, the interaction
* and interface between this class and the CDR streams
* should be looked at more closely for optimizations to
* avoid unnecessary copying of data between
* {@code char[] & CharBuffer} and
* {@code byte[] & ByteBuffer}, especially
* DirectByteBuffers.
*
*/
public class CodeSetConversion
{
Abstraction for char to byte conversion.
Must be used in the proper sequence:
1) convert
2) Optional getNumBytes and/or getAlignment (if necessary)
3) getBytes (see warning)
/**
* Abstraction for char to byte conversion.
*
* Must be used in the proper sequence:
*
* 1) convert
* 2) Optional getNumBytes and/or getAlignment (if necessary)
* 3) getBytes (see warning)
*/
public abstract static class CTBConverter
{
// Perform the conversion of the provided char or String,
// allowing the caller to query for more information
// before writing.
public abstract void convert(char chToConvert);
public abstract void convert(String strToConvert);
// How many bytes resulted from the conversion?
public abstract int getNumBytes();
// What's the maximum number of bytes per character?
public abstract float getMaxBytesPerChar();
public abstract boolean isFixedWidthEncoding();
// What byte boundary should the stream align to before
// calling writeBytes? For instance, a fixed width
// encoding with 2 bytes per char in a stream which
// doesn't encapsulate the char's bytes should align
// on a 2 byte boundary. (Ex: UTF16 in GIOP1.1)
//
// Note: This has no effect on the converted bytes. It
// is just information available to the caller.
public abstract int getAlignment();
// Get the resulting bytes. Warning: You must use getNumBytes()
// to determine the end of the data in the byte array instead
// of array.length! The array may be used internally, so don't
// save references.
public abstract byte[] getBytes();
}
Abstraction for byte to char conversion.
/**
* Abstraction for byte to char conversion.
*/
public abstract static class BTCConverter
{
// In GIOP 1.1, interoperability can only be achieved with
// fixed width encodings like UTF-16. This is because wstrings
// specified how many code points follow rather than specifying
// the length in octets.
public abstract boolean isFixedWidthEncoding();
public abstract int getFixedCharWidth();
// Called after getChars to determine the true size of the
// converted array.
public abstract int getNumChars();
// Perform the conversion using length bytes from the given
// input stream. Warning: You must use getNumChars() to
// determine the correct length of the resulting array.
// The same array may be used internally over multiple
// calls.
public abstract char[] getChars(byte[] bytes, int offset, int length);
}
Implementation of CTBConverter which uses a nio.Charset.CharsetEncoder
to do the real work. Handles translation of exceptions to the
appropriate CORBA versions.
/**
* Implementation of CTBConverter which uses a nio.Charset.CharsetEncoder
* to do the real work. Handles translation of exceptions to the
* appropriate CORBA versions.
*/
private class JavaCTBConverter extends CTBConverter
{
private ORBUtilSystemException wrapper = ORBUtilSystemException.get(
CORBALogDomains.RPC_ENCODING ) ;
private OMGSystemException omgWrapper = OMGSystemException.get(
CORBALogDomains.RPC_ENCODING ) ;
// nio.Charset.CharsetEncoder actually does the work here
// have to use it directly rather than through String's interface
// because we want to know when errors occur during the conversion.
private CharsetEncoder ctb;
// Proper alignment for this type of converter. For instance,
// ASCII has alignment of 1 (1 byte per char) but UTF16 has
// alignment of 2 (2 bytes per char)
private int alignment;
// Char buffer to hold the input.
private char[] chars = null;
// How many bytes are generated from the conversion?
private int numBytes = 0;
// How many characters were converted (temporary variable
// for cross method communication)
private int numChars = 0;
// ByteBuffer holding the converted input. This is necessary
// since we have to do calculations that require the conversion
// before writing the array to the stream.
private ByteBuffer buffer;
// What code set are we using?
private OSFCodeSetRegistry.Entry codeset;
public JavaCTBConverter(OSFCodeSetRegistry.Entry codeset,
int alignmentForEncoding) {
try {
ctb = cache.getCharToByteConverter(codeset.getName());
if (ctb == null) {
Charset tmpCharset = Charset.forName(codeset.getName());
ctb = tmpCharset.newEncoder();
cache.setConverter(codeset.getName(), ctb);
}
} catch(IllegalCharsetNameException icne) {
// This can only happen if one of our Entries has
// an invalid name.
throw wrapper.invalidCtbConverterName(icne,codeset.getName());
} catch(UnsupportedCharsetException ucne) {
// This can only happen if one of our Entries has
// an unsupported name.
throw wrapper.invalidCtbConverterName(ucne,codeset.getName());
}
this.codeset = codeset;
alignment = alignmentForEncoding;
}
public final float getMaxBytesPerChar() {
return ctb.maxBytesPerChar();
}
public void convert(char chToConvert) {
if (chars == null)
chars = new char[1];
// The CharToByteConverter only takes a char[]
chars[0] = chToConvert;
numChars = 1;
convertCharArray();
}
public void convert(String strToConvert) {
// Try to save a memory allocation if possible. Usual
// space/time trade off. If we could get the char[] out of
// the String without copying, that would be great, but
// it's forbidden since String is immutable.
if (chars == null || chars.length < strToConvert.length())
chars = new char[strToConvert.length()];
numChars = strToConvert.length();
strToConvert.getChars(0, numChars, chars, 0);
convertCharArray();
}
public final int getNumBytes() {
return numBytes;
}
public final int getAlignment() {
return alignment;
}
public final boolean isFixedWidthEncoding() {
return codeset.isFixedWidth();
}
public byte[] getBytes() {
// Note that you can't use buffer.length since the buffer might
// be larger than the actual number of converted bytes depending
// on the encoding.
return buffer.array();
}
private void convertCharArray() {
try {
// Possible optimization of directly converting into the CDR buffer.
// However, that means the CDR code would have to reserve
// a 4 byte string length ahead of time, and we'd need a
// confusing partial conversion scheme for when we couldn't
// fit everything in the buffer but needed to know the
// converted length before proceeding due to fragmentation.
// Then there's the issue of the chunking code.
//
// For right now, this is less messy and basic tests don't
// show more than a 1 ms penalty worst case. Less than a
// factor of 2 increase.
// Convert the characters
buffer = ctb.encode(CharBuffer.wrap(chars,0,numChars));
// ByteBuffer returned by the encoder will set its limit
// to byte immediately after the last written byte.
numBytes = buffer.limit();
} catch (IllegalStateException ise) {
// an encoding operation is already in progress
throw wrapper.ctbConverterFailure( ise ) ;
} catch (MalformedInputException mie) {
// There were illegal Unicode char pairs
throw wrapper.badUnicodePair( mie ) ;
} catch (UnmappableCharacterException uce) {
// A character doesn't map to the desired code set
// CORBA formal 00-11-03.
throw omgWrapper.charNotInCodeset( uce ) ;
} catch (CharacterCodingException cce) {
// If this happens, then some other encoding error occured
throw wrapper.ctbConverterFailure( cce ) ;
}
}
}
Special UTF16 converter which can either always write a BOM
or use a specified byte order without one.
/**
* Special UTF16 converter which can either always write a BOM
* or use a specified byte order without one.
*/
private class UTF16CTBConverter extends JavaCTBConverter
{
// Using this constructor, we will always write a BOM
public UTF16CTBConverter() {
super(OSFCodeSetRegistry.UTF_16, 2);
}
// Using this constructor, we don't use a BOM and use the
// byte order specified
public UTF16CTBConverter(boolean littleEndian) {
super(littleEndian ?
OSFCodeSetRegistry.UTF_16LE :
OSFCodeSetRegistry.UTF_16BE,
2);
}
}
Implementation of BTCConverter which uses a sun.io.ByteToCharConverter
for the real work. Handles translation of exceptions to the
appropriate CORBA versions.
/**
* Implementation of BTCConverter which uses a sun.io.ByteToCharConverter
* for the real work. Handles translation of exceptions to the
* appropriate CORBA versions.
*/
private class JavaBTCConverter extends BTCConverter
{
private ORBUtilSystemException wrapper = ORBUtilSystemException.get(
CORBALogDomains.RPC_ENCODING ) ;
private OMGSystemException omgWrapper = OMGSystemException.get(
CORBALogDomains.RPC_ENCODING ) ;
protected CharsetDecoder btc;
private char[] buffer;
private int resultingNumChars;
private OSFCodeSetRegistry.Entry codeset;
public JavaBTCConverter(OSFCodeSetRegistry.Entry codeset) {
// Obtain a Decoder
btc = this.getConverter(codeset.getName());
this.codeset = codeset;
}
public final boolean isFixedWidthEncoding() {
return codeset.isFixedWidth();
}
// Should only be called if isFixedWidthEncoding is true
// IMPORTANT: This calls OSFCodeSetRegistry.Entry, not
// CharsetDecoder.maxCharsPerByte().
public final int getFixedCharWidth() {
return codeset.getMaxBytesPerChar();
}
public final int getNumChars() {
return resultingNumChars;
}
public char[] getChars(byte[] bytes, int offset, int numBytes) {
// Possible optimization of reading directly from the CDR
// byte buffer. The sun.io converter supposedly can handle
// incremental conversions in which a char is broken across
// two convert calls.
//
// Basic tests didn't show more than a 1 ms increase
// worst case. It's less than a factor of 2 increase.
// Also makes the interface more difficult.
try {
ByteBuffer byteBuf = ByteBuffer.wrap(bytes, offset, numBytes);
CharBuffer charBuf = btc.decode(byteBuf);
// CharBuffer returned by the decoder will set its limit
// to byte immediately after the last written byte.
resultingNumChars = charBuf.limit();
// IMPORTANT - It's possible the underlying char[] in the
// CharBuffer returned by btc.decode(byteBuf)
// is longer in length than the number of characters
// decoded. Hence, the check below to ensure the
// char[] returned contains all the chars that have
// been decoded and no more.
if (charBuf.limit() == charBuf.capacity()) {
buffer = charBuf.array();
} else {
buffer = new char[charBuf.limit()];
charBuf.get(buffer, 0, charBuf.limit()).position(0);
}
return buffer;
} catch (IllegalStateException ile) {
// There were a decoding operation already in progress
throw wrapper.btcConverterFailure( ile ) ;
} catch (MalformedInputException mie) {
// There were illegal Unicode char pairs
throw wrapper.badUnicodePair( mie ) ;
} catch (UnmappableCharacterException uce) {
// A character doesn't map to the desired code set.
// CORBA formal 00-11-03.
throw omgWrapper.charNotInCodeset( uce ) ;
} catch (CharacterCodingException cce) {
// If this happens, then a character decoding error occured.
throw wrapper.btcConverterFailure( cce ) ;
}
}
Utility method to find a CharsetDecoder in the
cache or create a new one if necessary. Throws an
INTERNAL if the code set is unknown.
/**
* Utility method to find a CharsetDecoder in the
* cache or create a new one if necessary. Throws an
* INTERNAL if the code set is unknown.
*/
protected CharsetDecoder getConverter(String javaCodeSetName) {
CharsetDecoder result = null;
try {
result = cache.getByteToCharConverter(javaCodeSetName);
if (result == null) {
Charset tmpCharset = Charset.forName(javaCodeSetName);
result = tmpCharset.newDecoder();
cache.setConverter(javaCodeSetName, result);
}
} catch(IllegalCharsetNameException icne) {
// This can only happen if one of our charset entries has
// an illegal name.
throw wrapper.invalidBtcConverterName( icne, javaCodeSetName ) ;
}
return result;
}
}
Special converter for UTF16 since it's required to optionally
support a byte order marker while the internal Java converters
either require it or require that it isn't there.
The solution is to check for the byte order marker, and if we
need to do something differently, switch internal converters.
/**
* Special converter for UTF16 since it's required to optionally
* support a byte order marker while the internal Java converters
* either require it or require that it isn't there.
*
* The solution is to check for the byte order marker, and if we
* need to do something differently, switch internal converters.
*/
private class UTF16BTCConverter extends JavaBTCConverter
{
private boolean defaultToLittleEndian;
private boolean converterUsesBOM = true;
private static final char UTF16_BE_MARKER = (char) 0xfeff;
private static final char UTF16_LE_MARKER = (char) 0xfffe;
// When there isn't a byte order marker, used the byte
// order specified.
public UTF16BTCConverter(boolean defaultToLittleEndian) {
super(OSFCodeSetRegistry.UTF_16);
this.defaultToLittleEndian = defaultToLittleEndian;
}
public char[] getChars(byte[] bytes, int offset, int numBytes) {
if (hasUTF16ByteOrderMarker(bytes, offset, numBytes)) {
if (!converterUsesBOM)
switchToConverter(OSFCodeSetRegistry.UTF_16);
converterUsesBOM = true;
return super.getChars(bytes, offset, numBytes);
} else {
if (converterUsesBOM) {
if (defaultToLittleEndian)
switchToConverter(OSFCodeSetRegistry.UTF_16LE);
else
switchToConverter(OSFCodeSetRegistry.UTF_16BE);
converterUsesBOM = false;
}
return super.getChars(bytes, offset, numBytes);
}
}
Utility method for determining if a UTF-16 byte order marker is present.
/**
* Utility method for determining if a UTF-16 byte order marker is present.
*/
private boolean hasUTF16ByteOrderMarker(byte[] array, int offset, int length) {
// If there aren't enough bytes to represent the marker and data,
// return false.
if (length >= 4) {
int b1 = array[offset] & 0x00FF;
int b2 = array[offset + 1] & 0x00FF;
char marker = (char)((b1 << 8) | (b2 << 0));
return (marker == UTF16_BE_MARKER || marker == UTF16_LE_MARKER);
} else
return false;
}
The current solution for dealing with UTF-16 in CORBA
is that if our sun.io converter requires byte order markers,
and then we see a CORBA wstring/wchar without them, we
switch to the sun.io converter that doesn't require them.
/**
* The current solution for dealing with UTF-16 in CORBA
* is that if our sun.io converter requires byte order markers,
* and then we see a CORBA wstring/wchar without them, we
* switch to the sun.io converter that doesn't require them.
*/
private void switchToConverter(OSFCodeSetRegistry.Entry newCodeSet) {
// Use the getConverter method from our superclass.
btc = super.getConverter(newCodeSet.getName());
}
}
CTB converter factory for single byte or variable length encodings.
/**
* CTB converter factory for single byte or variable length encodings.
*/
public CTBConverter getCTBConverter(OSFCodeSetRegistry.Entry codeset) {
int alignment = (!codeset.isFixedWidth() ?
1 :
codeset.getMaxBytesPerChar());
return new JavaCTBConverter(codeset, alignment);
}
CTB converter factory for multibyte (mainly fixed) encodings.
Because of the awkwardness with byte order markers and the possibility of
using UCS-2, you must specify both the endianness of the stream as well as
whether or not to use byte order markers if applicable. UCS-2 has no byte
order markers. UTF-16 has optional markers.
If you select useByteOrderMarkers, there is no guarantee that the encoding
will use the endianness specified.
/**
* CTB converter factory for multibyte (mainly fixed) encodings.
*
* Because of the awkwardness with byte order markers and the possibility of
* using UCS-2, you must specify both the endianness of the stream as well as
* whether or not to use byte order markers if applicable. UCS-2 has no byte
* order markers. UTF-16 has optional markers.
*
* If you select useByteOrderMarkers, there is no guarantee that the encoding
* will use the endianness specified.
*
*/
public CTBConverter getCTBConverter(OSFCodeSetRegistry.Entry codeset,
boolean littleEndian,
boolean useByteOrderMarkers) {
// UCS2 doesn't have byte order markers, and we're encoding it
// as UTF-16 since UCS2 isn't available in all Java platforms.
// They should be identical with only minor differences in
// negative cases.
if (codeset == OSFCodeSetRegistry.UCS_2)
return new UTF16CTBConverter(littleEndian);
// We can write UTF-16 with or without a byte order marker.
if (codeset == OSFCodeSetRegistry.UTF_16) {
if (useByteOrderMarkers)
return new UTF16CTBConverter();
else
return new UTF16CTBConverter(littleEndian);
}
// Everything else uses the generic JavaCTBConverter.
//
// Variable width encodings are aligned on 1 byte boundaries.
// A fixed width encoding with a max. of 4 bytes/char should
// align on a 4 byte boundary. Note that UTF-16 is a special
// case because of the optional byte order marker, so it's
// handled above.
//
// This doesn't matter for GIOP 1.2 wchars and wstrings
// since the encoded bytes are treated as an encapsulation.
int alignment = (!codeset.isFixedWidth() ?
1 :
codeset.getMaxBytesPerChar());
return new JavaCTBConverter(codeset, alignment);
}
BTCConverter factory for single byte or variable width encodings.
/**
* BTCConverter factory for single byte or variable width encodings.
*/
public BTCConverter getBTCConverter(OSFCodeSetRegistry.Entry codeset) {
return new JavaBTCConverter(codeset);
}
BTCConverter factory for fixed width multibyte encodings.
/**
* BTCConverter factory for fixed width multibyte encodings.
*/
public BTCConverter getBTCConverter(OSFCodeSetRegistry.Entry codeset,
boolean defaultToLittleEndian) {
if (codeset == OSFCodeSetRegistry.UTF_16 ||
codeset == OSFCodeSetRegistry.UCS_2) {
return new UTF16BTCConverter(defaultToLittleEndian);
} else {
return new JavaBTCConverter(codeset);
}
}
Follows the code set negotiation algorithm in CORBA formal 99-10-07 13.7.2.
Returns the proper negotiated OSF character encoding number or
CodeSetConversion.FALLBACK_CODESET.
/**
* Follows the code set negotiation algorithm in CORBA formal 99-10-07 13.7.2.
*
* Returns the proper negotiated OSF character encoding number or
* CodeSetConversion.FALLBACK_CODESET.
*/
private int selectEncoding(CodeSetComponentInfo.CodeSetComponent client,
CodeSetComponentInfo.CodeSetComponent server) {
// A "null" value for the server's nativeCodeSet means that
// the server desired not to indicate one. We'll take that
// to mean that it wants the first thing in its conversion list.
// If it's conversion list is empty, too, then use the fallback
// codeset.
int serverNative = server.nativeCodeSet;
if (serverNative == 0) {
if (server.conversionCodeSets.length > 0)
serverNative = server.conversionCodeSets[0];
else
return CodeSetConversion.FALLBACK_CODESET;
}
if (client.nativeCodeSet == serverNative) {
// Best case -- client and server don't have to convert
return serverNative;
}
// Is this client capable of converting to the server's
// native code set?
for (int i = 0; i < client.conversionCodeSets.length; i++) {
if (serverNative == client.conversionCodeSets[i]) {
// The client will convert to the server's
// native code set.
return serverNative;
}
}
// Is the server capable of converting to the client's
// native code set?
for (int i = 0; i < server.conversionCodeSets.length; i++) {
if (client.nativeCodeSet == server.conversionCodeSets[i]) {
// The server will convert to the client's
// native code set.
return client.nativeCodeSet;
}
}
// See if there are any code sets that both the server and client
// support (giving preference to the server). The order
// of conversion sets is from most to least desired.
for (int i = 0; i < server.conversionCodeSets.length; i++) {
for (int y = 0; y < client.conversionCodeSets.length; y++) {
if (server.conversionCodeSets[i] == client.conversionCodeSets[y]) {
return server.conversionCodeSets[i];
}
}
}
// Before using the fallback codesets, the spec calls for a
// compatibility check on the native code sets. It doesn't make
// sense because loss free communication is always possible with
// UTF8 and UTF16, the fall back code sets. It's also a lot
// of work to implement. In the case of incompatibility, the
// spec says to throw a CODESET_INCOMPATIBLE exception.
// Use the fallback
return CodeSetConversion.FALLBACK_CODESET;
}
Perform the code set negotiation algorithm and come up with
the two encodings to use.
/**
* Perform the code set negotiation algorithm and come up with
* the two encodings to use.
*/
public CodeSetComponentInfo.CodeSetContext negotiate(CodeSetComponentInfo client,
CodeSetComponentInfo server) {
int charData
= selectEncoding(client.getCharComponent(),
server.getCharComponent());
if (charData == CodeSetConversion.FALLBACK_CODESET) {
charData = OSFCodeSetRegistry.UTF_8.getNumber();
}
int wcharData
= selectEncoding(client.getWCharComponent(),
server.getWCharComponent());
if (wcharData == CodeSetConversion.FALLBACK_CODESET) {
wcharData = OSFCodeSetRegistry.UTF_16.getNumber();
}
return new CodeSetComponentInfo.CodeSetContext(charData,
wcharData);
}
// No one should instantiate a CodeSetConversion but the singleton
// instance method
private CodeSetConversion() {}
// initialize-on-demand holder
private static class CodeSetConversionHolder {
static final CodeSetConversion csc = new CodeSetConversion() ;
}
CodeSetConversion is a singleton, and this is the access point.
/**
* CodeSetConversion is a singleton, and this is the access point.
*/
public final static CodeSetConversion impl() {
return CodeSetConversionHolder.csc ;
}
// Singleton instance
private static CodeSetConversion implementation;
// Number used internally to indicate the fallback code
// set.
private static final int FALLBACK_CODESET = 0;
// Provides a thread local cache for the sun.io
// converters.
private CodeSetCache cache = new CodeSetCache();
}