swingx-master/swingx-beaninfo/src/test/java/org/jdesktop/beans/AbstractBeanInfoTest.java
package org.jdesktop.beans;

import static org.hamcrest.CoreMatchers.is;
import static org.jdesktop.test.SerializableSupport.serialize;
import static org.jdesktop.test.matchers.Matchers.equivalentTo;
import static org.jdesktop.test.matchers.Matchers.property;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.RETURNS_MOCKS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import java.awt.Insets;
import java.beans.BeanInfo;
import java.beans.EventSetDescriptor;
import java.beans.Introspector;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.exceptions.verification.NoInteractionsWanted;

@SuppressWarnings("nls")
public abstract class AbstractBeanInfoTest<T> {
    protected Logger logger = Logger.getLogger(getClass().getName());
    protected T instance;
    private BeanInfo beanInfo;
    private Map<Class<?>, Object> listeners;
    
    @Before
    public void setUp() throws Exception {
        instance = createInstance();
        beanInfo = Introspector.getBeanInfo(instance.getClass());
        listeners = new HashMap<Class<?>, Object>();
        
        for (EventSetDescriptor descriptor : beanInfo.getEventSetDescriptors()) {
            Class<?> eventClass = descriptor.getListenerType();
            Object listener = mock(eventClass);
            
            descriptor.getAddListenerMethod().invoke(instance, listener);
            listeners.put(eventClass, listener);
        }
    }
    
    protected abstract T createInstance();
    
    @Test
    public final void testBoundProperties() throws Exception {
        for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
            if (descriptor.isBound()) {
                if (descriptor.isHidden()) {
                    continue;
                }
                
                if (descriptor.getWriteMethod() == null) {
                    //special-case this read-only property
                    if ("UIClassID".equals(descriptor.getName())) {
                        return;
                    }
                    
                    fail("bound read-only property: " + descriptor.getName());
                }
                
                Class<?> propertyType = descriptor.getPropertyType();
                
                if (isUnhandledType(propertyType)) {
                    //TODO log?
                    continue;
                }
                
                Object defaultValue = descriptor.getReadMethod().invoke(instance);
                Object newValue = getNewValue(propertyType, defaultValue);
                
                descriptor.getWriteMethod().invoke(instance, newValue);
                
                PropertyChangeListener pcl = (PropertyChangeListener) listeners.get(PropertyChangeListener.class);
                verify(pcl).propertyChange(argThat(is(property(descriptor.getName(), defaultValue, newValue))));
                reset(pcl);
            }
        }
    }
    
    private boolean isUnhandledType(Class<?> type) {
        return type == null;
    }
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected Object getNewValue(Class<?> propertyType, Object defaultValue) {
        Object result = null;
        
        if (propertyType.isArray()) {
            int length = defaultValue == null ? 1 : ((Object[]) defaultValue).length + 1;
            result = Array.newInstance(propertyType.getComponentType(), length);
        } else if (propertyType.isEnum()) {
            EnumSet set = EnumSet.allOf((Class<? extends Enum>) propertyType);
            int size = set.size();
            
            if (size == 1) {
                result = defaultValue == null ? set.iterator().next() : null;
            } else {
                int ordinal = ((Enum) defaultValue).ordinal();
                ordinal = ordinal == size - 1 ? 0 : ordinal + 1;
                Iterator iter = set.iterator();
                
                for (int i = 0; i < ordinal + 1; i++) {
                    result = iter.next();
                }
            }
        } else if (propertyType.isPrimitive()) {
            //help short circuit all of these checks
            if (propertyType == boolean.class) {
                result = Boolean.FALSE.equals(defaultValue);
            } else if (propertyType == int.class) {
                result = ((Integer) defaultValue) + 1;
            } else if (propertyType == double.class) {
                result = ((Double) defaultValue) + 1d;
            } else if (propertyType == float.class) {
                result = ((Float) defaultValue) + 1f;
            }
        } else if (propertyType == String.class) {
            result = "original string: " + defaultValue;
        } else if (propertyType == Insets.class) {
            if (new Insets(0, 0, 0, 0).equals(defaultValue)) {
                result = new Insets(1, 1, 1, 1);
            } else {
                result = mock(propertyType);
            }
        } else {
            result = mock(propertyType, RETURNS_MOCKS);
        }
        
        return result;
    }

    /**
     * A simple serialization check. Ensures that the reconstituted object is equivalent to the
     * original.
     */
    @Test
    public void testSerialization() {
        if (!Serializable.class.isInstance(instance)) {
            return;
        }
        
        T serialized = serialize(instance);
        
        assertThat(serialized, is(equivalentTo(instance)));
    }
    
    @After
    public void tearDown() {
        for (Object listener : listeners.values()) {
            try {
                // TODO need a way to handle components that have contained components,
                // like JXComboBox, that cause spurious container events
                verifyNoMoreInteractions(listener);
            } catch (NoInteractionsWanted logAndIgnore) {
                logger.log(Level.WARNING, "unexpected listener notification", logAndIgnore);
            }
        }
    }
}
