Author: robbie
Date: Wed May 14 16:00:06 2014
New Revision: 1594627

URL: http://svn.apache.org/r1594627
Log:
QPIDJMS-21: implement support for byte[] entries, use LinkedHashMap for 
predictable ordering behaviour, add additional tests.

Added:
    
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/MapMessageIntegrationTest.java
Modified:
    qpid/jms/trunk/src/main/java/org/apache/qpid/jms/engine/AmqpMapMessage.java
    qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/MapMessageImpl.java
    qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/SessionImpl.java
    
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/impl/MapMessageImplTest.java

Modified: 
qpid/jms/trunk/src/main/java/org/apache/qpid/jms/engine/AmqpMapMessage.java
URL: 
http://svn.apache.org/viewvc/qpid/jms/trunk/src/main/java/org/apache/qpid/jms/engine/AmqpMapMessage.java?rev=1594627&r1=1594626&r2=1594627&view=diff
==============================================================================
--- qpid/jms/trunk/src/main/java/org/apache/qpid/jms/engine/AmqpMapMessage.java 
(original)
+++ qpid/jms/trunk/src/main/java/org/apache/qpid/jms/engine/AmqpMapMessage.java 
Wed May 14 16:00:06 2014
@@ -18,10 +18,12 @@
  */
 package org.apache.qpid.jms.engine;
 
-import java.util.HashMap;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.qpid.proton.amqp.Binary;
 import org.apache.qpid.proton.amqp.messaging.AmqpValue;
 import org.apache.qpid.proton.amqp.messaging.Section;
 import org.apache.qpid.proton.engine.Delivery;
@@ -67,7 +69,9 @@ public class AmqpMapMessage extends Amqp
 
     private void initialiseMessageBodyMap()
     {
-        _messageBodyMap= new HashMap<String,Object>();
+        //Using LinkedHashMap because AMQP map equality considers order,
+        //so we should behave in as predictable a manner as possible
+        _messageBodyMap= new LinkedHashMap<String,Object>();
         getMessage().setBody(new AmqpValue(_messageBodyMap));
     }
 
@@ -84,25 +88,47 @@ public class AmqpMapMessage extends Amqp
     /**
      * Associates the specified value with the specified key in this map 
message.
      *
-     * If a previous mapping for the key exists, the old value is replaced by 
the specified value and the old value is returned.
+     * If a previous mapping for the key exists, the old value is replaced by 
the specified value.
+     *
+     * To be clear, if the value provided is a byte[] then it is NOT copied 
and MUST NOT be subsequently altered.
+     *
      * @param key the key for the mapping
      * @param value the value for the mapping
-     * @return the old value if one exists for this key, or null if there was 
none.
      */
-    public Object setMapEntry(String key, Object value)
+    public void setMapEntry(String key, Object value)
     {
-        return _messageBodyMap.put(key, value);
+        Object entry = value;
+        if(value instanceof byte[])
+        {
+            entry = new Binary((byte[]) value);
+        }
+
+        _messageBodyMap.put(key, entry);
     }
 
     /**
      * Returns the value to which the specified key is mapped, or null if this 
map contains no mapping for the key.
      *
+     * If the value being returned is a byte[], the array returned IS a copy.
+     *
      * @param key the key for the mapping
      * @return the value if one exists for this key, or null if there was none.
      */
     public Object getMapEntry(String key)
     {
-        return _messageBodyMap.get(key);
+        Object object = _messageBodyMap.get(key);
+
+        if(object instanceof Binary)
+        {
+            //We will return a byte[]. It is possibly only part of the 
underlying array, copy that bit.
+            Binary bin = ((Binary) object);
+
+            return Arrays.copyOfRange(bin.getArray(), bin.getArrayOffset(), 
bin.getLength());
+        }
+        else
+        {
+            return object;
+        }
     }
 
     /**

Modified: 
qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/MapMessageImpl.java
URL: 
http://svn.apache.org/viewvc/qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/MapMessageImpl.java?rev=1594627&r1=1594626&r2=1594627&view=diff
==============================================================================
--- qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/MapMessageImpl.java 
(original)
+++ qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/MapMessageImpl.java 
Wed May 14 16:00:06 2014
@@ -30,13 +30,6 @@ import javax.jms.MessageNotWriteableExce
 
 import org.apache.qpid.jms.engine.AmqpMapMessage;
 
-/**
- * TODO
- *
- * NOTES:
- * Implement handling for byte[] in the map (e.g convert to/from Binary);
- *
- */
 public class MapMessageImpl extends MessageImpl<AmqpMapMessage> implements 
MapMessage
 {
     //message to be sent
@@ -54,8 +47,8 @@ public class MapMessageImpl extends Mess
     @Override
     protected AmqpMapMessage 
prepareUnderlyingAmqpMessageForSending(AmqpMapMessage amqpMessage)
     {
-        //TODO
-        throw new UnsupportedOperationException("Not Implemented");
+        //Currently nothing to do, we always operate directly on the 
underlying AmqpMapMessage.
+        return amqpMessage;
     }
 
     private void setMapEntry(String name, Object value) throws 
IllegalArgumentException, MessageNotWriteableException
@@ -167,7 +160,7 @@ public class MapMessageImpl extends Mess
     {
         Object value = getMapEntry(name);
 
-        if ((value instanceof Character)) //TODO: verify we dont get a Binary, 
push down impl to ensure we dont
+        if ((value instanceof Character))
         {
             return (char) value;
         }
@@ -290,7 +283,7 @@ public class MapMessageImpl extends Mess
         {
             return (String) value;
         }
-        else if (value instanceof byte[])//TODO: verify we dont get a Binary, 
push down impl to ensure we dont
+        else if (value instanceof byte[])
         {
             throw new MessageFormatException("Map entry " + name + " of type 
byte[] " + "cannot be converted to String.");
         }
@@ -305,7 +298,7 @@ public class MapMessageImpl extends Mess
     {
         Object value = getMapEntry(name);
 
-        if ((value instanceof byte[]) || (value == null)) //TODO: verify we 
dont get a Binary, push down impl to ensure we dont
+        if ((value instanceof byte[]) || (value == null))
         {
             return (byte[]) value;
         }
@@ -318,8 +311,6 @@ public class MapMessageImpl extends Mess
     @Override
     public Object getObject(String name) throws JMSException
     {
-        //TODO: verify what happens with byte[] for received messages
-        //(i.e does it return a Binary? if so, push impl down to ensure we get 
a byte[] instead)
         return getMapEntry(name);
     }
 
@@ -388,16 +379,32 @@ public class MapMessageImpl extends Mess
     @Override
     public void setBytes(String name, byte[] value) throws JMSException
     {
-        //TODO
-        throw new UnsupportedOperationException("Not Implemented");
+        int length = 0;
+        if(value != null)
+        {
+            length = value.length;
+        }
+
+        setBytes(name, value, 0, length);
     }
 
     @Override
     public void setBytes(String name, byte[] value, int offset, int length)
             throws JMSException
     {
-        //TODO
-        throw new UnsupportedOperationException("Not Implemented");
+        byte[] dest;
+
+        if(value == null)
+        {
+            dest = null;
+        }
+        else
+        {
+            dest = new byte[length];
+            System.arraycopy(value, offset, dest, 0, length);
+        }
+
+        setMapEntry(name, dest);
     }
 
     @Override

Modified: qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/SessionImpl.java
URL: 
http://svn.apache.org/viewvc/qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/SessionImpl.java?rev=1594627&r1=1594626&r2=1594627&view=diff
==============================================================================
--- qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/SessionImpl.java 
(original)
+++ qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/SessionImpl.java Wed 
May 14 16:00:06 2014
@@ -228,8 +228,7 @@ public class SessionImpl implements Sess
     @Override
     public MapMessage createMapMessage() throws JMSException
     {
-        // TODO Auto-generated method stub
-        throw new UnsupportedOperationException("Not Implemented");
+        return new MapMessageImpl(this, getConnectionImpl());
     }
 
     @Override

Added: 
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/MapMessageIntegrationTest.java
URL: 
http://svn.apache.org/viewvc/qpid/jms/trunk/src/test/java/org/apache/qpid/jms/MapMessageIntegrationTest.java?rev=1594627&view=auto
==============================================================================
--- 
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/MapMessageIntegrationTest.java 
(added)
+++ 
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/MapMessageIntegrationTest.java 
Wed May 14 16:00:06 2014
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.jms;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.jms.Connection;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+import org.apache.qpid.jms.test.testpeer.TestAmqpPeer;
+import 
org.apache.qpid.jms.test.testpeer.describedtypes.sections.AmqpValueDescribedType;
+import 
org.apache.qpid.jms.test.testpeer.matchers.sections.MessageAnnotationsSectionMatcher;
+import 
org.apache.qpid.jms.test.testpeer.matchers.sections.MessageHeaderSectionMatcher;
+import 
org.apache.qpid.jms.test.testpeer.matchers.sections.MessagePropertiesSectionMatcher;
+import 
org.apache.qpid.jms.test.testpeer.matchers.sections.TransferPayloadCompositeMatcher;
+import 
org.apache.qpid.jms.test.testpeer.matchers.types.EncodedAmqpValueMatcher;
+import org.apache.qpid.proton.amqp.Binary;
+import org.apache.qpid.proton.amqp.DescribedType;
+import org.junit.Test;
+
+public class MapMessageIntegrationTest extends QpidJmsTestCase
+{
+    private final IntegrationTestFixture _testFixture = new 
IntegrationTestFixture();
+
+    /**
+     * Test that a message received from the test peer with an AmqpValue 
section containing
+     * a map which holds entries of the various supported entry types is 
returned as a
+     * {@link MapMessage}, and verify the values can all be retrieved as 
expected.
+     */
+    @Test
+    public void testReceiveBasicMapMessage() throws Exception
+    {
+        try(TestAmqpPeer testPeer = new 
TestAmqpPeer(IntegrationTestFixture.PORT);)
+        {
+            Connection connection = _testFixture.establishConnecton(testPeer);
+            connection.start();
+
+            testPeer.expectBegin();
+
+            Session session = connection.createSession(false, 
Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+
+            //Prepare an AMQP message for the test peer to send, containing an
+            //AmqpValue section holding a map with entries for each supported 
type.
+            //with each supported type of entry
+            String myBoolKey = "myBool";
+            boolean myBool = true;
+            String myByteKey = "myByte";
+            byte myByte = 4;
+            String myBytesKey = "myBytes";
+            byte[] myBytes = myBytesKey.getBytes();
+            String myCharKey = "myChar";
+            char myChar = 'd';
+            String myDoubleKey = "myDouble";
+            double myDouble = 1234567890123456789.1234;
+            String myFloatKey = "myFloat";
+            float myFloat = 1.1F;
+            String myIntKey = "myInt";
+            int myInt = Integer.MAX_VALUE;
+            String myLongKey = "myLong";
+            long myLong = Long.MAX_VALUE;
+            String myShortKey = "myShort";
+            short myShort = 25;
+            String myStringKey = "myString";
+            String myString = myStringKey;
+
+            Map<String,Object> map = new LinkedHashMap<String,Object>();
+            map.put(myBoolKey, myBool);
+            map.put(myByteKey, myByte);
+            map.put(myBytesKey, new Binary(myBytes));//the underlying AMQP 
message uses Binary rather than byte[] directly.
+            map.put(myCharKey, myChar);
+            map.put(myDoubleKey, myDouble);
+            map.put(myFloatKey, myFloat);
+            map.put(myIntKey, myInt);
+            map.put(myLongKey, myLong);
+            map.put(myShortKey, myShort);
+            map.put(myStringKey, myString);
+
+            DescribedType amqpValueSectionContent = new 
AmqpValueDescribedType(map);
+
+            //receive the message from the test peer
+            testPeer.expectReceiverAttach();
+            testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null, 
amqpValueSectionContent);
+            testPeer.expectDispositionThatIsAcceptedAndSettled();
+
+            MessageConsumer messageConsumer = session.createConsumer(queue);
+            Message receivedMessage = messageConsumer.receive(1000);
+            testPeer.waitForAllHandlersToComplete(3000);
+
+            //verify the content is as expected
+            assertNotNull("Message was not received", receivedMessage);
+            assertTrue("Message was not a BytesMessage", receivedMessage 
instanceof MapMessage);
+            MapMessage receivedMapMessage  = (MapMessage) receivedMessage;
+
+            assertEquals("Unexpected boolean value", myBool, 
receivedMapMessage.getBoolean(myBoolKey));
+            assertEquals("Unexpected byte value", myByte, 
receivedMapMessage.getByte(myByteKey));
+            byte[] readBytes = receivedMapMessage.getBytes(myBytesKey);
+            assertTrue("Read bytes were not as expected: " + 
Arrays.toString(readBytes), Arrays.equals(myBytes, readBytes));
+            assertEquals("Unexpected char value", myChar, 
receivedMapMessage.getChar(myCharKey));
+            assertEquals("Unexpected double value", myDouble, 
receivedMapMessage.getDouble(myDoubleKey), 0.0);
+            assertEquals("Unexpected float value", myFloat, 
receivedMapMessage.getFloat(myFloatKey), 0.0);
+            assertEquals("Unexpected int value", myInt, 
receivedMapMessage.getInt(myIntKey));
+            assertEquals("Unexpected long value", myLong, 
receivedMapMessage.getLong(myLongKey));
+            assertEquals("Unexpected short value", myShort, 
receivedMapMessage.getShort(myShortKey));
+            assertEquals("Unexpected UTF value", myString, 
receivedMapMessage.getString(myStringKey));
+        }
+    }
+
+/*
+ * TODO: decide what to do about this
+ *
+ * The test below fails if a char is added, because the DataImpl-based decoder 
used by the test peer
+ * decodes the char to an Integer object and thus the EncodedAmqpValueMatcher 
fails the comparison
+ * of its contained map due to the differing types. This doesn't happen in the 
above test as the
+ * reversed roles mean it is DecoderImpl doing the decoding and it casts the 
output to a char.
+ *
+ * The below change to alter DataImpl's output makes the test pass, as would 
making the test 'expect an int'.
+----START PATCH-----
+diff --git 
proton-j/src/main/java/org/apache/qpid/proton/codec/impl/CharElement.java 
proton-j/src/main/java/org/apache/qpid/proton/codec/impl/CharElement.java
+index 808d43e..6cdf84c 100644
+--- proton-j/src/main/java/org/apache/qpid/proton/codec/impl/CharElement.java
++++ proton-j/src/main/java/org/apache/qpid/proton/codec/impl/CharElement.java
+@@ -25,9 +25,8 @@ import java.nio.ByteBuffer;
+
+ import org.apache.qpid.proton.codec.Data;
+
+-class CharElement extends AtomicElement<Integer>
++class CharElement extends AtomicElement<Character>
+ {
+-
+     private final int _value;
+
+     CharElement(Element parent, Element prev, int i)
+@@ -43,9 +42,9 @@ class CharElement extends AtomicElement<Integer>
+     }
+
+     @Override
+-    public Integer getValue()
++    public Character getValue()
+     {
+-        return _value;
++        return (char)_value;
+     }
+
+     @Override
+----END PATCH-----
+*/
+    /**
+     * Test that sending a map message to the test peer results in receipt of 
a message with
+     * an AmqpValue section containing a map which holds entries of the 
various supported entry
+     * types with the expected values.
+     */
+    @Test
+    public void testSendBasicMapMessage() throws Exception
+    {
+        try(TestAmqpPeer testPeer = new 
TestAmqpPeer(IntegrationTestFixture.PORT);)
+        {
+            Connection connection = _testFixture.establishConnecton(testPeer);
+            testPeer.expectBegin();
+            testPeer.expectSenderAttach();
+
+            Session session = connection.createSession(false, 
Session.AUTO_ACKNOWLEDGE);
+            Queue queue = session.createQueue("myQueue");
+            MessageProducer producer = session.createProducer(queue);
+
+
+            String myBoolKey = "myBool";
+            boolean myBool = true;
+            String myByteKey = "myByte";
+            byte myByte = 4;
+            String myBytesKey = "myBytes";
+            byte[] myBytes = myBytesKey.getBytes();
+            String myCharKey = "myChar";
+            char myChar = 'd';
+            String myDoubleKey = "myDouble";
+            double myDouble = 1234567890123456789.1234;
+            String myFloatKey = "myFloat";
+            float myFloat = 1.1F;
+            String myIntKey = "myInt";
+            int myInt = Integer.MAX_VALUE;
+            String myLongKey = "myLong";
+            long myLong = Long.MAX_VALUE;
+            String myShortKey = "myShort";
+            short myShort = 25;
+            String myStringKey = "myString";
+            String myString = myStringKey;
+
+            //Prepare a MapMessage to send to the test peer to send
+            MapMessage mapMessage = session.createMapMessage();
+
+            mapMessage.setBoolean(myBoolKey, myBool);
+            mapMessage.setByte(myByteKey, myByte);
+            mapMessage.setBytes(myBytesKey, myBytes);
+            //TODO: see note above: mapMessage.setChar(myCharKey, myChar);
+            mapMessage.setDouble(myDoubleKey, myDouble);
+            mapMessage.setFloat(myFloatKey, myFloat);
+            mapMessage.setInt(myIntKey, myInt);
+            mapMessage.setLong(myLongKey, myLong);
+            mapMessage.setShort(myShortKey, myShort);
+            mapMessage.setString(myStringKey, myString);
+
+            //prepare a matcher for the test peer to use to receive and verify 
the message
+            Map<String,Object> map = new LinkedHashMap<String,Object>();
+            map.put(myBoolKey, myBool);
+            map.put(myByteKey, myByte);
+            map.put(myBytesKey, new Binary(myBytes));//the underlying AMQP 
message uses Binary rather than byte[] directly.
+            //TODO: see note above: map.put(myCharKey, myChar);
+            map.put(myDoubleKey, myDouble);
+            map.put(myFloatKey, myFloat);
+            map.put(myIntKey, myInt);
+            map.put(myLongKey, myLong);
+            map.put(myShortKey, myShort);
+            map.put(myStringKey, myString);
+
+            MessageHeaderSectionMatcher headersMatcher = new 
MessageHeaderSectionMatcher(true).withDurable(equalTo(true));
+            MessageAnnotationsSectionMatcher msgAnnotationsMatcher = new 
MessageAnnotationsSectionMatcher(true);
+            MessagePropertiesSectionMatcher propertiesMatcher = new 
MessagePropertiesSectionMatcher(true);
+            TransferPayloadCompositeMatcher messageMatcher = new 
TransferPayloadCompositeMatcher();
+            messageMatcher.setHeadersMatcher(headersMatcher);
+            messageMatcher.setMessageAnnotationsMatcher(msgAnnotationsMatcher);
+            messageMatcher.setPropertiesMatcher(propertiesMatcher);
+            messageMatcher.setMessageContentMatcher(new 
EncodedAmqpValueMatcher(map));
+
+            //send the message
+            testPeer.expectTransfer(messageMatcher);
+            producer.send(mapMessage);
+        }
+    }
+}

Modified: 
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/impl/MapMessageImplTest.java
URL: 
http://svn.apache.org/viewvc/qpid/jms/trunk/src/test/java/org/apache/qpid/jms/impl/MapMessageImplTest.java?rev=1594627&r1=1594626&r2=1594627&view=diff
==============================================================================
--- 
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/impl/MapMessageImplTest.java 
(original)
+++ 
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/impl/MapMessageImplTest.java 
Wed May 14 16:00:06 2014
@@ -22,10 +22,12 @@ package org.apache.qpid.jms.impl;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
@@ -38,8 +40,11 @@ import javax.jms.MessageNotWriteableExce
 import org.apache.qpid.jms.QpidJmsTestCase;
 import org.apache.qpid.jms.engine.AmqpConnection;
 import org.apache.qpid.jms.engine.AmqpMapMessage;
+import 
org.apache.qpid.jms.test.testpeer.describedtypes.sections.AmqpValueDescribedType;
 import org.apache.qpid.proton.Proton;
+import org.apache.qpid.proton.amqp.Binary;
 import org.apache.qpid.proton.amqp.messaging.AmqpValue;
+import org.apache.qpid.proton.codec.impl.DataImpl;
 import org.apache.qpid.proton.engine.Delivery;
 import org.apache.qpid.proton.message.Message;
 import org.junit.Before;
@@ -64,6 +69,8 @@ public class MapMessageImplTest extends 
         Mockito.when(_mockSessionImpl.getDestinationHelper()).thenReturn(new 
DestinationHelper());
     }
 
+    // ======= general =========
+
     @Test
     public void testGetMapNamesWithNewMessageToSendReturnsEmptyEnumeration() 
throws Exception
     {
@@ -204,6 +211,40 @@ public class MapMessageImplTest extends 
     }
 
     /**
+     * When a map entry is not set, the behaviour of JMS specifies that it is 
equivalent to a null value,
+     * and the accessors should either return null, throw NPE, or behave in 
the same fashion as <primitive>.valueOf(String).
+     *
+     * Test that this is the case.
+     */
+    @Test
+    public void testGetMissingMapEntryResultsInExpectedBehaviour() throws 
Exception
+    {
+        MapMessageImpl mapMessageImpl = new 
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+        String name = "does_not_exist";
+
+        //expect null
+        assertNull(mapMessageImpl.getBytes(name));
+        assertNull(mapMessageImpl.getString(name));
+
+        //expect false from Boolean.valueOf(null).
+        assertFalse(mapMessageImpl.getBoolean(name));
+
+        //expect an NFE from the primitive integral <type>.valueOf(null) 
conversions
+        assertGetMapEntryThrowsNumberFormatException(mapMessageImpl, name, 
Byte.class);
+        assertGetMapEntryThrowsNumberFormatException(mapMessageImpl, name, 
Short.class);
+        assertGetMapEntryThrowsNumberFormatException(mapMessageImpl, name, 
Integer.class);
+        assertGetMapEntryThrowsNumberFormatException(mapMessageImpl, name, 
Long.class);
+
+        //expect an NPE from the primitive float, double, and char 
<type>.valuleOf(null) conversions
+        assertGetMapEntryThrowsNullPointerException(mapMessageImpl, name, 
Float.class);
+        assertGetMapEntryThrowsNullPointerException(mapMessageImpl, name, 
Double.class);
+        assertGetMapEntryThrowsNullPointerException(mapMessageImpl, name, 
Character.class);
+    }
+
+    // ======= object =========
+
+    /**
      * Test that the {@link MapMessageImpl#setObject(String, Object)} method 
rejects Objects of unexpected types
      */
     @Test
@@ -267,9 +308,11 @@ public class MapMessageImplTest extends 
         mapMessageImpl.setObject(keyName, entryValue);
         assertEquals(entryValue, mapMessageImpl.getObject(keyName));
 
-        entryValue = new byte[] { (byte)1, (byte) 0, (byte)1};
-        mapMessageImpl.setObject(keyName, entryValue);
-        assertEquals(entryValue, mapMessageImpl.getObject(keyName));
+        byte[] bytes = new byte[] { (byte)1, (byte) 0, (byte)1};
+        mapMessageImpl.setObject(keyName, bytes);
+        Object retrieved = mapMessageImpl.getObject(keyName);
+        assertTrue(retrieved instanceof byte[]);
+        assertTrue(Arrays.equals(bytes, (byte[])retrieved));
     }
 
     // ======= Strings =========
@@ -618,6 +661,9 @@ public class MapMessageImplTest extends 
 
     // ======= double  =========
 
+    /**
+     * Set a double, then retrieve it as all of the legal type combinations to 
verify it is parsed correctly
+     */
     @Test
     public void testSetDoubleGetLegal() throws Exception
     {
@@ -632,6 +678,9 @@ public class MapMessageImplTest extends 
         assertGetMapEntryEquals(mapMessageImpl, name, String.valueOf(value), 
String.class);
     }
 
+    /**
+     * Set a double, then retrieve it as all of the illegal type combinations 
to verify it fails as expected
+     */
     @Test
     public void testSetDoubleGetIllegal() throws Exception
     {
@@ -654,6 +703,9 @@ public class MapMessageImplTest extends 
 
     // ======= character =========
 
+    /**
+     * Set a char, then retrieve it as all of the legal type combinations to 
verify it is parsed correctly
+     */
     @Test
     public void testSetCharGetLegal() throws Exception
     {
@@ -668,6 +720,9 @@ public class MapMessageImplTest extends 
         assertGetMapEntryEquals(mapMessageImpl, name, String.valueOf(value), 
String.class);
     }
 
+    /**
+     * Set a char, then retrieve it as all of the illegal type combinations to 
verify it fails as expected
+     */
     @Test
     public void testSetCharGetIllegal() throws Exception
     {
@@ -685,26 +740,174 @@ public class MapMessageImplTest extends 
         assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Integer.class);
         assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Long.class);
         assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Float.class);
+        assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Double.class);
     }
 
+    //========= bytes ========
+
     /**
-     * Verify behaviour when retrieving a character with null value (i.e 
missing).
-     * Unlike all the other types, this should throw an explicit NPE.
+     * Set bytes, then retrieve it as all of the legal type combinations to 
verify it is parsed correctly
      */
     @Test
-    public void testGetCharWithMissingValueThrowsNPE() throws Exception
+    public void testSetBytesGetLegal() throws Exception
     {
         MapMessageImpl mapMessageImpl = new 
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
 
-        try
-        {
-            mapMessageImpl.getChar("does.not.exist");
-            fail("expected exception to be thrown");
-        }
-        catch(NullPointerException npe)
-        {
-            //expected
-        }
+        String name = "myName";
+        byte[] value = "myBytes".getBytes();
+
+        mapMessageImpl.setBytes(name, value);
+        assertTrue(Arrays.equals(value, mapMessageImpl.getBytes(name)));
+    }
+
+    /**
+     * Set bytes, then retrieve it as all of the illegal type combinations to 
verify it fails as expected
+     */
+    @Test
+    public void testSetBytesGetIllegal() throws Exception
+    {
+        MapMessageImpl mapMessageImpl = new 
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+        String name = "myName";
+        byte[] value = "myBytes".getBytes();
+
+        mapMessageImpl.setBytes(name, value);
+
+        assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Character.class);
+        assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
String.class);
+        assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Boolean.class);
+        assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Byte.class);
+        assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Short.class);
+        assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Integer.class);
+        assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Long.class);
+        assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Float.class);
+        assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name, 
Double.class);
+    }
+
+    /**
+     * Verify that for a message received with an AmqpValue containing a Map 
with a
+     * Binary entry value, we are able to read it back as a byte[].
+     */
+    @Test
+    public void testReceivedMapWithBinaryEntryReturnsByteArray() throws 
Exception
+    {
+        String myKey1 = "key1";
+        String bytesSource = "myBytesAmqpValue";
+
+        Map<String,Object> origMap = new HashMap<String,Object>();
+        byte[] bytes = bytesSource.getBytes();
+        origMap.put(myKey1, new Binary(bytes));
+
+        org.apache.qpid.proton.codec.Data payloadData = new DataImpl();
+        payloadData.putDescribedType(new AmqpValueDescribedType(origMap));
+        Binary b = payloadData.encode();
+
+        System.out.println("Using encoded AMQP message payload: " + b);
+
+        Message message = Proton.message();
+        int decoded = message.decode(b.getArray(), b.getArrayOffset(), 
b.getLength());
+        assertEquals(decoded, b.getLength());
+
+        AmqpMapMessage amqpMapMessage = new AmqpMapMessage(message, 
_mockDelivery, _mockAmqpConnection);
+
+        MapMessageImpl mapMessageImpl = new MapMessageImpl(amqpMapMessage, 
_mockSessionImpl,_mockConnectionImpl, null);
+
+        //retrieve the bytes using getBytes, check they match expectation
+        byte[] receivedBytes = mapMessageImpl.getBytes(myKey1);
+        assertTrue(Arrays.equals(bytes, receivedBytes));
+
+        //retrieve the bytes using getObject, check they match expectation
+        Object o = mapMessageImpl.getObject(myKey1);
+        assertTrue(o instanceof byte[]);
+        assertTrue(Arrays.equals(bytes, (byte[]) o));
+    }
+
+    /**
+     * Verify that setting bytes takes a copy of the array.
+     * Set bytes, then modify them, then retrieve the map entry and verify the 
two differ.
+     */
+    @Test
+    public void testSetBytesTakesSnapshot() throws Exception
+    {
+        MapMessageImpl mapMessageImpl = new 
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+        String name = "myName";
+        byte[] orig = "myBytes".getBytes();
+        byte[] copy = Arrays.copyOf(orig, orig.length);
+
+        //set the original bytes
+        mapMessageImpl.setBytes(name, orig);
+
+        //corrupt the original bytes
+        orig[0] = (byte)0;
+
+        //verify retrieving the bytes still matches the copy but not the 
original array
+        byte[] retrieved = mapMessageImpl.getBytes(name);
+        assertFalse(Arrays.equals(orig, retrieved));
+        assertTrue(Arrays.equals(copy, retrieved));
+    }
+
+    /**
+     * Verify that getting bytes returns a copy of the array.
+     * Set bytes, then get them, modify the retrieved value, then get them 
again and verify the two differ.
+     */
+    @Test
+    public void testGetBytesReturnsSnapshot() throws Exception
+    {
+        MapMessageImpl mapMessageImpl = new 
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+        String name = "myName";
+        byte[] orig = "myBytes".getBytes();
+
+        //set the original bytes
+        mapMessageImpl.setBytes(name, orig);
+
+        //retrieve them
+        byte[] retrieved1 = mapMessageImpl.getBytes(name);;
+
+        //corrupt the retrieved bytes
+        retrieved1[0] = (byte)0;
+
+        //verify retrieving the bytes again still matches the original array, 
but not the previously retrieved (and now corrupted) bytes.
+        byte[] retrieved2 = mapMessageImpl.getBytes(name);
+        assertTrue(Arrays.equals(orig, retrieved2));
+        assertFalse(Arrays.equals(retrieved1, retrieved2));
+    }
+
+    /**
+     * Verify that setting bytes takes a copy of the array.
+     * Set bytes, then modify them, then retrieve the map entry and verify the 
two differ.
+     */
+    @Test
+    public void testSetBytesWithOffsetAndLength() throws Exception
+    {
+        MapMessageImpl mapMessageImpl = new 
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+        String name = "myName";
+        byte[] orig = "myBytesAll".getBytes();
+
+        //extract the segment containing 'Bytes'
+        int offset = 2;
+        int length = 5;
+        byte[] segment = Arrays.copyOfRange(orig, offset, offset + length);
+
+        //set the same section from the original bytes
+        mapMessageImpl.setBytes(name, orig, offset, length);
+
+        //verify the retrieved bytes from the map match the segment but not 
the full original array
+        byte[] retrieved = mapMessageImpl.getBytes(name);
+        assertFalse(Arrays.equals(orig, retrieved));
+        assertTrue(Arrays.equals(segment, retrieved));
+    }
+
+    @Test
+    public void testSetBytesWithNull() throws Exception
+    {
+        MapMessageImpl mapMessageImpl = new 
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+        String name = "myName";
+        mapMessageImpl.setBytes(name, null);
+        assertNull(mapMessageImpl.getBytes(name));
     }
 
     //========= utility methods ========
@@ -733,6 +936,38 @@ public class MapMessageImplTest extends 
         }
     }
 
+    private void assertGetMapEntryThrowsNumberFormatException(MapMessageImpl 
testMessage,
+                                                              String name,
+                                                              Class<?> clazz) 
throws JMSException
+    {
+        try
+        {
+            getMapEntryUsingTypeMethod(testMessage, name, clazz);
+
+            fail("expected exception to be thrown");
+        }
+        catch(NumberFormatException nfe)
+        {
+            //expected
+        }
+    }
+
+    private void assertGetMapEntryThrowsNullPointerException(MapMessageImpl 
testMessage,
+                                                            String name,
+                                                            Class<?> clazz) 
throws JMSException
+    {
+        try
+        {
+            getMapEntryUsingTypeMethod(testMessage, name, clazz);
+
+            fail("expected exception to be thrown");
+        }
+        catch(NullPointerException npe)
+        {
+            //expected
+        }
+    }
+
     private Object getMapEntryUsingTypeMethod(MapMessageImpl testMessage, 
String name, Class<?> clazz) throws JMSException
     {
         if(clazz == Boolean.class)



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to