/*
 * 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.openjpa.util;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.AbstractMap;
import java.util.AbstractSequentialList;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.TreeMap;
import java.util.Vector;

import org.apache.openjpa.util.Proxy;
import junit.framework.TestCase;
import junit.textui.TestRunner;

/**
 * Test proxies generated by the proxy manager.
 * 
 * @author Abe White
 */
public class TestProxyManager extends TestCase {

    private ProxyManagerImpl _mgr;

    public void setUp() {
        _mgr = new ProxyManagerImpl();
    }

    public void testCopyLists() {
        List orig = new ArrayList();
        populate(orig);
        assertListsEqual(orig, (List) _mgr.copyCollection(orig));

        orig = new LinkedList();
        populate(orig);
        assertListsEqual(orig, (List) _mgr.copyCollection(orig));

        orig = new CustomList();
        populate(orig);
        assertListsEqual(orig, (List) _mgr.copyCollection(orig));
    }

    /**
     * Populate the given list with arbitrary data.
     */
    private static void populate(Collection coll) {
        coll.add(new Integer(1));
        coll.add("foo");
        coll.add(new Long(99));
        coll.add("bar");
        coll.add(new Short((short) 50));
    }

    /**
     * Assert that the given lists are exactly the same.
     */
    private static void assertListsEqual(List l1, List l2) {
        assertTrue(l1.getClass() == l2.getClass());
        assertEquals(l1.size(), l2.size());
        for (int i = 0; i < l1.size(); i++)
            assertTrue(l1.get(i) + " != " + l2.get(i), l1.get(i) == l2.get(i));
    }

    public void testCopySets() {
        Set orig = new HashSet();
        populate(orig);
        assertSetsEqual(orig, (Set) _mgr.copyCollection(orig));

        orig = new CustomSet();
        populate(orig);
        assertSetsEqual(orig, (Set) _mgr.copyCollection(orig));
    }

    /**
     * Assert that the given sets are exactly the same.
     */
    private static void assertSetsEqual(Set s1, Set s2) {
        assertTrue(s1.getClass() == s2.getClass());
        assertEquals(s1.size(), s2.size());
        assertEquals(s1, s2);
    }

    public void testCopySortedSets() {
        SortedSet orig = new TreeSet();
        populate(orig);
        assertSortedSetsEqual(orig, (SortedSet) _mgr.copyCollection(orig));

        orig = new TreeSet(new CustomComparator());
        populate(orig);
        assertSortedSetsEqual(orig, (SortedSet) _mgr.copyCollection(orig));

        orig = new CustomSortedSet();
        populate(orig);
        assertSortedSetsEqual(orig, (SortedSet) _mgr.copyCollection(orig));

        orig = new CustomComparatorSortedSet(new CustomComparator());
        populate(orig);
        assertSortedSetsEqual(orig, (SortedSet) _mgr.copyCollection(orig));
    }

    /**
     * Populate the given sorted set with arbitrary data.
     */
    private static void populate(SortedSet coll) {
        coll.add(new Integer(1));
        coll.add(new Integer(99));
        coll.add(new Integer(50));
        coll.add(new Integer(-5));
        coll.add(new Integer(10));
    }

    /**
     * Assert that the given sets are exactly the same.
     */
    private static void assertSortedSetsEqual(SortedSet s1, SortedSet s2) {
        assertTrue(s1.getClass() == s2.getClass());
        assertSortedSetsEquals(s1, s2);
    }

    /**
     * Assert that the given sets are exactly the same (minus the class).
     */
    private static void assertSortedSetsEquals(SortedSet s1, SortedSet s2) {
        assertEquals(s1.comparator(), s2.comparator());
        assertEquals(s1.size(), s2.size());
        Iterator itr1 = s1.iterator();
        Iterator itr2 = s2.iterator();
        while (itr1.hasNext())
            assertTrue(itr1.next() == itr2.next());
        assertTrue(s1.equals(s2));
    }

    public void testCopyNullCollection() {
        assertNull(_mgr.copyCollection(null));
    }

    public void testCopyProxyCollection() {
        List orig = (List) _mgr.newCollectionProxy(ArrayList.class, null, null, true);
        populate(orig);
        assertListsEqual(new ArrayList(orig), (List) _mgr.copyCollection(orig));

        TreeSet torig = (TreeSet) _mgr.newCollectionProxy(TreeSet.class, null, new CustomComparator(), true);
        assertTrue(torig.comparator() instanceof CustomComparator);
        populate(torig);
        assertSortedSetsEqual(new TreeSet(torig), (SortedSet) _mgr.copyCollection(torig));
    }

    public void testCloneProxyCollection() {
        // List doesn't support clone()

        TreeSet torig = (TreeSet) _mgr.newCollectionProxy(TreeSet.class, null, new CustomComparator(), true);
        assertTrue(torig.comparator() instanceof CustomComparator);
        populate(torig);
        assertSortedSetsEquals(new TreeSet(torig), (SortedSet) torig.clone());
    }

    public void testListMethodsProxied() throws Exception {
        Class proxy = _mgr.newCollectionProxy(ArrayList.class, null, null, true).getClass();
        assertListMethodsProxied(proxy);

        proxy = _mgr.newCollectionProxy(CustomList.class, null, null, true).getClass();
        assertListMethodsProxied(proxy);
    }

    /**
     * Assert that the methods we need to override to dirty the collection are proxied appropriately.
     */
    private void assertCollectionMethodsProxied(Class cls) throws Exception {
        assertNotNull(cls.getDeclaredMethod("add", new Class[] { Object.class }));
        assertNotNull(cls.getDeclaredMethod("addAll", new Class[] { Collection.class }));
        assertNotNull(cls.getDeclaredMethod("clear", (Class[]) null));
        assertNotNull(cls.getDeclaredMethod("iterator", (Class[]) null));
        assertNotNull(cls.getDeclaredMethod("remove", new Class[] { Object.class }));
        assertNotNull(cls.getDeclaredMethod("removeAll", new Class[] { Collection.class }));
        assertNotNull(cls.getDeclaredMethod("retainAll", new Class[] { Collection.class }));

        // check a non-mutating method to make sure we're not just proxying
        // everything
        try {
            cls.getDeclaredMethod("contains", new Class[] { Object.class });
            fail("Proxied non-mutating method.");
        } catch (NoSuchMethodException nsme) {
            // expected
        }
    }

    /**
     * Assert that the methods we need to override to dirty the list are proxied appropriately.
     */
    private void assertListMethodsProxied(Class cls) throws Exception {
        assertCollectionMethodsProxied(cls);
        assertNotNull(cls.getDeclaredMethod("add", new Class[] { int.class, Object.class }));
        assertNotNull(cls.getDeclaredMethod("addAll", new Class[] { int.class, Collection.class }));
        assertNotNull(cls.getDeclaredMethod("listIterator", (Class[]) null));
        assertNotNull(cls.getDeclaredMethod("listIterator", new Class[] { int.class }));
        assertNotNull(cls.getDeclaredMethod("remove", new Class[] { int.class }));
        assertNotNull(cls.getDeclaredMethod("set", new Class[] { int.class, Object.class }));
    }

    public void testSetMethodsProxied() throws Exception {
        Class proxy = _mgr.newCollectionProxy(HashSet.class, null, null, true).getClass();
        assertCollectionMethodsProxied(proxy);

        proxy = _mgr.newCollectionProxy(CustomSet.class, null, null, true).getClass();
        assertCollectionMethodsProxied(proxy);

        proxy = _mgr.newCollectionProxy(CustomSortedSet.class, null, null, true).getClass();
        assertCollectionMethodsProxied(proxy);

        proxy = _mgr.newCollectionProxy(CustomComparatorSortedSet.class, null, new CustomComparator(), true).getClass();
        assertCollectionMethodsProxied(proxy);
    }

    public void testQueueMethodsProxied() throws Exception {
        Class queue = getQueueClass();
        if (queue == null)
            return;

        Class proxy = _mgr.newCollectionProxy(LinkedList.class, null, null, true).getClass();
        assertTrue(queue.isAssignableFrom(proxy));
        assertCollectionMethodsProxied(proxy);
        assertNotNull(proxy.getDeclaredMethod("offer", new Class[] { Object.class }));
        assertNotNull(proxy.getDeclaredMethod("poll", (Class[]) null));
        assertNotNull(proxy.getDeclaredMethod("remove", (Class[]) null));
        try {
            proxy.getDeclaredMethod("peek", (Class[]) null);
            fail("Proxied non-mutating method.");
        } catch (NoSuchMethodException nsme) {
            // expected
        }
    }

    public void testLinkedListMethodsProxied() throws Exception {
        Class proxy = _mgr.newCollectionProxy(LinkedList.class, null, null, true).getClass();
        assertListMethodsProxied(proxy);
        assertNotNull(proxy.getDeclaredMethod("addFirst", new Class[] { Object.class }));
        assertNotNull(proxy.getDeclaredMethod("addLast", new Class[] { Object.class }));
        assertNotNull(proxy.getDeclaredMethod("removeFirst", (Class[]) null));
        assertNotNull(proxy.getDeclaredMethod("removeLast", (Class[]) null));
    }

    public void testVectorMethodsProxied() throws Exception {
        Class proxy = _mgr.newCollectionProxy(Vector.class, null, null, true).getClass();
        assertListMethodsProxied(proxy);
        assertNotNull(proxy.getDeclaredMethod("addElement", new Class[] { Object.class }));
        assertNotNull(proxy.getDeclaredMethod("insertElementAt", new Class[] { Object.class, int.class }));
        assertNotNull(proxy.getDeclaredMethod("removeAllElements", (Class[]) null));
        assertNotNull(proxy.getDeclaredMethod("removeElement", new Class[] { Object.class }));
        assertNotNull(proxy.getDeclaredMethod("removeElementAt", new Class[] { int.class }));
        assertNotNull(proxy.getDeclaredMethod("setElementAt", new Class[] { Object.class, int.class }));
    }

    public void testListChangeTracker() {
        Proxy coll = _mgr.newCollectionProxy(ArrayList.class, null, null, true);
        assertNotNull(coll);
        assertNotNull(coll.getChangeTracker());
        assertTrue(coll.getChangeTracker() instanceof CollectionChangeTrackerImpl);
        CollectionChangeTrackerImpl ct = (CollectionChangeTrackerImpl) coll.getChangeTracker();
        assertTrue(ct.allowsDuplicates());
        assertTrue(ct.isOrdered());
    }

    public void testSetChangeTracker() {
        Proxy coll = _mgr.newCollectionProxy(HashSet.class, null, null, true);
        assertNotNull(coll);
        assertNotNull(coll.getChangeTracker());
        assertTrue(coll.getChangeTracker() instanceof CollectionChangeTrackerImpl);
        CollectionChangeTrackerImpl ct = (CollectionChangeTrackerImpl) coll.getChangeTracker();
        assertFalse(ct.allowsDuplicates());
        assertFalse(ct.isOrdered());
    }

    public void testCollectionInterfaceProxy() {
        Proxy coll = _mgr.newCollectionProxy(Collection.class, null, null, true);
        assertNotNull(coll);
    }

    public void testListInterfaceProxy() {
        Proxy coll = _mgr.newCollectionProxy(List.class, null, null, true);
        assertNotNull(coll);
        assertTrue(coll instanceof List);
    }

    public void testSetInterfaceProxy() {
        Proxy coll = _mgr.newCollectionProxy(Set.class, null, null, true);
        assertNotNull(coll);
        assertTrue(coll instanceof Set);
        assertFalse(coll instanceof SortedSet);
    }

    public void testSortedSetInterfaceProxy() {
        Proxy coll = _mgr.newCollectionProxy(SortedSet.class, null, null, true);
        assertNotNull(coll);
        assertTrue(coll instanceof SortedSet);
    }

    public void testQueueInterfaceProxy() {
        Class queue = getQueueClass();
        if (queue == null)
            return;

        Proxy coll = _mgr.newCollectionProxy(queue, null, null, true);
        assertNotNull(coll);
        assertTrue(queue.isInstance(coll));
    }

    public void testProxyCustomDefaultScopedType() throws Exception {
        // Use reflection to instantiate a type that isn't in the current package.
        Class<?> cls = Class.forName("org.apache.openjpa.util.custom.CustomProxyDefaultScopeType");
        assertNotNull(cls);
        Method meth = cls.getMethod("instance", new Class[0]);
        assertNotNull(meth);

        meth.setAccessible(true);
        Object obj = meth.invoke(cls, new Object[0]);
        assertNotNull(obj);
        assertNull(_mgr.newCustomProxy(obj, true));
    }
    
    public void testProxyCustomDefaultScopedList() throws Exception {
        // Use reflection to instantiate a type that isn't in the current package.
        Class<?> cls = Class.forName("org.apache.openjpa.util.custom.CustomProxyDefaultScopeList");
        assertNotNull(cls);
        Method meth = cls.getMethod("instance", new Class[0]);
        assertNotNull(meth);

        meth.setAccessible(true);
        Object obj = meth.invoke(cls, new Object[0]);
        assertNotNull(obj);
        assertNull(_mgr.newCustomProxy(obj, true));
    }

    /**
     * Return the {@link java.util.Queue} class if avaialble.
     */
    private static Class getQueueClass() {
        try {
            return Class.forName("java.util.Queue");
        } catch (Throwable t) {
            return null;
        }
    }

    public void testCopyMaps() {
        Map orig = new HashMap();
        populate(orig);
        assertMapsEqual(orig, (Map) _mgr.copyMap(orig));

        orig = new CustomMap();
        populate(orig);
        assertMapsEqual(orig, (Map) _mgr.copyMap(orig));

        Properties porig = new Properties();
        porig.setProperty("foo", "bar");
        porig.setProperty("bar", "biz");
        porig.setProperty("biz", "baz");
        assertMapsEqual(orig, (Map) _mgr.copyMap(orig));
    }

    /**
     * Populate the given map with arbitrary data.
     */
    private static void populate(Map map) {
        map.put(new Integer(1), "1");
        map.put(new Integer(99), "99");
        map.put(new Integer(-2), "-2");
        map.put(new Integer(50), "50");
    }

    /**
     * Assert that the given maps are exactly the same.
     */
    private static void assertMapsEqual(Map m1, Map m2) {
        assertTrue(m1.getClass() == m2.getClass());
        assertEquals(m1.size(), m2.size());
        assertEquals(m1, m2);
    }

    public void testCopySortedMaps() {
        SortedMap orig = new TreeMap();
        populate(orig);
        assertSortedMapsEqual(orig, (SortedMap) _mgr.copyMap(orig));

        orig = new TreeMap(new CustomComparator());
        populate(orig);
        assertSortedMapsEqual(orig, (SortedMap) _mgr.copyMap(orig));

        orig = new CustomSortedMap();
        populate(orig);
        assertSortedMapsEqual(orig, (SortedMap) _mgr.copyMap(orig));

        orig = new CustomComparatorSortedMap(new CustomComparator());
        populate(orig);
        assertSortedMapsEqual(orig, (SortedMap) _mgr.copyMap(orig));
    }

    /**
     * Assert that the given maps are exactly the same.
     */
    private static void assertSortedMapsEqual(SortedMap m1, SortedMap m2) {
        assertTrue(m1.getClass() == m2.getClass());
        assertSortedMapsEquals(m1, m2);
    }

    /**
     * Assert that the given maps are exactly the same (minus the class).
     */
    private static void assertSortedMapsEquals(SortedMap m1, SortedMap m2) {
        assertEquals(m1.comparator(), m2.comparator());
        assertEquals(m1.size(), m2.size());
        Map.Entry entry1;
        Map.Entry entry2;
        Iterator itr1 = m1.entrySet().iterator();
        Iterator itr2 = m2.entrySet().iterator();
        while (itr1.hasNext()) {
            entry1 = (Map.Entry) itr1.next();
            entry2 = (Map.Entry) itr2.next();
            assertTrue(entry1.getKey() == entry2.getKey());
            assertTrue(entry1.getValue() == entry2.getValue());
        }
        assertTrue(m1.equals(m2));
    }

    public void testCopyNullMap() {
        assertNull(_mgr.copyMap(null));
    }

    public void testCopyProxyMap() {
        Map orig = (Map) _mgr.newMapProxy(HashMap.class, null, null, null, true);
        populate(orig);
        assertMapsEqual(new HashMap(orig), (Map) _mgr.copyMap(orig));

        TreeMap torig = (TreeMap) _mgr.newMapProxy(TreeMap.class, null, null, new CustomComparator(), true);
        assertTrue(torig.comparator() instanceof CustomComparator);
        populate(torig);
        assertSortedMapsEqual(new TreeMap(torig), (SortedMap) _mgr.copyMap(torig));
    }

    public void testCloneProxyMap() {
        // Map does not support clone()

        TreeMap torig = (TreeMap) _mgr.newMapProxy(TreeMap.class, null, null, new CustomComparator(), true);
        assertTrue(torig.comparator() instanceof CustomComparator);
        populate(torig);
        assertSortedMapsEquals(new TreeMap(torig), (SortedMap) torig.clone());
    }

    public void testMapMethodsProxied() throws Exception {
        Class proxy = _mgr.newMapProxy(HashMap.class, null, null, null, true).getClass();
        assertMapMethodsProxied(proxy);

        proxy = _mgr.newMapProxy(TreeMap.class, null, null, null, true).getClass();
        assertMapMethodsProxied(proxy);

        proxy = _mgr.newMapProxy(TreeMap.class, null, null, new CustomComparator(), true).getClass();
        assertMapMethodsProxied(proxy);

        proxy = _mgr.newMapProxy(CustomMap.class, null, null, null, true).getClass();
        assertMapMethodsProxied(proxy);

        proxy = _mgr.newMapProxy(CustomSortedMap.class, null, null, null, true).getClass();
        assertMapMethodsProxied(proxy);

        proxy = _mgr.newMapProxy(CustomComparatorSortedMap.class, null, null, new CustomComparator(), true).getClass();
        assertMapMethodsProxied(proxy);
    }

    /**
     * Assert that the methods we need to override to dirty the collection are proxied appropriately.
     */
    private void assertMapMethodsProxied(Class cls) throws Exception {
        assertNotNull(cls.getDeclaredMethod("put", new Class[] { Object.class, Object.class }));
        assertNotNull(cls.getDeclaredMethod("putAll", new Class[] { Map.class }));
        assertNotNull(cls.getDeclaredMethod("clear", (Class[]) null));
        assertNotNull(cls.getDeclaredMethod("remove", new Class[] { Object.class }));
        assertNotNull(cls.getDeclaredMethod("keySet", (Class[]) null));
        assertNotNull(cls.getDeclaredMethod("values", (Class[]) null));
        assertNotNull(cls.getDeclaredMethod("entrySet", (Class[]) null));

        // check a non-mutating method to make sure we're not just proxying
        // everything
        try {
            cls.getDeclaredMethod("containsKey", new Class[] { Object.class });
            fail("Proxied non-mutating method.");
        } catch (NoSuchMethodException nsme) {
            // expected
        }
    }

    public void testPropertiesMethodsProxied() throws Exception {
        Class proxy = _mgr.newMapProxy(Properties.class, null, null, null, true).getClass();
        assertMapMethodsProxied(proxy);
        assertNotNull(proxy.getDeclaredMethod("setProperty", new Class[] { String.class, String.class }));
        assertNotNull(proxy.getDeclaredMethod("load", new Class[] { InputStream.class }));
        assertNotNull(proxy.getDeclaredMethod("loadFromXML", new Class[] { InputStream.class }));
    }

    public void testCopyDates() {
        Date orig = new Date(1999);
        assertDatesEqual(orig, (Date) _mgr.copyDate(orig));

        orig = new java.sql.Date(1999);
        assertDatesEqual(orig, (Date) _mgr.copyDate(orig));

        orig = new Time(1999);
        assertDatesEqual(orig, (Date) _mgr.copyDate(orig));

        Timestamp torig = new Timestamp(1999);
        torig.setNanos(2001);
        assertDatesEqual(torig, (Date) _mgr.copyDate(torig));

        torig = new CustomDate(1999);
        torig.setNanos(2001);
        assertDatesEqual(torig, (Date) _mgr.copyDate(torig));
    }

    /**
     * Assert that the given dates are exactly the same.
     */
    private static void assertDatesEqual(Date d1, Date d2) {
        assertTrue(d1.getClass() == d2.getClass());
        assertDatesEquals(d1, d2);
    }

    /**
     * Assert that the given dates are exactly the same (minus the class).
     */
    private static void assertDatesEquals(Date d1, Date d2) {
        assertTrue(d1.equals(d2));
    }

    public void testCopyNullDate() {
        assertNull(_mgr.copyDate(null));
    }

    public void testCopyProxyDate() {
        Date orig = (Date) _mgr.newDateProxy(Time.class);
        orig.setTime(1999);
        assertDatesEqual(new Time(orig.getTime()), (Date) _mgr.copyDate(orig));
    }

    public void testCloneProxyDate() {
        Date orig = (Date) _mgr.newDateProxy(Time.class);
        orig.setTime(1999);
        assertDatesEquals(new Time(orig.getTime()), (Date) orig.clone());
    }

    public void testDateMethodsProxied() throws Exception {
        Class proxy = _mgr.newDateProxy(Date.class).getClass();
        assertDateMethodsProxied(proxy);

        proxy = _mgr.newDateProxy(java.sql.Date.class).getClass();
        assertDateMethodsProxied(proxy);

        proxy = _mgr.newDateProxy(Time.class).getClass();
        assertDateMethodsProxied(proxy);

        proxy = _mgr.newDateProxy(Timestamp.class).getClass();
        assertTimestampMethodsProxied(proxy);

        proxy = _mgr.newDateProxy(CustomDate.class).getClass();
        assertTimestampMethodsProxied(proxy);
    }

    /**
     * Assert that the methods we need to override to dirty the date are proxied appropriately.
     */
    private void assertDateMethodsProxied(Class cls) throws Exception {
        assertNotNull(cls.getDeclaredMethod("setDate", new Class[] { int.class }));
        assertNotNull(cls.getDeclaredMethod("setHours", new Class[] { int.class }));
        assertNotNull(cls.getDeclaredMethod("setMinutes", new Class[] { int.class }));
        assertNotNull(cls.getDeclaredMethod("setMonth", new Class[] { int.class }));
        assertNotNull(cls.getDeclaredMethod("setSeconds", new Class[] { int.class }));
        assertNotNull(cls.getDeclaredMethod("setTime", new Class[] { long.class }));
        assertNotNull(cls.getDeclaredMethod("setYear", new Class[] { int.class }));

        // check a non-mutating method to make sure we're not just proxying
        // everything
        try {
            cls.getDeclaredMethod("getTime", (Class[]) null);
            fail("Proxied non-mutating method.");
        } catch (NoSuchMethodException nsme) {
            // expected
        }
    }

    /**
     * Assert that the methods we need to override to dirty the timestamp are proxied appropriately.
     */
    private void assertTimestampMethodsProxied(Class cls) throws Exception {
        assertDateMethodsProxied(cls);
        assertNotNull(cls.getDeclaredMethod("setNanos", new Class[] { int.class }));
    }

    public void testCopyCalendars() {
        Calendar orig = new GregorianCalendar();
        populate(orig);
        assertCalendarsEqual(orig, _mgr.copyCalendar(orig));

        orig = new CustomCalendar();
        populate(orig);
        assertCalendarsEqual(orig, _mgr.copyCalendar(orig));
    }

    /**
     * Populate calendar with arbitrary data.
     */
    private static void populate(Calendar cal) {
        cal.setTimeInMillis(1999);
        cal.setTimeZone(TimeZone.getTimeZone("CST"));
    }

    /**
     * Assert that the given dates are exactly the same.
     */
    private static void assertCalendarsEqual(Calendar c1, Calendar c2) {
        assertTrue(c1.getClass() == c2.getClass());
        assertCalendarsEquals(c1, c2);
    }

    /**
     * Assert that the given dates are exactly the same (minus the class).
     */
    private static void assertCalendarsEquals(Calendar c1, Calendar c2) {
        assertTrue(c1.equals(c2));
    }

    public void testCopyNullCalendar() {
        assertNull(_mgr.copyCalendar(null));
    }

    public void testCopyProxyCalendar() {
        Calendar orig = (Calendar) _mgr.newCalendarProxy(GregorianCalendar.class, TimeZone.getTimeZone("CST"));
        populate(orig);
        Calendar cal = new GregorianCalendar();
        populate(cal);
        assertCalendarsEqual(cal, _mgr.copyCalendar(orig));
    }

    public void testCloneProxyCalendar() {
        Calendar orig = (Calendar) _mgr.newCalendarProxy(GregorianCalendar.class, TimeZone.getTimeZone("CST"));
        populate(orig);
        Calendar cal = new GregorianCalendar();
        populate(cal);
        assertCalendarsEquals(cal, (Calendar) orig.clone());
    }

    public void testCalendarAbstractClassProxy() {
        Proxy cal = _mgr.newCalendarProxy(Calendar.class, null);
        assertNotNull(cal);
    }

    public void testCalendarMethodsProxied() throws Exception {
        Class proxy = _mgr.newCalendarProxy(GregorianCalendar.class, TimeZone.getDefault()).getClass();
        assertCalendarMethodsProxied(proxy);

        proxy = _mgr.newCalendarProxy(CustomCalendar.class, TimeZone.getDefault()).getClass();
        assertCalendarMethodsProxied(proxy);

        proxy = _mgr.newCalendarProxy(Calendar.class, TimeZone.getDefault()).getClass();
        assertCalendarMethodsProxied(proxy);
    }

    /**
     * Assert that the methods we need to override to dirty the calendar are proxied appropriately.
     */
    private void assertCalendarMethodsProxied(Class cls) throws Exception {
        assertNotNull(cls.getDeclaredMethod("set", new Class[] { int.class, int.class }));
        assertNotNull(cls.getDeclaredMethod("roll", new Class[] { int.class, int.class }));
        assertNotNull(cls.getDeclaredMethod("setTimeInMillis", new Class[] { long.class }));
        assertNotNull(cls.getDeclaredMethod("setTimeZone", new Class[] { TimeZone.class }));
        assertNotNull(cls.getDeclaredMethod("computeFields", (Class[]) null));

        // check a non-mutating method to make sure we're not just proxying
        // everything
        try {
            cls.getDeclaredMethod("getTimeInMillis", (Class[]) null);
            fail("Proxied non-mutating method.");
        } catch (NoSuchMethodException nsme) {
            // expected
        }
    }

    public void testCopyBeans() {
        CustomBean orig = new CustomBean();
        populate(orig);
        assertBeansEqual(orig, (CustomBean) _mgr.copyCustom(orig));

        orig = new CustomCopyConstructorBean(orig);
        assertBeansEqual(orig, (CustomBean) _mgr.copyCustom(orig));
    }

    /**
     * Populate the given bean with arbitrary data.
     */
    private void populate(CustomBean bean) {
        bean.setString("foo");
        bean.setNumber(99);
    }

    public void testNonproxyableBean() {
        NonproxyableBean orig = new NonproxyableBean(1);
        populate(orig);
        assertNull(_mgr.copyCustom(orig));
        assertNull(_mgr.copyCustom(orig));
        assertNull(_mgr.newCustomProxy(orig, true));
    }
    
    public void testIsUnproxyable() {
        CustomBean validBean = new CustomBean();
        populate(validBean);
        assertNotNull(_mgr.copyCustom(validBean));
        assertNotNull(_mgr.newCustomProxy(validBean, true));
        assertFalse(_mgr.isUnproxyable(CustomBean.class));
        
        NonproxyableBean bean1 = new NonproxyableBean(1);
        populate(bean1);
        
        NonproxyableBean2 bean2 = new NonproxyableBean2();
        populate(bean2);

        assertFalse(_mgr.isUnproxyable(NonproxyableBean.class));
        assertNull(_mgr.copyCustom(bean1));
        assertTrue(_mgr.isUnproxyable(NonproxyableBean.class));
        assertNull(_mgr.newCustomProxy(bean1, true));
        assertTrue(_mgr.isUnproxyable(NonproxyableBean.class));
        
        assertFalse(_mgr.isUnproxyable(NonproxyableBean2.class));
        assertNull(_mgr.newCustomProxy(bean2, true));
        assertTrue(_mgr.isUnproxyable(NonproxyableBean2.class));
        assertNull(_mgr.copyCustom(bean2));
        assertTrue(_mgr.isUnproxyable(NonproxyableBean2.class));
    }

    /**
     * Assert that the given beans are exactly the same.
     */
    private static void assertBeansEqual(CustomBean b1, CustomBean b2) {
        assertTrue(b1.getClass() == b2.getClass());
        assertTrue(b1.getString() == b2.getString());
        assertTrue(b1.getNumber() == b2.getNumber());
    }

    public void testCopyNullBean() {
        assertNull(_mgr.copyCustom(null));
    }

    public void testCopyProxyBean() {
        CustomBean orig = (CustomBean) _mgr.newCustomProxy(new CustomBean(), true);
        populate(orig);
        CustomBean comp = new CustomBean();
        populate(comp);
        assertBeansEqual(comp, (CustomBean) _mgr.copyCustom(orig));
    }

    public void testBeanMethodsProxied() throws Exception {
        Class proxy = _mgr.newCustomProxy(new CustomBean(), true).getClass();
        assertBeanMethodsProxied(proxy);

        proxy = _mgr.newCustomProxy(new CustomCopyConstructorBean(new CustomBean()), true).getClass();
        assertBeanMethodsProxied(proxy);
    }

    /**
     * Assert that the methods we need to override to dirty the bean are proxied appropriately.
     */
    private void assertBeanMethodsProxied(Class cls) throws Exception {
        assertNotNull(cls.getDeclaredMethod("setString", new Class[] { String.class }));
        assertNotNull(cls.getDeclaredMethod("setNumber", new Class[] { int.class }));

        // check a non-mutating method to make sure we're not just proxying
        // everything
        try {
            cls.getDeclaredMethod("getString", (Class[]) null);
            fail("Proxied non-mutating method.");
        } catch (NoSuchMethodException nsme) {
            // expected
        }
    }

    public static void main(String[] args) {
        TestRunner.run(TestProxyManager.class);
    }

    /**
     * Used to test custom list handling. Copy constructor intentionally ommitted.
     */
    public static class CustomList extends AbstractSequentialList {

        private final List _delegate = new ArrayList();

        public int size() {
            return _delegate.size();
        }

        public ListIterator listIterator(int idx) {
            return _delegate.listIterator(idx);
        }
    }

    /**
     * Used to test custom set handling. Copy constructor intentionally ommitted.
     */
    public static class CustomSet extends AbstractSet {

        private final Set _delegate = new HashSet();

        public int size() {
            return _delegate.size();
        }

        public Iterator iterator() {
            return _delegate.iterator();
        }

        public boolean add(Object o) {
            return _delegate.add(o);
        }
    }

    /**
     * Used to test custom set handling. Copy constructor intentionally ommitted.
     */
    public static class CustomSortedSet extends TreeSet {
    }

    /**
     * Used to test custom set handling. Copy constructor intentionally ommitted.
     */
    public static class CustomComparatorSortedSet extends TreeSet {

        public CustomComparatorSortedSet() {
        }

        public CustomComparatorSortedSet(Comparator comp) {
            super(comp);
        }
    }

    /**
     * Used to test custom map handling. Copy constructor intentionally ommitted.
     */
    public static class CustomMap extends AbstractMap {

        private final Map _delegate = new HashMap();

        public Object put(Object key, Object value) {
            return _delegate.put(key, value);
        }

        public Set entrySet() {
            return _delegate.entrySet();
        }
    }

    /**
     * Used to test custom map handling. Copy constructor intentionally ommitted.
     */
    public static class CustomSortedMap extends TreeMap {
    }

    /**
     * Used to test custom map handling. Copy constructor intentionally ommitted.
     */
    public static class CustomComparatorSortedMap extends TreeMap {

        public CustomComparatorSortedMap() {
        }

        public CustomComparatorSortedMap(Comparator comp) {
            super(comp);
        }
    }

    /**
     * Used to test transfer of comparators to proxies.
     */
    private static class CustomComparator implements Comparator {

        public int compare(Object o1, Object o2) {
            return ((Comparable) o1).compareTo(o2);
        }
    }

    /**
     * Used to test custom date handling.
     */
    public static class CustomDate extends Timestamp {

        public CustomDate(long time) {
            super(time);
        }
    }

    /**
     * Used to test custom bean handling.
     */
    public static class CustomBean {
        private String _string;
        private int _number;

        public String getString() {
            return _string;
        }

        public void setString(String str) {
            _string = str;
        }

        public int getNumber() {
            return _number;
        }

        public void setNumber(int number) {
            _number = number;
        }
    }

    /**
     * Used to test custom bean handling.
     */
    public static class CustomCopyConstructorBean extends CustomBean {

        public CustomCopyConstructorBean(CustomBean bean) {
            setString(bean.getString());
            setNumber(bean.getNumber());
        }
    }

    /**
     * Used to non-proxyable custom bean handling.
     */
    public static class NonproxyableBean extends CustomBean {

        public NonproxyableBean(long x) {
            // single non-default, non-copy constructor
        }
    }

    /**
     * Used to non-proxyable custom bean handling.
     */
    public class NonproxyableBean2 extends CustomBean {
        // class is not static
    }

    /**
     * Used to test custom calendar handling.
     */
    public static class CustomCalendar extends GregorianCalendar {
    }
}
