You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by ka...@apache.org on 2011/05/20 11:49:47 UTC
svn commit: r1125299 - in /db/derby/code/trunk/java:
client/org/apache/derby/client/net/
testing/org/apache/derbyTesting/functionTests/tests/derbynet/
Author: kahatlen
Date: Fri May 20 09:49:46 2011
New Revision: 1125299
URL: http://svn.apache.org/viewvc?rev=1125299&view=rev
Log:
DERBY-5068: Investigate increased CPU usage on client after introduction of UTF-8 CcsidManager
Make the CcsidManager implementations encode strings directly into the
ByteBuffer instead of going via an intermediate byte array.
Modified:
db/derby/code/trunk/java/client/org/apache/derby/client/net/CcsidManager.java
db/derby/code/trunk/java/client/org/apache/derby/client/net/EbcdicCcsidManager.java
db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnection.java
db/derby/code/trunk/java/client/org/apache/derby/client/net/NetPackageRequest.java
db/derby/code/trunk/java/client/org/apache/derby/client/net/Request.java
db/derby/code/trunk/java/client/org/apache/derby/client/net/Utf8CcsidManager.java
db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/Utf8CcsidManagerClientTest.java
Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/CcsidManager.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/CcsidManager.java?rev=1125299&r1=1125298&r2=1125299&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/CcsidManager.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/CcsidManager.java Fri May 20 09:49:46 2011
@@ -21,6 +21,11 @@
package org.apache.derby.client.net;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import org.apache.derby.client.am.Agent;
+import org.apache.derby.client.am.SqlException;
+
// Performs character conversions as required to send and receive PROTOCOL control data.
// User data uses the JVM's built in converters, i18n.jar,
@@ -57,26 +62,6 @@ public abstract class CcsidManager {
// @return A new byte array representing the String in a particular ccsid.
public abstract byte[] convertFromJavaString(String sourceString, org.apache.derby.client.am.Agent agent) throws org.apache.derby.client.am.SqlException;
-
- // Convert a Java String into bytes for a particular ccsid.
- // The String is converted into a buffer provided by the caller.
- //
- // @param sourceString A Java String to convert.
- // @param buffer The buffer to convert the String into.
- // @param offset Offset in buffer to start putting output.
- // @return An int containing the buffer offset after conversion.
- public abstract int convertFromJavaString(String sourceString,
- byte[] buffer,
- int offset,
- org.apache.derby.client.am.Agent agent) throws org.apache.derby.client.am.SqlException;
-
- // Convert a byte array representing characters in a particular ccsid into a Java String.
- //
- // @param sourceBytes An array of bytes to be converted.
- // @return String A new Java String Object created after conversion.
- abstract String convertToJavaString(byte[] sourceBytes);
-
-
// Convert a byte array representing characters in a particular ccsid into a Java String.
//
// @param sourceBytes An array of bytes to be converted.
@@ -85,19 +70,35 @@ public abstract class CcsidManager {
// @return A new Java String Object created after conversion.
abstract String convertToJavaString(byte[] sourceBytes, int offset, int numToConvert);
-
-
/**
- *
- * @return Maximum number of bytes per character
+ * Initialize this instance for encoding a new string. This method resets
+ * any internal state that may be left after earlier calls to
+ * {@link #encode()} on this instance. For example, it may reset the
+ * internal {@code java.nio.charset.CharsetEncoder}, if the implementation
+ * uses one to do the encoding.
*/
- abstract int maxBytesPerChar();
+ public abstract void startEncoding();
/**
- * Get length in bytes for string s
- * @param s The string from which to obtain the length
- * @return The length of s in bytes
+ * Encode the contents of a {@code CharBuffer} into a {@code ByteBuffer}.
+ * The method will return {@code true} if all the characters were encoded
+ * and copied to the destination. If the receiving byte buffer is too small
+ * to hold the entire encoded representation of the character buffer, the
+ * method will return {@code false}. The caller should then allocate a
+ * larger byte buffer, copy the contents from the old byte buffer to the
+ * new one, and then call this method again to get the remaining characters
+ * encoded.
+ *
+ * @param src buffer holding the characters to encode
+ * @param dest buffer receiving the encoded bytes
+ * @param agent where to report errors
+ * @return {@code true} if all characters were encoded, {@code false} if
+ * the destination buffer is full and there still are more characters to
+ * encode
+ * @throws SqlException if the characters cannot be encoded using this
+ * CCSID manager's character encoding
*/
- abstract int getByteLength(String s);
+ public abstract boolean encode(
+ CharBuffer src, ByteBuffer dest, Agent agent) throws SqlException;
}
Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/EbcdicCcsidManager.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/EbcdicCcsidManager.java?rev=1125299&r1=1125298&r2=1125299&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/EbcdicCcsidManager.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/EbcdicCcsidManager.java Fri May 20 09:49:46 2011
@@ -21,6 +21,9 @@
package org.apache.derby.client.net;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import org.apache.derby.client.am.Agent;
import org.apache.derby.client.am.SqlException;
import org.apache.derby.client.am.ClientMessageId;
import org.apache.derby.shared.common.reference.SQLState;
@@ -127,41 +130,41 @@ public class EbcdicCcsidManager extends
}
public byte[] convertFromJavaString(String sourceString, org.apache.derby.client.am.Agent agent) throws SqlException {
- byte[] bytes = new byte[sourceString.length()];
- convertFromJavaString(sourceString, bytes, 0, agent);
- return bytes;
- }
-
- public int convertFromJavaString(String sourceString,
- byte[] buffer,
- int offset,
- org.apache.derby.client.am.Agent agent) throws SqlException {
- for (int i = 0; i < sourceString.length(); i++) {
- char c = sourceString.charAt(i);
- if (c > 0xff)
- // buffer[offset++] = (byte) 63;
- {
- throw new SqlException(agent.logWriter_,
- new ClientMessageId(SQLState.CANT_CONVERT_UNICODE_TO_EBCDIC));
+ CharBuffer src = CharBuffer.wrap(sourceString);
+ ByteBuffer dest = ByteBuffer.allocate(sourceString.length());
+ startEncoding();
+ encode(src, dest, agent);
+ return dest.array();
+ }
+
+ public void startEncoding() {
+ // We don't have a CharsetEncoder instance to reset, or any other
+ // internal state associated with earlier encode() calls. Do nothing.
+ }
+
+ public boolean encode(CharBuffer src, ByteBuffer dest, Agent agent)
+ throws SqlException {
+ // Encode as many characters as the destination buffer can hold.
+ int charsToEncode = Math.min(src.remaining(), dest.remaining());
+ for (int i = 0; i < charsToEncode; i++) {
+ char c = src.get();
+ if (c > 0xff) {
+ throw new SqlException(agent.logWriter_,
+ new ClientMessageId(
+ SQLState.CANT_CONVERT_UNICODE_TO_EBCDIC));
} else {
- buffer[offset++] = (byte) (conversionArrayToEbcdic[c]);
+ dest.put((byte) conversionArrayToEbcdic[c]);
}
- ;
}
- return offset;
- }
- String convertToJavaString(byte[] sourceBytes) {
- int i = 0;
- char[] theChars = new char[sourceBytes.length];
- int num = 0;
-
- for (i = 0; i < sourceBytes.length; i++) {
- num = (sourceBytes[i] < 0) ? (sourceBytes[i] + 256) : sourceBytes[i];
- theChars[i] = (char) conversionArrayToUCS2[num];
+ if (src.remaining() == 0) {
+ // All characters have been encoded. We're done.
+ return true;
+ } else {
+ // We still have more characters to encode, but no room in
+ // destination buffer.
+ return false;
}
-
- return new String(theChars);
}
String convertToJavaString(byte[] sourceBytes, int offset, int numToConvert) {
@@ -176,18 +179,4 @@ public class EbcdicCcsidManager extends
}
return new String(theChars);
}
-
-
- /* (non-Javadoc)
- * @see org.apache.derby.client.net.CcsidManager#maxBytesPerChar()
- */
- int maxBytesPerChar() {
- return 1;
- }
-
- public int getByteLength(String s) {
- return s.length();
- }
-
}
-
Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnection.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnection.java?rev=1125299&r1=1125298&r2=1125299&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnection.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnection.java Fri May 20 09:49:46 2011
@@ -20,6 +20,8 @@
*/
package org.apache.derby.client.net;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
import java.sql.SQLException;
import org.apache.derby.client.am.CallableStatement;
import org.apache.derby.client.am.DatabaseMetaData;
@@ -38,6 +40,7 @@ import org.apache.derby.jdbc.ClientDrive
import org.apache.derby.client.ClientPooledConnection;
import org.apache.derby.shared.common.reference.SQLState;
+import org.apache.derby.shared.common.sanity.SanityManager;
public class NetConnection extends org.apache.derby.client.am.Connection {
@@ -127,8 +130,7 @@ public class NetConnection extends org.a
// non unicode ccsids. this is done when the server doesn't recoginze the
// unicode ccsids).
//
-
- byte[] prddta_;
+ private ByteBuffer prddta_;
// Correlation Token of the source sent to the server in the accrdb.
// It is saved like the prddta in case it is needed for a connect reflow.
@@ -837,7 +839,7 @@ public class NetConnection extends org.a
netAgent_.netConnectionRequest_.writeAccessDatabase(databaseName_,
false,
crrtkn_,
- prddta_,
+ prddta_.array(),
netAgent_.typdef_);
}
@@ -1321,41 +1323,62 @@ public class NetConnection extends org.a
}
private void constructPrddta() throws SqlException {
- int prddtaLen = 1;
-
if (prddta_ == null) {
- prddta_ = new byte[NetConfiguration.PRDDTA_MAXSIZE];
+ prddta_ = ByteBuffer.allocate(NetConfiguration.PRDDTA_MAXSIZE);
} else {
- java.util.Arrays.fill(prddta_, (byte) 0);
+ prddta_.clear();
+ java.util.Arrays.fill(prddta_.array(), (byte) 0);
}
+ CcsidManager ccsidMgr = netAgent_.getCurrentCcsidManager();
+
for (int i = 0; i < NetConfiguration.PRDDTA_ACCT_SUFFIX_LEN_BYTE; i++) {
- prddta_[i] = netAgent_.getCurrentCcsidManager().space_;
+ prddta_.put(i, ccsidMgr.space_);
}
- prddtaLen = netAgent_.getCurrentCcsidManager().convertFromJavaString(NetConfiguration.PRDID,
- prddta_,
- prddtaLen,
- netAgent_);
-
- prddtaLen = netAgent_.getCurrentCcsidManager().convertFromJavaString(NetConfiguration.PRDDTA_PLATFORM_ID,
- prddta_,
- prddtaLen,
- netAgent_);
+ // Start inserting data right after the length byte.
+ prddta_.position(NetConfiguration.PRDDTA_LEN_BYTE + 1);
+
+ // Register the success of the encode operations for verification in
+ // sane mode.
+ boolean success = true;
+
+ ccsidMgr.startEncoding();
+ success &= ccsidMgr.encode(
+ CharBuffer.wrap(NetConfiguration.PRDID), prddta_, agent_);
+
+ ccsidMgr.startEncoding();
+ success &= ccsidMgr.encode(
+ CharBuffer.wrap(NetConfiguration.PRDDTA_PLATFORM_ID),
+ prddta_, agent_);
+
+ int prddtaLen = prddta_.position();
int extnamTruncateLength = Math.min(extnam_.length(), NetConfiguration.PRDDTA_APPL_ID_FIXED_LEN);
- netAgent_.getCurrentCcsidManager().convertFromJavaString(extnam_.substring(0, extnamTruncateLength),
- prddta_,
- prddtaLen,
- netAgent_);
+ ccsidMgr.startEncoding();
+ success &= ccsidMgr.encode(
+ CharBuffer.wrap(extnam_, 0, extnamTruncateLength),
+ prddta_, agent_);
+
+ if (SanityManager.DEBUG) {
+ // The encode() calls above should all complete without overflow,
+ // since we control the contents of the strings. Verify this in
+ // sane mode so that we notice it if the strings change so that
+ // they go beyond the max size of PRDDTA.
+ SanityManager.ASSERT(success,
+ "PRDID, PRDDTA_PLATFORM_ID and EXTNAM exceeded PRDDTA_MAXSIZE");
+ }
+
prddtaLen += NetConfiguration.PRDDTA_APPL_ID_FIXED_LEN;
prddtaLen += NetConfiguration.PRDDTA_USER_ID_FIXED_LEN;
- prddta_[NetConfiguration.PRDDTA_ACCT_SUFFIX_LEN_BYTE] = 0;
+ // Mark that we have an empty suffix in PRDDTA_ACCT_SUFFIX_LEN_BYTE.
+ prddta_.put(NetConfiguration.PRDDTA_ACCT_SUFFIX_LEN_BYTE, (byte) 0);
prddtaLen++;
+
// the length byte value does not include itself.
- prddta_[NetConfiguration.PRDDTA_LEN_BYTE] = (byte) (prddtaLen - 1);
+ prddta_.put(NetConfiguration.PRDDTA_LEN_BYTE, (byte) (prddtaLen - 1));
}
private void initializePublicKeyForEncryption() throws SqlException {
Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/NetPackageRequest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/NetPackageRequest.java?rev=1125299&r1=1125298&r2=1125299&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/NetPackageRequest.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/NetPackageRequest.java Fri May 20 09:49:46 2011
@@ -57,49 +57,59 @@ public class NetPackageRequest extends N
// Relational Database Name (RDBNAM)
// RDB Package Identifier (PKGID)
int maxIdentifierLength = NetConfiguration.PKG_IDENTIFIER_MAX_LEN;
+ CcsidManager ccsidMgr = netAgent_.getCurrentCcsidManager();
+
+ byte[] dbnameBytes = ccsidMgr.convertFromJavaString(
+ netAgent_.netConnection_.databaseName_, netAgent_);
+
+ byte[] collectionToFlowBytes = ccsidMgr.convertFromJavaString(
+ collectionToFlow, netAgent_);
+
+ byte[] pkgNameBytes = ccsidMgr.convertFromJavaString(
+ section.getPackageName(), netAgent_);
boolean scldtalenRequired = false;
scldtalenRequired = checkPKGNAMlengths(netAgent_.netConnection_.databaseName_,
+ dbnameBytes.length,
maxIdentifierLength,
NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
if (!scldtalenRequired) {
scldtalenRequired = checkPKGNAMlengths(collectionToFlow,
+ collectionToFlowBytes.length,
maxIdentifierLength,
NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
}
if (!scldtalenRequired) {
scldtalenRequired = checkPKGNAMlengths(section.getPackageName(),
+ pkgNameBytes.length,
maxIdentifierLength,
NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
}
// the format is different depending on if an SCLDTALEN is required.
if (!scldtalenRequired) {
- writeScalarPaddedString(netAgent_.netConnection_.databaseName_,
- NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
- writeScalarPaddedString(collectionToFlow,
- NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
- writeScalarPaddedString(section.getPackageName(),
- NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
+ byte padByte = ccsidMgr.space_;
+ writeScalarPaddedBytes(dbnameBytes,
+ NetConfiguration.PKG_IDENTIFIER_FIXED_LEN, padByte);
+ writeScalarPaddedBytes(collectionToFlowBytes,
+ NetConfiguration.PKG_IDENTIFIER_FIXED_LEN, padByte);
+ writeScalarPaddedBytes(pkgNameBytes,
+ NetConfiguration.PKG_IDENTIFIER_FIXED_LEN, padByte);
} else {
- buildSCLDTA(netAgent_.netConnection_.databaseName_, NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
- buildSCLDTA(collectionToFlow, NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
- buildSCLDTA(section.getPackageName(), NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
+ buildSCLDTA(dbnameBytes, NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
+ buildSCLDTA(collectionToFlowBytes, NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
+ buildSCLDTA(pkgNameBytes, NetConfiguration.PKG_IDENTIFIER_FIXED_LEN);
}
}
- private void buildSCLDTA(String identifier, int minimumLength) throws SqlException {
- int length = netAgent_.getCurrentCcsidManager().getByteLength(identifier);
-
- if (length <= minimumLength) {
- write2Bytes(minimumLength);
- writeScalarPaddedString(identifier, minimumLength);
- } else {
- write2Bytes(length);
- writeScalarPaddedString(identifier, length);
- }
+ private void buildSCLDTA(byte[] identifier, int minimumLength)
+ throws SqlException {
+ int length = Math.max(minimumLength, identifier.length);
+ write2Bytes(length);
+ byte padByte = netAgent_.getCurrentCcsidManager().space_;
+ writeScalarPaddedBytes(identifier, length, padByte);
}
@@ -152,9 +162,9 @@ public class NetPackageRequest extends N
// throws an exception if lengths exceed the maximum.
// returns a boolean indicating if SLCDTALEN is required.
private boolean checkPKGNAMlengths(String identifier,
+ int length,
int maxIdentifierLength,
int lengthRequiringScldta) throws SqlException {
- int length = netAgent_.getCurrentCcsidManager().getByteLength(identifier);;
if (length > maxIdentifierLength) {
throw new SqlException(netAgent_.logWriter_,
new ClientMessageId(SQLState.LANG_IDENTIFIER_TOO_LONG),
Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/Request.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/Request.java?rev=1125299&r1=1125298&r2=1125299&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/Request.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/Request.java Fri May 20 09:49:46 2011
@@ -35,6 +35,7 @@ import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
public class Request {
@@ -1106,48 +1107,51 @@ public class Request {
/* Grab the current CCSID MGR from the NetAgent */
CcsidManager currentCcsidMgr = netAgent_.getCurrentCcsidManager();
- int stringByteLength = currentCcsidMgr.getByteLength(string);
+ // We don't know the length of the string yet, so set it to 0 for now.
+ // Will be updated later.
+ int lengthPos = buffer.position();
+ writeLengthCodePoint(0, codePoint);
+
+ int stringByteLength = encodeString(string);
if (stringByteLength > byteLengthLimit) {
throw new SqlException(netAgent_.logWriter_,
new ClientMessageId(sqlState), string);
}
- writeScalarHeader(codePoint, Math.max(byteMinLength, stringByteLength));
-
- buffer.position(
- currentCcsidMgr.convertFromJavaString(
- string, buffer.array(), buffer.position(), netAgent_));
-
// pad if we don't reach the byteMinLength limit
if (stringByteLength < byteMinLength) {
padBytes(currentCcsidMgr.space_, byteMinLength - stringByteLength);
+ stringByteLength = byteMinLength;
}
- }
-
-
- // this method inserts ddm character data into the buffer and pad's the
- // data with the ccsid manager's space character if the character data length
- // is less than paddedLength.
- // Not: this method is not to be used for String truncation and the string length
- // must be <= paddedLength.
- // This method assumes that the String argument can be
- // converted by the ccsid manager. This should be fine because usually
- // there are restrictions on the characters which can be used for ddm
- // character data. This method also assumes that the string.length() will
- // be the number of bytes following the conversion.
- final void writeScalarPaddedString(String string, int paddedLength) throws SqlException {
- ensureLength(paddedLength);
-
- /* Grab the current CCSID MGR from the NetAgent */
- CcsidManager currentCcsidMgr = netAgent_.getCurrentCcsidManager();
-
- int stringLength = currentCcsidMgr.getByteLength(string);
-
- buffer.position(currentCcsidMgr.convertFromJavaString(
- string, buffer.array(), buffer.position(), netAgent_));
+ // Update the length field. The length includes two bytes for the
+ // length field itself and two bytes for the codepoint.
+ buffer.putShort(lengthPos, (short) (stringByteLength + 4));
+ }
- padBytes(currentCcsidMgr.space_, paddedLength - stringLength);
+ /**
+ * Encode a string and put it into the buffer. A larger buffer will be
+ * allocated if the current buffer is too small to hold the entire string.
+ *
+ * @param string the string to encode
+ * @return the number of bytes in the encoded representation of the string
+ */
+ private int encodeString(String string) throws SqlException {
+ int startPos = buffer.position();
+ CharBuffer src = CharBuffer.wrap(string);
+ CcsidManager ccsidMgr = netAgent_.getCurrentCcsidManager();
+ ccsidMgr.startEncoding();
+ while (!ccsidMgr.encode(src, buffer, netAgent_)) {
+ // The buffer was too small to hold the entire string. Let's
+ // allocate a larger one. We don't know how much more space we
+ // need, so we just tell ensureLength() that we need more than
+ // what we have, until we manage to encode the entire string.
+ // ensureLength() typically doubles the size of the buffer, so
+ // we shouldn't have to call it many times before we get a large
+ // enough buffer.
+ ensureLength(buffer.remaining() + 1);
+ }
+ return buffer.position() - startPos;
}
// this method writes a 4 byte length/codepoint pair plus the bytes contained
@@ -1228,6 +1232,7 @@ public class Request {
}
final void maskOutPassword() {
+ int savedPos = buffer.position();
try {
String maskChar = "*";
// construct a mask using the maskChar.
@@ -1236,14 +1241,16 @@ public class Request {
mask.append(maskChar);
}
// try to write mask over password.
- netAgent_.getCurrentCcsidManager().convertFromJavaString(
- mask.toString(), buffer.array(), passwordStart_, netAgent_);
+ buffer.position(passwordStart_);
+ encodeString(mask.toString());
} catch (SqlException sqle) {
// failed to convert mask,
// them simply replace with 0xFF.
for (int i = 0; i < passwordLength_; i++) {
buffer.put(passwordStart_ + i, (byte) 0xFF);
}
+ } finally {
+ buffer.position(savedPos);
}
}
@@ -1457,12 +1464,7 @@ public class Request {
// ccsid manager or typdef rules. should this method write ddm character
// data or fodca data right now it is coded for ddm char data only
final void writeDDMString(String s) throws SqlException {
- CcsidManager currentCcsidManager = netAgent_.getCurrentCcsidManager();
-
- ensureLength(currentCcsidManager.getByteLength(s));
-
- buffer.position(currentCcsidManager.convertFromJavaString(
- s, buffer.array(), buffer.position(), netAgent_));
+ encodeString(s);
}
private void buildLengthAndCodePointForLob(int codePoint,
Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/Utf8CcsidManager.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/Utf8CcsidManager.java?rev=1125299&r1=1125298&r2=1125299&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/Utf8CcsidManager.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/Utf8CcsidManager.java Fri May 20 09:49:46 2011
@@ -22,6 +22,12 @@
package org.apache.derby.client.net;
import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
import org.apache.derby.client.am.Agent;
import org.apache.derby.client.am.ClientMessageId;
@@ -31,6 +37,10 @@ import org.apache.derby.shared.common.re
public class Utf8CcsidManager extends CcsidManager {
+ private final static String UTF8 = "UTF-8";
+ private final static Charset UTF8_CHARSET = Charset.forName(UTF8);
+ private final CharsetEncoder encoder = UTF8_CHARSET.newEncoder();
+
public Utf8CcsidManager() {
super((byte) ' ', // 0x40 is the ebcdic space character
(byte) '.',
@@ -56,14 +66,27 @@ public class Utf8CcsidManager extends Cc
}
public byte[] convertFromJavaString(String sourceString, Agent agent)
- throws SqlException {
- byte[] bytes = new byte[getByteLength(sourceString)];
- convertFromJavaString(sourceString, bytes, 0, agent);
- return bytes;
- }
-
- public String convertToJavaString(byte[] sourceBytes) {
- return convertToJavaString(sourceBytes, 0, sourceBytes.length);
+ throws SqlException {
+ try {
+ ByteBuffer buf = encoder.encode(CharBuffer.wrap(sourceString));
+
+ if (buf.limit() == buf.capacity()) {
+ // The length of the encoded representation of the string
+ // matches the length of the returned buffer, so just return
+ // the backing array.
+ return buf.array();
+ }
+
+ // Otherwise, copy the interesting bytes into an array with the
+ // correct length.
+ byte[] bytes = new byte[buf.limit()];
+ buf.get(bytes);
+ return bytes;
+ } catch (CharacterCodingException cce) {
+ throw new SqlException(agent.logWriter_,
+ new ClientMessageId(SQLState.CANT_CONVERT_UNICODE_TO_UTF8),
+ cce);
+ }
}
/**
@@ -71,7 +94,10 @@ public class Utf8CcsidManager extends Cc
*/
public String convertToJavaString(byte[] sourceBytes, int offset, int numToConvert) {
try {
- return new String(sourceBytes, offset, numToConvert, "UTF-8");
+ // Here we'd rather specify the encoding using a Charset object to
+ // avoid the need to handle UnsupportedEncodingException, but that
+ // constructor wasn't introduced until Java 6.
+ return new String(sourceBytes, offset, numToConvert, UTF8);
} catch (UnsupportedEncodingException e) {
// We don't have an agent in this method
if (SanityManager.DEBUG) {
@@ -81,37 +107,30 @@ public class Utf8CcsidManager extends Cc
return null;
}
- public int convertFromJavaString(String sourceString, byte[] buffer,
- int offset, Agent agent) throws SqlException {
- try {
- byte[] strBytes = sourceString.getBytes("UTF-8");
-
- for(int i=0; i<strBytes.length; i++) {
- buffer[offset++] = strBytes[i];
- }
- } catch (UnsupportedEncodingException e) {
- throw new SqlException(agent.logWriter_,
- new ClientMessageId(SQLState.CANT_CONVERT_UNICODE_TO_UTF8));
- }
- return offset;
+ public void startEncoding() {
+ encoder.reset();
}
- int maxBytesPerChar() {
- return 4;
- }
+ public boolean encode(CharBuffer src, ByteBuffer dest, Agent agent)
+ throws SqlException {
+ CoderResult result = encoder.encode(src, dest, true);
+ if (result == CoderResult.UNDERFLOW) {
+ // We've exhausted the input buffer, which means we're done if
+ // we just get everything flushed to the destination buffer.
+ result = encoder.flush(dest);
+ }
- public int getByteLength(String s) {
- try {
- return s.getBytes("UTF-8").length;
- } catch (UnsupportedEncodingException e) {
- // We don't have an agent in this method
- if (SanityManager.DEBUG) {
- SanityManager.THROWASSERT("Could not obtain byte length of Java String",e);
- }
+ if (result == CoderResult.UNDERFLOW) {
+ // Input buffer is exhausted and everything is flushed to the
+ // destination. We're done.
+ return true;
+ } else if (result == CoderResult.OVERFLOW) {
+ // Need more room in the output buffer.
+ return false;
+ } else {
+ // Something in the input buffer couldn't be encoded.
+ throw new SqlException(agent.logWriter_,
+ new ClientMessageId(SQLState.CANT_CONVERT_UNICODE_TO_UTF8));
}
- return -1;
}
-
-
-
}
Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/Utf8CcsidManagerClientTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/Utf8CcsidManagerClientTest.java?rev=1125299&r1=1125298&r2=1125299&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/Utf8CcsidManagerClientTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/Utf8CcsidManagerClientTest.java Fri May 20 09:49:46 2011
@@ -21,21 +21,47 @@
package org.apache.derbyTesting.functionTests.tests.derbynet;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
import java.util.Arrays;
import junit.framework.Test;
+import org.apache.derby.client.am.Agent;
+import org.apache.derby.client.am.LogWriter;
+import org.apache.derby.client.am.SqlException;
+import org.apache.derby.client.net.NetAgent;
import org.apache.derby.client.net.Utf8CcsidManager;
import org.apache.derbyTesting.junit.BaseTestCase;
import org.apache.derbyTesting.junit.TestConfiguration;
public class Utf8CcsidManagerClientTest extends BaseTestCase {
+ private static final String CANNOT_CONVERT = "22005";
+
private Utf8CcsidManager ccsidManager;
+ private Agent agent;
- public Utf8CcsidManagerClientTest(String name) {
+ public Utf8CcsidManagerClientTest(String name) throws Exception {
super(name);
ccsidManager = new Utf8CcsidManager();
+
+ // Set up a dummy Agent since many of the methods require one for
+ // generating exceptions.
+ PrintWriter pw = new PrintWriter(new OutputStream() {
+ public void write(int b) throws IOException {
+ // Everything goes to /dev/null...
+ }
+ });
+ agent = new NetAgent(null, new LogWriter(pw, 0));
+ }
+
+ protected void tearDown() {
+ ccsidManager = null;
+ agent = null;
}
/**
@@ -77,7 +103,12 @@ public class Utf8CcsidManagerClientTest
buffer[2] = additionalBytes[2];
// Offset 3 bytes and convert the 4 chars in ucs2String
- ccsidManager.convertFromJavaString(ucs2String, buffer, 3, null);
+ ByteBuffer wrapper = ByteBuffer.wrap(buffer);
+ wrapper.position(3);
+ ccsidManager.startEncoding();
+ boolean success =
+ ccsidManager.encode(CharBuffer.wrap(ucs2String), wrapper, null);
+ assertTrue("Overflow in encode()", success);
assertTrue("UTF-8 conversion isn't equal to bytes (with buffer)",
Arrays.equals(additionalBytes, buffer));
@@ -90,21 +121,45 @@ public class Utf8CcsidManagerClientTest
// Get the UTF-8 bytes for "Hello World" in Chinese
byte[] utf8Bytes = new String("\u4f60\u597d\u4e16\u754c").getBytes("UTF-8");
- // Get the UTF-16 string for "Hello World" in Chinese
- String ucs2String = new String(new String("\u4f60\u597d\u4e16\u754c").getBytes("UTF-16"),"UTF-16");
-
// Get the 2nd and 3rd Chinese characters in UTF-16
String offsetUcs2String = new String(new String("\u597d\u4e16").getBytes("UTF-16"),"UTF-16");
- // Convert our UTF-8 bytes to UTF-16 using the CcsidManager and compare
- String convertedString = ccsidManager.convertToJavaString(utf8Bytes);
- assertEquals(ucs2String, convertedString);
-
// Convert just the two characters as offset above and compare
String convertedOffset = ccsidManager.convertToJavaString(utf8Bytes, 3, 6);
assertEquals(offsetUcs2String, convertedOffset);
}
-
+
+ /**
+ * Test encoding of invalid Unicode characters. Expect an exception to
+ * be thrown when encountering a character that cannot be encoded.
+ */
+ public void testInvalidCharacters() {
+ // Codepoints 0xD800 - 0xDFFF arent legal
+ String invalidString = "\uD800";
+
+ ccsidManager.startEncoding();
+ try {
+ ccsidManager.encode(
+ CharBuffer.wrap(invalidString),
+ ByteBuffer.allocate(10),
+ agent);
+ fail("Encoding invalid codepoint should fail");
+ } catch (SqlException sqle) {
+ if (!CANNOT_CONVERT.equals(sqle.getSQLState())) {
+ fail("Expected SQLState " + CANNOT_CONVERT, sqle);
+ }
+ }
+
+ try {
+ ccsidManager.convertFromJavaString(invalidString, agent);
+ fail("Encoding invalid codepoint should fail");
+ } catch (SqlException sqle) {
+ if (!CANNOT_CONVERT.equals(sqle.getSQLState())) {
+ fail("Expected SQLState " + CANNOT_CONVERT, sqle);
+ }
+ }
+ }
+
public static Test suite() {
return TestConfiguration.clientServerSuite(Utf8CcsidManagerClientTest.class);
}