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);
     }