PROTON-1911 Improve performance of String encodes when possible

Optimize the String encoding by adding new method to WritableBuffer that
allows the buffer implementation to optimize how String values are
encoded to UTF8 bytes based on the buffer type.  Using the Java 8
default implementation support for interface methods add a default
version that behaves as the older releases would.  For the ByteBuffer
wrapper optimize the writes by accessing the buffer data in the most
direct way possible.

Help on this solution provided by Robbie Gemmell and Francesco Nigro
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/CompositeWritableBuffer.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/CompositeWritableBuffer.java
index 5b2c71c..7d1fef2 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/CompositeWritableBuffer.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/CompositeWritableBuffer.java
@@ -1,4 +1,3 @@
-package org.apache.qpid.proton.codec;
 /*
  *
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,9 +18,10 @@
  * under the License.
  *
 */
-
+package org.apache.qpid.proton.codec;
 
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 
 public class CompositeWritableBuffer implements WritableBuffer
 {
@@ -34,21 +34,25 @@
         _second = second;
     }
 
+    @Override
     public void put(byte b)
     {
         (_first.hasRemaining() ? _first : _second).put(b);
     }
 
+    @Override
     public void putFloat(float f)
     {
         putInt(Float.floatToRawIntBits(f));
     }
 
+    @Override
     public void putDouble(double d)
     {
         putLong(Double.doubleToRawLongBits(d));
     }
 
+    @Override
     public void putShort(short s)
     {
         int remaining = _first.remaining();
@@ -69,6 +73,7 @@
         }
     }
 
+    @Override
     public void putInt(int i)
     {
         int remaining = _first.remaining();
@@ -89,6 +94,7 @@
         }
     }
 
+    @Override
     public void putLong(long l)
     {
         int remaining = _first.remaining();
@@ -109,26 +115,31 @@
         }
     }
 
+    @Override
     public boolean hasRemaining()
     {
         return _first.hasRemaining() || _second.hasRemaining();
     }
 
+    @Override
     public int remaining()
     {
         return _first.remaining()+_second.remaining();
     }
 
+    @Override
     public int position()
     {
         return _first.position()+_second.position();
     }
 
+    @Override
     public int limit()
     {
         return _first.limit() + _second.limit();
     }
 
+    @Override
     public void position(int position)
     {
         int first_limit = _first.limit();
@@ -144,6 +155,7 @@
         }
     }
 
+    @Override
     public void put(byte[] src, int offset, int length)
     {
         final int firstRemaining = _first.remaining();
@@ -162,6 +174,7 @@
         _second.put(src, offset+firstRemaining, length-firstRemaining);
     }
 
+    @Override
     public void put(ByteBuffer payload)
     {
         int firstRemaining = _first.remaining();
@@ -209,4 +222,18 @@
         }
         _second.put(payload);
     }
+
+    @Override
+    public void put(String value)
+    {
+        if (_first.hasRemaining())
+        {
+            byte[] utf8Bytes = value.getBytes(StandardCharsets.UTF_8);
+            put(utf8Bytes, 0, utf8Bytes.length);
+        }
+        else
+        {
+            _second.put(value);
+        }
+    }
 }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/DroppingWritableBuffer.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/DroppingWritableBuffer.java
index ade5a08..067c41a 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/DroppingWritableBuffer.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/DroppingWritableBuffer.java
@@ -21,6 +21,7 @@
 package org.apache.qpid.proton.codec;
 
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 
 public class DroppingWritableBuffer implements WritableBuffer
 {
@@ -106,8 +107,15 @@
     }
 
     @Override
-    public void put(ReadableBuffer payload) {
+    public void put(ReadableBuffer payload)
+    {
         _pos += payload.remaining();
         payload.position(payload.limit());
     }
+
+    @Override
+    public void put(String value)
+    {
+        _pos += value.getBytes(StandardCharsets.UTF_8).length;
+    }
 }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java
index d709a77..512805b 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java
@@ -80,12 +80,6 @@
 
     private final ArrayType             _arrayType;
 
-    EncoderImpl(ByteBuffer buffer, DecoderImpl decoder)
-    {
-        this(decoder);
-        setByteBuffer(buffer);
-    }
-
     public EncoderImpl(DecoderImpl decoder)
     {
         _decoder                = decoder;
@@ -129,10 +123,9 @@
                                                 _floatType,
                                                 _doubleType,
                                                 _characterType);
-
-
     }
 
+    @Override
     public void setByteBuffer(final ByteBuffer buf)
     {
         _buffer = new WritableBuffer.ByteBufferWrapper(buf);
@@ -228,11 +221,13 @@
         _describedTypesClassRegistry.put(clazz, type);
     }
 
+    @Override
     public void writeNull()
     {
         _buffer.put(EncodingCodes.NULL);
     }
 
+    @Override
     public void writeBoolean(final boolean bool)
     {
         if (bool)
@@ -245,6 +240,7 @@
         }
     }
 
+    @Override
     public void writeBoolean(final Boolean bool)
     {
         if(bool == null)
@@ -261,6 +257,7 @@
         }
     }
 
+    @Override
     public void writeUnsignedByte(final UnsignedByte ubyte)
     {
         if(ubyte == null)
@@ -273,6 +270,7 @@
         }
     }
 
+    @Override
     public void writeUnsignedShort(final UnsignedShort ushort)
     {
         if(ushort == null)
@@ -285,6 +283,7 @@
         }
     }
 
+    @Override
     public void writeUnsignedInteger(final UnsignedInteger uint)
     {
         if(uint == null)
@@ -297,6 +296,7 @@
         }
     }
 
+    @Override
     public void writeUnsignedLong(final UnsignedLong ulong)
     {
         if(ulong == null)
@@ -309,11 +309,13 @@
         }
     }
 
+    @Override
     public void writeByte(final byte b)
     {
         _byteType.write(b);
     }
 
+    @Override
     public void writeByte(final Byte b)
     {
         if(b == null)
@@ -326,11 +328,13 @@
         }
     }
 
+    @Override
     public void writeShort(final short s)
     {
         _shortType.write(s);
     }
 
+    @Override
     public void writeShort(final Short s)
     {
         if(s == null)
@@ -343,11 +347,13 @@
         }
     }
 
+    @Override
     public void writeInteger(final int i)
     {
         _integerType.write(i);
     }
 
+    @Override
     public void writeInteger(final Integer i)
     {
         if(i == null)
@@ -360,11 +366,13 @@
         }
     }
 
+    @Override
     public void writeLong(final long l)
     {
         _longType.write(l);
     }
 
+    @Override
     public void writeLong(final Long l)
     {
 
@@ -378,11 +386,13 @@
         }
     }
 
+    @Override
     public void writeFloat(final float f)
     {
         _floatType.write(f);
     }
 
+    @Override
     public void writeFloat(final Float f)
     {
         if(f == null)
@@ -395,11 +405,13 @@
         }
     }
 
+    @Override
     public void writeDouble(final double d)
     {
         _doubleType.write(d);
     }
 
+    @Override
     public void writeDouble(final Double d)
     {
         if(d == null)
@@ -412,6 +424,7 @@
         }
     }
 
+    @Override
     public void writeDecimal32(final Decimal32 d)
     {
         if(d == null)
@@ -424,6 +437,7 @@
         }
     }
 
+    @Override
     public void writeDecimal64(final Decimal64 d)
     {
         if(d == null)
@@ -436,6 +450,7 @@
         }
     }
 
+    @Override
     public void writeDecimal128(final Decimal128 d)
     {
         if(d == null)
@@ -448,12 +463,14 @@
         }
     }
 
+    @Override
     public void writeCharacter(final char c)
     {
         // TODO - java character may be half of a pair, should probably throw exception then
         _characterType.write(c);
     }
 
+    @Override
     public void writeCharacter(final Character c)
     {
         if(c == null)
@@ -466,11 +483,13 @@
         }
     }
 
+    @Override
     public void writeTimestamp(final long timestamp)
     {
         _timestampType.fastWrite(this, timestamp);
     }
 
+    @Override
     public void writeTimestamp(final Date d)
     {
         if(d == null)
@@ -483,6 +502,7 @@
         }
     }
 
+    @Override
     public void writeUUID(final UUID uuid)
     {
         if(uuid == null)
@@ -496,6 +516,7 @@
 
     }
 
+    @Override
     public void writeBinary(final Binary b)
     {
         if(b == null)
@@ -508,6 +529,7 @@
         }
     }
 
+    @Override
     public void writeString(final String s)
     {
         if(s == null)
@@ -520,6 +542,7 @@
         }
     }
 
+    @Override
     public void writeSymbol(final Symbol s)
     {
         if(s == null)
@@ -533,6 +556,7 @@
 
     }
 
+    @Override
     public void writeList(final List l)
     {
         if(l == null)
@@ -545,6 +569,7 @@
         }
     }
 
+    @Override
     public void writeMap(final Map m)
     {
 
@@ -558,6 +583,7 @@
         }
     }
 
+    @Override
     public void writeDescribedType(final DescribedType d)
     {
         if(d == null)
@@ -572,6 +598,7 @@
         }
     }
 
+    @Override
     public void writeArray(final boolean[] a)
     {
         if(a == null)
@@ -584,6 +611,7 @@
         }
     }
 
+    @Override
     public void writeArray(final byte[] a)
     {
         if(a == null)
@@ -596,6 +624,7 @@
         }
     }
 
+    @Override
     public void writeArray(final short[] a)
     {
         if(a == null)
@@ -608,6 +637,7 @@
         }
     }
 
+    @Override
     public void writeArray(final int[] a)
     {
         if(a == null)
@@ -620,6 +650,7 @@
         }
     }
 
+    @Override
     public void writeArray(final long[] a)
     {
         if(a == null)
@@ -632,6 +663,7 @@
         }
     }
 
+    @Override
     public void writeArray(final float[] a)
     {
         if(a == null)
@@ -644,6 +676,7 @@
         }
     }
 
+    @Override
     public void writeArray(final double[] a)
     {
         if(a == null)
@@ -656,6 +689,7 @@
         }
     }
 
+    @Override
     public void writeArray(final char[] a)
     {
         if(a == null)
@@ -668,6 +702,7 @@
         }
     }
 
+    @Override
     public void writeArray(final Object[] a)
     {
         if(a == null)
@@ -680,6 +715,7 @@
         }
     }
 
+    @Override
     public void writeObject(final Object o)
     {
         if (o == null)
@@ -802,46 +838,9 @@
         _buffer.put(src, offset, length);
     }
 
-    void writeRaw(String string)
+    void writeRaw(final String string)
     {
-        final int length = string.length();
-        int c;
-
-        for (int i = 0; i < length; i++)
-        {
-            c = string.charAt(i);
-            if ((c & 0xFF80) == 0)          /* U+0000..U+007F */
-            {
-                _buffer.put((byte) c);
-            }
-            else if ((c & 0xF800) == 0)     /* U+0080..U+07FF */
-            {
-                _buffer.put((byte)(0xC0 | ((c >> 6) & 0x1F)));
-                _buffer.put((byte)(0x80 | (c & 0x3F)));
-            }
-            else if ((c & 0xD800) != 0xD800 || (c > 0xDBFF))     /* U+0800..U+FFFF - excluding surrogate pairs */
-            {
-                _buffer.put((byte)(0xE0 | ((c >> 12) & 0x0F)));
-                _buffer.put((byte)(0x80 | ((c >> 6) & 0x3F)));
-                _buffer.put((byte)(0x80 | (c & 0x3F)));
-            }
-            else
-            {
-                int low;
-
-                if((++i == length) || ((low = string.charAt(i)) & 0xDC00) != 0xDC00)
-                {
-                    throw new IllegalArgumentException("String contains invalid Unicode code points");
-                }
-
-                c = 0x010000 + ((c & 0x03FF) << 10) + (low & 0x03FF);
-
-                _buffer.put((byte)(0xF0 | ((c >> 18) & 0x07)));
-                _buffer.put((byte)(0x80 | ((c >> 12) & 0x3F)));
-                _buffer.put((byte)(0x80 | ((c >> 6) & 0x3F)));
-                _buffer.put((byte)(0x80 | (c & 0x3F)));
-            }
-        }
+        _buffer.put(string);
     }
 
     AMQPType getNullTypeEncoder()
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/WritableBuffer.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/WritableBuffer.java
index 67c8292..6941ed7 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/WritableBuffer.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/WritableBuffer.java
@@ -23,8 +23,7 @@
 
 import java.nio.ByteBuffer;
 
-public interface WritableBuffer
-{
+public interface WritableBuffer {
     void put(byte b);
 
     void putFloat(float f);
@@ -51,131 +50,191 @@
 
     void put(ReadableBuffer payload);
 
+    default void put(final String value) {
+        final int length = value.length();
+
+        for (int i = 0; i < length; i++) {
+            int c = value.charAt(i);
+            if ((c & 0xFF80) == 0) {
+                // U+0000..U+007F
+                put((byte) c);
+            } else if ((c & 0xF800) == 0)  {
+                // U+0080..U+07FF
+                put((byte) (0xC0 | ((c >> 6) & 0x1F)));
+                put((byte) (0x80 | (c & 0x3F)));
+            } else if ((c & 0xD800) != 0xD800 || (c > 0xDBFF)) {
+                // U+0800..U+FFFF - excluding surrogate pairs
+                put((byte) (0xE0 | ((c >> 12) & 0x0F)));
+                put((byte) (0x80 | ((c >> 6) & 0x3F)));
+                put((byte) (0x80 | (c & 0x3F)));
+            } else {
+                int low;
+
+                if ((++i == length) || ((low = value.charAt(i)) & 0xDC00) != 0xDC00) {
+                    throw new IllegalArgumentException("String contains invalid Unicode code points");
+                }
+
+                c = 0x010000 + ((c & 0x03FF) << 10) + (low & 0x03FF);
+
+                put((byte) (0xF0 | ((c >> 18) & 0x07)));
+                put((byte) (0x80 | ((c >> 12) & 0x3F)));
+                put((byte) (0x80 | ((c >> 6) & 0x3F)));
+                put((byte) (0x80 | (c & 0x3F)));
+            }
+        }
+    }
+
     int limit();
 
-    class ByteBufferWrapper implements WritableBuffer
-    {
+    class ByteBufferWrapper implements WritableBuffer {
         private final ByteBuffer _buf;
 
-        public ByteBufferWrapper(ByteBuffer buf)
-        {
+        public ByteBufferWrapper(ByteBuffer buf) {
             _buf = buf;
         }
 
         @Override
-        public void put(byte b)
-        {
+        public void put(byte b) {
             _buf.put(b);
         }
 
         @Override
-        public void putFloat(float f)
-        {
+        public void putFloat(float f) {
             _buf.putFloat(f);
         }
 
         @Override
-        public void putDouble(double d)
-        {
+        public void putDouble(double d) {
             _buf.putDouble(d);
         }
 
         @Override
-        public void put(byte[] src, int offset, int length)
-        {
+        public void put(byte[] src, int offset, int length) {
             _buf.put(src, offset, length);
         }
 
         @Override
-        public void putShort(short s)
-        {
+        public void putShort(short s) {
             _buf.putShort(s);
         }
 
         @Override
-        public void putInt(int i)
-        {
+        public void putInt(int i) {
             _buf.putInt(i);
         }
 
         @Override
-        public void putLong(long l)
-        {
+        public void putLong(long l) {
             _buf.putLong(l);
         }
 
         @Override
-        public boolean hasRemaining()
-        {
+        public boolean hasRemaining() {
             return _buf.hasRemaining();
         }
 
         @Override
-        public int remaining()
-        {
+        public int remaining() {
             return _buf.remaining();
         }
 
         @Override
-        public int position()
-        {
+        public int position() {
             return _buf.position();
         }
 
         @Override
-        public void position(int position)
-        {
+        public void position(int position) {
             _buf.position(position);
         }
 
         @Override
-        public void put(ByteBuffer src)
-        {
+        public void put(ByteBuffer src) {
             _buf.put(src);
         }
 
         @Override
-        public void put(ReadableBuffer src)
-        {
+        public void put(ReadableBuffer src) {
             src.get(this);
         }
 
         @Override
-        public int limit()
-        {
+        public void put(final String value) {
+            final int length = value.length();
+
+            int pos = _buf.position();
+
+            for (int i = 0; i < length; i++) {
+                int c = value.charAt(i);
+                if ((c & 0xFF80) == 0) {
+                    // U+0000..U+007F
+                    put(pos++, (byte) c);
+                } else if ((c & 0xF800) == 0)  {
+                    // U+0080..U+07FF
+                    put(pos++, (byte) (0xC0 | ((c >> 6) & 0x1F)));
+                    put(pos++, (byte) (0x80 | (c & 0x3F)));
+                } else if ((c & 0xD800) != 0xD800 || (c > 0xDBFF))  {
+                    // U+0800..U+FFFF - excluding surrogate pairs
+                    put(pos++, (byte) (0xE0 | ((c >> 12) & 0x0F)));
+                    put(pos++, (byte) (0x80 | ((c >> 6) & 0x3F)));
+                    put(pos++, (byte) (0x80 | (c & 0x3F)));
+                } else {
+                    int low;
+
+                    if ((++i == length) || ((low = value.charAt(i)) & 0xDC00) != 0xDC00) {
+                        throw new IllegalArgumentException("String contains invalid Unicode code points");
+                    }
+
+                    c = 0x010000 + ((c & 0x03FF) << 10) + (low & 0x03FF);
+
+                    put(pos++, (byte) (0xF0 | ((c >> 18) & 0x07)));
+                    put(pos++, (byte) (0x80 | ((c >> 12) & 0x3F)));
+                    put(pos++, (byte) (0x80 | ((c >> 6) & 0x3F)));
+                    put(pos++, (byte) (0x80 | (c & 0x3F)));
+                }
+            }
+
+            // Now move the buffer position to reflect the work done here
+            _buf.position(pos);
+        }
+
+        @Override
+        public int limit() {
             return _buf.limit();
         }
 
-        public ByteBuffer byteBuffer()
-        {
+        public ByteBuffer byteBuffer() {
             return _buf;
         }
 
-        public ReadableBuffer toReadableBuffer()
-        {
+        public ReadableBuffer toReadableBuffer() {
             return ReadableBuffer.ByteBufferReader.wrap((ByteBuffer) _buf.duplicate().flip());
         }
 
         @Override
-        public String toString()
-        {
+        public String toString() {
             return String.format("[pos: %d, limit: %d, remaining:%d]", _buf.position(), _buf.limit(), _buf.remaining());
         }
 
-        public static ByteBufferWrapper allocate(int size)
-        {
+        public static ByteBufferWrapper allocate(int size) {
             ByteBuffer allocated = ByteBuffer.allocate(size);
             return new ByteBufferWrapper(allocated);
         }
 
-        public static ByteBufferWrapper wrap(ByteBuffer buffer)
-        {
+        public static ByteBufferWrapper wrap(ByteBuffer buffer) {
             return new ByteBufferWrapper(buffer);
         }
 
-        public static ByteBufferWrapper wrap(byte[] bytes)
-        {
+        public static ByteBufferWrapper wrap(byte[] bytes) {
             return new ByteBufferWrapper(ByteBuffer.wrap(bytes));
         }
+
+        private void put(int index, byte value) {
+            if (_buf.hasArray()) {
+                _buf.array()[_buf.arrayOffset() + index] = value;
+            } else {
+                _buf.put(index, value);
+            }
+        }
     }
 }
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/Benchmark.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/Benchmark.java
index 5dd4077..e1e78ad 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/Benchmark.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/Benchmark.java
@@ -34,8 +34,8 @@
 import org.apache.qpid.proton.amqp.UnsignedInteger;
 import org.apache.qpid.proton.amqp.UnsignedShort;
 import org.apache.qpid.proton.amqp.messaging.Accepted;
-import org.apache.qpid.proton.amqp.messaging.Data;
 import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
+import org.apache.qpid.proton.amqp.messaging.Data;
 import org.apache.qpid.proton.amqp.messaging.Header;
 import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
 import org.apache.qpid.proton.amqp.messaging.Properties;
@@ -243,8 +243,9 @@
 
     private void benchmarkProperties() throws IOException {
         Properties properties = new Properties();
-        properties.setTo("queue:1");
-        properties.setMessageId("ID:Message:1");
+        properties.setTo("queue:1-1024");
+        properties.setReplyTo("queue:1-11024-reply");
+        properties.setMessageId("ID:255f1297-5a71-4df1-8147-b2cdf850a56f:1");
         properties.setCreationTime(new Date(System.currentTimeMillis()));
 
         resultSet.start();
@@ -377,9 +378,9 @@
     }
 
     private void benchmarkString() throws IOException {
-        String string1 = new String("String-1");
-        String string2 = new String("String-2");
-        String string3 = new String("String-3");
+        String string1 = new String("String-1-somewhat-long-test-to-validate-performance-improvements-to-the-proton-j-codec-@!%$");
+        String string2 = new String("String-2-somewhat-long-test-to-validate-performance-improvements-to-the-proton-j-codec-@!%$");
+        String string3 = new String("String-3-somewhat-long-test-to-validate-performance-improvements-to-the-proton-j-codec-@!%$");
 
         resultSet.start();
         for (int i = 0; i < ITERATIONS; i++) {
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/StringTypeTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/StringTypeTest.java
index 944a0f2..3ba2007 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/StringTypeTest.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/StringTypeTest.java
@@ -44,13 +44,13 @@
     private static final List<String> TEST_DATA = generateTestData();
 
     /**
-     * Loop over all the chars in given {@link UnicodeBlock}s and return a
-     * {@link Set <String>} containing all the possible values as their
-     * {@link String} values.
+     * Loop over all the chars in given {@link UnicodeBlock}s and return a {@link Set <String>}
+     * containing all the possible values as their {@link String} values.
      *
-     * @param blocks the {@link UnicodeBlock}s to loop over
-     * @return a {@link Set <String>} containing all the possible values as
-     * {@link String} values
+     * @param blocks
+     *        the {@link UnicodeBlock}s to loop over
+     * @return a {@link Set <String>} containing all the possible values as {@link String}
+     *         values
      */
     private static Set<String> getAllStringsFromUnicodeBlocks(final UnicodeBlock... blocks)
     {
@@ -87,10 +87,9 @@
         return strings;
     }
 
-
     /**
-     * Test the encoding and decoding of various complicated Unicode characters
-     * which will end up as "surrogate pairs" when encoded to UTF-8
+     * Test the encoding and decoding of various complicated Unicode characters which will end
+     * up as "surrogate pairs" when encoded to UTF-8
      */
     @Test
     public void calculateUTF8Length()
@@ -102,7 +101,7 @@
     }
 
     /**
-     * Test the encoding and decoding of various  Unicode characters
+     * Test the encoding and decoding of various Unicode characters
      */
     @Test
     public void encodeDecodeStrings()
@@ -225,7 +224,7 @@
                                                      UnicodeBlock.GREEK,
                                                      UnicodeBlock.LETTERLIKE_SYMBOLS));
                 // blocks with surrogate pairs
-                //TODO: restore others when Java 7 is baseline
+                // TODO: restore others when Java 7 is baseline
                 addAll(getAllStringsFromUnicodeBlocks(UnicodeBlock.LINEAR_B_SYLLABARY,
                                                      /*UnicodeBlock.MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS,*/
                                                      UnicodeBlock.MUSICAL_SYMBOLS,
@@ -246,4 +245,113 @@
             }
         };
     }
+
+    @Test
+    public void testEncodeAndDecodeUsingWritableBufferDefaultPutString() {
+        final DecoderImpl decoder = new DecoderImpl();
+        final EncoderImpl encoder = new EncoderImpl(decoder);
+        AMQPDefinedTypes.registerAllTypes(decoder, encoder);
+
+        for (final String input : TEST_DATA) {
+            final WritableBufferWithoutPutStringOverride sink = new WritableBufferWithoutPutStringOverride(16);
+            final AmqpValue inputValue = new AmqpValue(input);
+            encoder.setByteBuffer(sink);
+            encoder.writeObject(inputValue);
+            ReadableBuffer source = new ReadableBuffer.ByteBufferReader(ByteBuffer.wrap(sink.getArray(), 0, sink.getArrayLength()));
+            decoder.setBuffer(source);
+            final AmqpValue outputValue = (AmqpValue) decoder.readObject();
+            assertEquals("Failed to round trip String correctly: ", input, outputValue.getValue());
+        }
+    }
+
+    /**
+     * Test class which implements WritableBuffer but does not override the default put(String)
+     * method, used to verify the default method is in place and works.
+     */
+    private static final class WritableBufferWithoutPutStringOverride implements WritableBuffer {
+
+        private final ByteBufferWrapper delegate;
+
+        public WritableBufferWithoutPutStringOverride(int capacity) {
+            delegate = WritableBuffer.ByteBufferWrapper.allocate(capacity);
+        }
+
+        public byte[] getArray() {
+            return delegate.byteBuffer().array();
+        }
+
+        public int getArrayLength() {
+            return delegate.byteBuffer().position();
+        }
+
+        @Override
+        public void put(byte b) {
+            delegate.put(b);
+        }
+
+        @Override
+        public void putShort(short value) {
+            delegate.putShort(value);
+        }
+
+        @Override
+        public void putInt(int value) {
+            delegate.putInt(value);
+        }
+
+        @Override
+        public void putLong(long value) {
+            delegate.putLong(value);
+        }
+
+        @Override
+        public void putFloat(float value) {
+            delegate.putFloat(value);
+        }
+
+        @Override
+        public void putDouble(double value) {
+            delegate.putDouble(value);
+        }
+
+        @Override
+        public void put(byte[] src, int offset, int length) {
+            delegate.put(src, offset, length);
+        }
+
+        @Override
+        public boolean hasRemaining() {
+            return delegate.hasRemaining();
+        }
+
+        @Override
+        public int remaining() {
+            return delegate.remaining();
+        }
+
+        @Override
+        public int position() {
+            return delegate.position();
+        }
+
+        @Override
+        public void position(int position) {
+            delegate.position(position);
+        }
+
+        @Override
+        public void put(ByteBuffer payload) {
+            delegate.put(payload);
+        }
+
+        @Override
+        public int limit() {
+            return delegate.limit();
+        }
+
+        @Override
+        public void put(ReadableBuffer src) {
+            delegate.put(src);
+        }
+    }
 }