/*
 * 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.commons.chain2.web.servlet;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.chain2.Context;
import org.apache.commons.chain2.impl.ContextBaseTestCase;
import org.apache.commons.chain2.web.WebContext;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;


/**
 * Extension of <code>ContextBaseTestCase</code> to validate the
 * extra functionality of this implementation.
 */

public final class ServletWebContextBaseTestCase extends ContextBaseTestCase {


    // ----------------------------------------------------- Instance Variables


    // Servlet API Objects
    private ServletContext scontext = null;
    private HttpServletRequest request = null;
    private HttpServletResponse response = null;
    private HttpSession session = null;


    // -------------------------------------------------- Overall Test Methods


    /**
     * Set up instance variables required by this test case.
     */
    @Before
    @Override
    public void setUp() {
        this.scontext = new MockServletContext();
        this.scontext.setAttribute("akey1", "avalue1");
        this.scontext.setAttribute("akey2", "avalue2");
        this.scontext.setAttribute("akey3", "avalue3");
        this.scontext.setAttribute("akey4", "avalue4");
        ((MockServletContext) this.scontext).addInitParameter("ikey1", "ivalue1");
        ((MockServletContext) this.scontext).addInitParameter("ikey2", "ivalue2");
        ((MockServletContext) this.scontext).addInitParameter("ikey3", "ivalue3");
        this.session = new MockHttpSession(this.scontext);
        this.session.setAttribute("skey1", "svalue1");
        this.session.setAttribute("skey2", "svalue2");
        this.session.setAttribute("skey3", "svalue3");
        this.request = new MockHttpServletRequest("/context", "/servlet",
                                             "/path/info", "a=b&c=d",
                                             this.session);
        this.request.setAttribute("rkey1", "rvalue1");
        this.request.setAttribute("rkey2", "rvalue2");
        ((MockHttpServletRequest) this.request).addHeader("hkey1", "hvalue1");
        ((MockHttpServletRequest) this.request).addHeader("hkey2", "hvalue2a");
        ((MockHttpServletRequest) this.request).addHeader("hkey2", "hvalue2b");
        ((MockHttpServletRequest) this.request).addParameter("pkey1", "pvalue1");
        ((MockHttpServletRequest) this.request).addParameter("pkey2", "pvalue2a");
        ((MockHttpServletRequest) this.request).addParameter("pkey2", "pvalue2b");
        ((MockHttpServletRequest) this.request).addCookie("ckey1", "cvalue1");
        ((MockHttpServletRequest) this.request).addCookie("ckey2", "cvalue2");
        this.response = new MockHttpServletResponse();
        super.setContext(createContext());
    }


    /**
     * Tear down instance variables required by this test case.
     */
    @After
    @Override
    public void tearDown() {
        this.scontext = null;
        this.session = null;
        this.request = null;
        this.response = null;
        super.setContext(null);
    }


    // ------------------------------------------------ Individual Test Methods


    /**
     * Test getApplicationScope()
     * @throws Exception Exception
     */
    @Test
    public void testApplicationScope() throws Exception {

        Map<String, Object> map =
                ((WebContext<String, Object>) super.getContext()).getApplicationScope();
        assertNotNull(map);

        // Initial contents
        checkMapSize(map, 4);
        assertEquals("avalue1", map.get("akey1"));
        assertEquals("avalue2", map.get("akey2"));
        assertEquals("avalue3", map.get("akey3"));
        assertEquals("avalue4", map.get("akey4"));

        // Transparency - entrySet()
        checkEntrySet(map, true);

        // Transparency - removal via web object
        this.scontext.removeAttribute("akey1");
        checkMapSize(map, 3);
        assertNull(map.get("akey1"));

        // Transparency - removal via map
        map.remove("akey2");
        checkMapSize(map, 2);
        assertNull(this.scontext.getAttribute("akey2"));

        // Transparency - addition via web object
        this.scontext.setAttribute("akeyA", "avalueA");
        checkMapSize(map, 3);
        assertEquals("avalueA", map.get("akeyA"));

        // Transparency - addition via map
        map.put("akeyB", "avalueB");
        checkMapSize(map, 4);
        assertEquals("avalueB", this.scontext.getAttribute("akeyB"));

        // Transparency - replacement via web object
        this.scontext.setAttribute("akeyA", "newvalueA");
        checkMapSize(map, 4);
        assertEquals("newvalueA", map.get("akeyA"));

        // Transparency - replacement via map
        map.put("akeyB", "newvalueB");
        assertEquals(4, map.size());
        assertEquals("newvalueB", this.scontext.getAttribute("akeyB"));

        // Clearing the map
        map.clear();
        checkMapSize(map, 0);

        // Test putAll()
        Map<String, Object> values = new HashMap<>();
        values.put("1", "One");
        values.put("2", "Two");
        map.putAll(values);
        assertEquals("putAll(1)", "One", map.get("1"));
        assertEquals("putAll(2)", "Two", map.get("2"));
        checkMapSize(map, 2);
    }


    /**
     * Test equals() and hashCode()
     * Copied from ContextBaseTestCase with customized creation of "other"
     */
    @Test
    @Override
    public void testEquals() {

        // FIXME - ServletWebContextBase needs a better equals()

        // Compare to self
        assertTrue(super.getContext().equals(super.getContext()));
        assertTrue(super.getContext().hashCode() == super.getContext().hashCode());

        // Compare to equivalent instance
        Context<String, Object> other =
                new ServletWebContextBase(this.scontext, this.request, this.response);
        // assertTrue(context.equals(other));
        assertTrue(super.getContext().hashCode() == other.hashCode());

        // Compare to non-equivalent instance - other modified
        other.put("bop", "bop value");
        // assertTrue(!context.equals(other));
        assertTrue(super.getContext().hashCode() != other.hashCode());

        // Compare to non-equivalent instance - self modified
        other = new ServletWebContextBase(this.scontext, this.request, this.response);
        super.getContext().put("bop", "bop value");
        // assertTrue(!context.equals(other));
        assertTrue(super.getContext().hashCode() != other.hashCode());

    }


    /**
     * Test getHeader()
     * @throws Exception Exception
     */
    @Test
    public void testHeader() throws Exception {

        Map<String, String> map = ((WebContext<String, Object>) super.getContext()).getHeader();
        assertNotNull(map);

        // Initial contents
        checkMapSize(map, 2);
        assertEquals("hvalue1", map.get("hkey1"));
        assertEquals("hvalue2a", map.get("hkey2"));
        assertTrue(map.containsKey("hkey1"));
        assertTrue(map.containsKey("hkey2"));
        assertTrue(map.containsValue("hvalue1"));
        assertTrue(map.containsValue("hvalue2a"));

        // Transparency - entrySet()
        checkEntrySet(map, false);

        // Unsupported operations on read-only map
        try {
            map.clear();
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.put("hkey3", "hvalue3");
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.putAll(new HashMap<String, String>());
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.remove("hkey1");
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
    }


    /**
     * Test getHeaderValues()
     * @throws Exception Exception
     */
    @Test
    public void testHeaderValues() throws Exception {

        Map<String, String[]> map =
                ((WebContext<String, Object>) super.getContext()).getHeaderValues();
        assertNotNull(map);

        // Initial contents
        checkMapSize(map, 2);
        Object value1 = map.get("hkey1");
        assertNotNull(value1);
        String[] values1 = (String[]) value1;
        assertEquals(1, values1.length);
        assertEquals("hvalue1", values1[0]);
        Object value2 = map.get("hkey2");
        assertNotNull(value2);
        String[] values2 = (String[]) value2;
        assertEquals(2, values2.length);
        assertEquals("hvalue2a", values2[0]);
        assertEquals("hvalue2b", values2[1]);
        assertTrue(map.containsKey("hkey1"));
        assertTrue(map.containsKey("hkey2"));
        assertTrue(map.containsValue(values1));
        assertTrue(map.containsValue(values2));

        // Transparency - entrySet()
        checkEntrySet(map, false);

        // Unsupported operations on read-only map
        try {
            map.clear();
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.put("hkey3", values2);
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.putAll(new HashMap<String, String[]>());
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.remove("hkey1");
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }

    }


    /**
     * Test getInitParam()
     * @throws Exception Exception
     */
    @Test
    public void testInitParam() throws Exception {

        Map<String, String> map = ((WebContext<String, Object>) super.getContext()).getInitParam();
        assertNotNull(map);

        // Initial contents
        checkMapSize(map, 3);
        assertEquals("ivalue1", map.get("ikey1"));
        assertEquals("ivalue2", map.get("ikey2"));
        assertEquals("ivalue3", map.get("ikey3"));
        assertTrue(map.containsKey("ikey1"));
        assertTrue(map.containsKey("ikey2"));
        assertTrue(map.containsKey("ikey3"));
        assertTrue(map.containsValue("ivalue1"));
        assertTrue(map.containsValue("ivalue2"));
        assertTrue(map.containsValue("ivalue3"));

        // Transparency - entrySet()
        checkEntrySet(map, false);

        // Unsupported operations on read-only map
        try {
            map.clear();
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.put("ikey4", "ivalue4");
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.putAll(new HashMap<String, String>());
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.remove("ikey1");
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }

    }


    /**
     * Test getParam()
     * @throws Exception Exception
     */
    @Test
    public void testParam() throws Exception {

        Map<String, String> map = ((WebContext<String, Object>) super.getContext()).getParam();
        assertNotNull(map);

        // Initial contents
        checkMapSize(map, 2);
        assertEquals("pvalue1", map.get("pkey1"));
        assertEquals("pvalue2a", map.get("pkey2"));
        assertTrue(map.containsKey("pkey1"));
        assertTrue(map.containsKey("pkey2"));
        assertTrue(map.containsValue("pvalue1"));
        assertTrue(map.containsValue("pvalue2a"));

        checkEntrySet(map, false);

        // Unsupported operations on read-only map
        try {
            map.clear();
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.put("pkey3", "pvalue3");
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.putAll(new HashMap<String, String>());
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.remove("pkey1");
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }

    }


    /**
     * Test getParamValues()
     */
    @Test
    public void testParamValues() {

        Map<String, String[]> map =
                ((WebContext<String, Object>) super.getContext()).getParamValues();
        assertNotNull(map);

        // Initial contents
        checkMapSize(map, 2);
        Object value1 = map.get("pkey1");
        assertNotNull(value1);
        String[] values1 = (String[]) value1;
        assertEquals(1, values1.length);
        assertEquals("pvalue1", values1[0]);
        Object value2 = map.get("pkey2");
        assertNotNull(value2);
        String[] values2 = (String[]) value2;
        assertEquals(2, values2.length);
        assertEquals("pvalue2a", values2[0]);
        assertEquals("pvalue2b", values2[1]);
        assertTrue(map.containsKey("pkey1"));
        assertTrue(map.containsKey("pkey2"));
        assertTrue(map.containsValue(values1));
        assertTrue(map.containsValue(values2));

        // Unsupported operations on read-only map
        try {
            map.clear();
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.put("pkey3", values2);
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.putAll(new HashMap<String, String[]>());
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.remove("pkey1");
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }

    }


    /**
     * Test getCookies()
     */
    @Test
    public void testCookies() {

        Map<String, Cookie> map = ((WebContext<String, Object>) super.getContext()).getCookies();
        assertNotNull(map);

        // Initial contents
        checkMapSize(map, 2);
        Cookie cookie1 = map.get("ckey1");
        assertNotNull(cookie1);
        assertEquals("cvalue1", cookie1.getValue());
        Cookie cookie2 = map.get("ckey2");
        assertNotNull(cookie2);
        assertEquals("cvalue2", cookie2.getValue());
        assertTrue(map.containsKey("ckey1"));
        assertTrue(map.containsKey("ckey2"));
        assertTrue(map.containsValue(cookie1));
        assertTrue(map.containsValue(cookie2));

        // Unsupported operations on read-only map
        try {
            map.clear();
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        /* remove
           map is typed, that assignment is not possible by definition
        try {
            map.put("ckey3", "ZZZ");
            fail("Should have thrown UnsupportedOperationException");
        } catch (ClassCastException e) {
            // expected result
        }
        */
        try {
            map.putAll(new HashMap<String, Cookie>());
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }
        try {
            map.remove("ckey1");
            fail("Should have thrown UnsupportedOperationException");
        } catch (final UnsupportedOperationException e) {
            // expected result
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }

    }

    /**
     * Test state of newly created instance
     */
    @Test
    @Override
    public void testPristine() {

        super.testPristine();
        ServletWebContext<String, Object> swcontext =
                (ServletWebContext<String, Object>) super.getContext();

        // Properties should all be non-null
        assertNotNull(swcontext.getApplicationScope());
        assertNotNull(swcontext.getHeader());
        assertNotNull(swcontext.getHeaderValues());
        assertNotNull(swcontext.getInitParam());
        assertNotNull(swcontext.getParam());
        assertNotNull(swcontext.getParamValues());
        assertNotNull(swcontext.getCookies());
        assertNotNull(swcontext.getRequestScope());
        assertNotNull(swcontext.getSessionScope());

        // Attribute-property transparency
        assertTrue(swcontext.getApplicationScope() == swcontext.get("applicationScope"));
        assertTrue(swcontext.getHeader() == swcontext.get("header"));
        assertTrue(swcontext.getHeaderValues() == swcontext.get("headerValues"));
        assertTrue(swcontext.getInitParam() == swcontext.get("initParam"));
        assertTrue(swcontext.getParam() == swcontext.get("param"));
        assertTrue(swcontext.getParamValues() == swcontext.get("paramValues"));
        assertTrue(swcontext.getCookies() == swcontext.get("cookies"));
        assertTrue(swcontext.getRequestScope() == swcontext.get("requestScope"));
        assertTrue(swcontext.getSessionScope() == swcontext.get("sessionScope"));

    }


    /**
     * Test release()
     */
    @Test
    public void testRelease() {

        ServletWebContext<String, Object> swcontext =
                (ServletWebContext<String, Object>) super.getContext();
        swcontext.release();

        // Properties should all be null
        assertNull(swcontext.getApplicationScope());
        assertNull(swcontext.getHeader());
        assertNull(swcontext.getHeaderValues());
        assertNull(swcontext.getInitParam());
        assertNull(swcontext.getParam());
        assertNull(swcontext.getParamValues());
        assertNull(swcontext.getCookies());
        assertNull(swcontext.getRequestScope());
        assertNull(swcontext.getSessionScope());

        // Attributes should all be null
        assertNull(swcontext.get("applicationScope"));
        assertNull(swcontext.get("header"));
        assertNull(swcontext.get("headerValues"));
        assertNull(swcontext.get("initParam"));
        assertNull(swcontext.get("param"));
        assertNull(swcontext.get("paramValues"));
        assertNull(swcontext.get("cookies"));
        assertNull(swcontext.get("requestScope"));
        assertNull(swcontext.get("sessionScope"));

    }


    /**
     * Test getRequestScope()
     * @throws Exception Exception
     */
    @Test
    public void testRequestScope() throws Exception {

        Map<String, Object> map =
                ((WebContext<String, Object>) super.getContext()).getRequestScope();
        assertNotNull(map);

        // Initial contents
        checkMapSize(map, 2);
        assertEquals("rvalue1", map.get("rkey1"));
        assertEquals("rvalue2", map.get("rkey2"));

        // Transparency - entrySet()
        checkEntrySet(map, true);

        // Transparency - removal via web object
        this.request.removeAttribute("rkey1");
        checkMapSize(map, 1);
        assertNull(map.get("rkey1"));

        // Transparency - removal via map
        map.remove("rkey2");
        checkMapSize(map, 0);
        assertNull(this.request.getAttribute("rkey2"));

        // Transparency - addition via web object
        this.request.setAttribute("rkeyA", "rvalueA");
        checkMapSize(map, 1);
        assertEquals("rvalueA", map.get("rkeyA"));

        // Transparency - addition via map
        map.put("rkeyB", "rvalueB");
        checkMapSize(map, 2);
        assertEquals("rvalueB", this.request.getAttribute("rkeyB"));

        // Transparency - replacement via web object
        this.request.setAttribute("rkeyA", "newvalueA");
        checkMapSize(map, 2);
        assertEquals("newvalueA", map.get("rkeyA"));

        // Transparency - replacement via map
        map.put("rkeyB", "newvalueB");
        checkMapSize(map, 2);
        assertEquals("newvalueB", this.request.getAttribute("rkeyB"));

        // Clearing the map
        map.clear();
        checkMapSize(map, 0);

        // Test putAll()
        Map<String, Object> values = new HashMap<>();
        values.put("1", "One");
        values.put("2", "Two");
        map.putAll(values);
        assertEquals("putAll(1)", "One", map.get("1"));
        assertEquals("putAll(2)", "Two", map.get("2"));
        checkMapSize(map, 2);

    }


    /**
     * Test getSessionScope()
     * @throws Exception Exception
     */
    @Test
    public void testSessionScope() throws Exception {

        Map<String, Object> map =
                ((WebContext<String, Object>) super.getContext()).getSessionScope();
        assertNotNull(map);

        // Initial contents
        checkMapSize(map, 3);
        assertEquals("svalue1", map.get("skey1"));
        assertEquals("svalue2", map.get("skey2"));
        assertEquals("svalue3", map.get("skey3"));

        // Transparency - entrySet()
        checkEntrySet(map, true);

        // Transparency - removal via web object
        this.session.removeAttribute("skey1");
        checkMapSize(map, 2);
        assertNull(map.get("skey1"));

        // Transparency - removal via map
        map.remove("skey2");
        checkMapSize(map, 1);
        assertNull(this.session.getAttribute("skey2"));

        // Transparency - addition via web object
        this.session.setAttribute("skeyA", "svalueA");
        checkMapSize(map, 2);
        assertEquals("svalueA", map.get("skeyA"));

        // Transparency - addition via map
        map.put("skeyB", "svalueB");
        checkMapSize(map, 3);
        assertEquals("svalueB", this.session.getAttribute("skeyB"));

        // Transparency - replacement via web object
        this.session.setAttribute("skeyA", "newvalueA");
        checkMapSize(map, 3);
        assertEquals("newvalueA", map.get("skeyA"));

        // Transparency - replacement via map
        map.put("skeyB", "newvalueB");
        checkMapSize(map, 3);
        assertEquals("newvalueB", this.session.getAttribute("skeyB"));

        // Clearing the map
        map.clear();
        checkMapSize(map, 0);

        // Test putAll()
        Map<String, Object> values = new HashMap<>();
        values.put("1", "One");
        values.put("2", "Two");
        map.putAll(values);
        assertEquals("putAll(1)", "One", map.get("1"));
        assertEquals("putAll(2)", "Two", map.get("2"));
        checkMapSize(map, 2);

    }


    /**
     * Test getSessionScope() without Session
     */
    @Test
    public void testSessionScopeWithoutSession() {

        // Create a Context without a session
        ServletWebContext<String, Object> ctx = new ServletWebContextBase(this.scontext,
                new MockHttpServletRequest(), this.response);
        assertNull("Session(A)", ctx.getRequest().getSession(false));

        // Get the session Map & check session doesn't exist
        Map<String, Object> sessionMap = ctx.getSessionScope();
        assertNull("Session(B)", ctx.getRequest().getSession(false));
        assertNotNull("Session Map(A)", sessionMap);

        // test clear()
        sessionMap.clear();
        assertNull("Session(C)", ctx.getRequest().getSession(false));

        // test containsKey()
        assertFalse("containsKey()", sessionMap.containsKey("ABC"));
        assertNull("Session(D)", ctx.getRequest().getSession(false));

        // test containsValue()
        assertFalse("containsValue()", sessionMap.containsValue("ABC"));
        assertNull("Session(E)", ctx.getRequest().getSession(false));

        // test entrySet()
        Set<Entry<String, Object>> entrySet = sessionMap.entrySet();
        assertNotNull("entrySet", entrySet);
        assertEquals("entrySet Size", 0, entrySet.size());
        assertNull("Session(F)", ctx.getRequest().getSession(false));

        // test equals()
        assertFalse("equals()", sessionMap.equals("ABC"));
        assertNull("Session(G)", ctx.getRequest().getSession(false));

        // test get()
        assertNull("get()", sessionMap.get("ABC"));
        assertNull("Session(H)", ctx.getRequest().getSession(false));

        // test hashCode()
        sessionMap.hashCode();
        assertNull("Session(I)", ctx.getRequest().getSession(false));

        // test isEmpty()
        assertTrue("isEmpty()", sessionMap.isEmpty());
        assertNull("Session(J)", ctx.getRequest().getSession(false));

        // test keySet()
        Set<String> keySet = sessionMap.keySet();
        assertNotNull("keySet", keySet);
        assertEquals("keySet Size", 0, keySet.size());
        assertNull("Session(K)", ctx.getRequest().getSession(false));

        // test putAll() with an empty Map
        sessionMap.putAll(new HashMap<String, Object>());
        assertNull("Session(L)", ctx.getRequest().getSession(false));

        // test remove()
        assertNull("remove()", sessionMap.remove("ABC"));
        assertNull("Session(M)", ctx.getRequest().getSession(false));

        // test size()
        assertEquals("size() Size", 0, sessionMap.size());
        assertNull("Session(N)", ctx.getRequest().getSession(false));

        // test values()
        Collection<Object> values = sessionMap.values();
        assertNotNull("values", values);
        assertEquals("values Size", 0, values.size());
        assertNull("Session(O)", ctx.getRequest().getSession(false));

        // test put()
        try {
            assertNull("put()", sessionMap.put("ABC", "XYZ"));
            assertNotNull("Session(P)", ctx.getRequest().getSession(false));
        } catch (final UnsupportedOperationException e) {
            // expected: currently MockHttpServletRequest throws this
            //           when trying to create a HttpSession
            LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
        }

    }


    // ------------------------------------------------------- Protected Methods


    protected void checkMapSize(final Map<?, ?> map, final int size) {
        // Check reported size of the map
        assertEquals(size, map.size());
        // Iterate over key set
        int nk = 0;
        Iterator<?> keys = map.keySet().iterator();
        while (keys.hasNext()) {
            keys.next();
            nk++;
        }
        assertEquals(size, nk);
        // Iterate over entry set
        int nv = 0;
        Iterator<?> values = map.entrySet().iterator();
        while (values.hasNext()) {
            values.next();
            nv++;
        }
        assertEquals(size, nv);
        // Count the values
        assertEquals(size, map.values().size());
    }

    // Test to ensure proper entrySet() and are modifiable optionally
    protected <T> void checkEntrySet(final Map<String, T> map,
            final boolean modifiable, final T... val) throws Exception {
        assertTrue(map.size() > 1);
        Set<Entry<String, T>> entries = map.entrySet();
        assertTrue(map.size() == entries.size());
        Entry<String, T> o = entries.iterator().next();

        if (!modifiable) {
            try {
                if (!val.getClass().isArray()) {
                    o.setValue((T) val.getClass().getConstructor().newInstance());
                } else {
                    o.setValue((T) Array.newInstance(val.getClass().getComponentType(), 0));
                }
                fail("Should have thrown UnsupportedOperationException");
            } catch (final UnsupportedOperationException e) {
                // expected result
                LogFactory.getLog(ServletWebContextBaseTestCase.class).debug(e.getMessage());
            }
        } else {
            // Should pass and not throw UnsupportedOperationException
            if (!val.getClass().isArray()) {
                o.setValue((T) val.getClass().getConstructor().newInstance());
            } else {
                o.setValue((T) Array.newInstance(val.getClass().getComponentType(), 0));
            }
        }
    }

    // Create a new instance of the appropriate Context type for this test case
    @Override
    protected Context<String, Object> createContext() {
        return new ServletWebContextBase(this.scontext, this.request, this.response);
    }

}
