swingx-master/swingx-core/src/test/java/org/jdesktop/swingx/decorator/AbstractTestHighlighterClient.java
/*
 * $Id$
 *
 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package org.jdesktop.swingx.decorator;

import static org.hamcrest.CoreMatchers.is;
import static org.jdesktop.test.matchers.Matchers.property;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import java.awt.Color;
import java.beans.PropertyChangeListener;
import java.util.logging.Logger;

import javax.swing.UIManager;

import junit.framework.TestCase;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Contains tests around Highlighter client api for collection components.
 * <p>
 * 
 * The common public api:
 * 
 * <pre><code>
 *  void setHighlighters(Highlighter...)
 *  HighLighter[] getHighlighters()
 *  void addHighlighter(Highlighter)
 *  void removeHighlighter(Highlighter)
 *  updateUI()
 * </code></pre>
 * 
 * Subclasses testing concrete implementations must override the createHighlighterClient.
 * 
 * @author Jeanette Winzenburg
 */
@RunWith(JUnit4.class)
public abstract class AbstractTestHighlighterClient extends TestCase {

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(AbstractTestHighlighterClient.class.getName());
    
    // ---- HighlighterClient
    

    /**
     * 
     * @return the concrete HighlighterClient to test
     */
    protected abstract HighlighterClient createHighlighterClient();
    
    
    /**
     * Test that the client is messaged on change to a managed Highlighter.
     */
    @Test
    public void testUpdateUI() {
        HighlighterClient client = createHighlighterClient();
        // force loading of striping colors
        ColorHighlighter colorHighlighter = (ColorHighlighter) HighlighterFactory.createSimpleStriping();
        Color uiColor = UIManager.getColor("UIColorHighlighter.stripingBackground");
        if (uiColor == null) {
            LOG.info("cannot run test - no ui striping color");
            return;
        }
        assertSame("sanity", uiColor, colorHighlighter.getBackground());
        client.addHighlighter(colorHighlighter);
        Color changedUIColor = Color.RED;
        UIManager.put("UIColorHighlighter.stripingBackground", changedUIColor);
        client.updateUI();
        try {
            assertSame("support must update ui color", changedUIColor, colorHighlighter.getBackground());
        } finally {
            UIManager.put("UIColorHighlighter.stripingBackground", uiColor);
        }
    }

    /**
     * Test assumption is incorrect if the client has internal Highlighters which
     * are always included!
     */
    @Test
    public void testSetHighlighters() {
        HighlighterClient client = createHighlighterClient();
        Highlighter[] highlighters = new Highlighter[] {new ColorHighlighter(), new ColorHighlighter()};
        client.setHighlighters(highlighters);
        assertSameContent(highlighters, client.getHighlighters());
    }

    /**
     * Test property change event on setHighlighters for JXTable.
     */
    @Test
    public void testSetHighlightersChangeEvent() {
        HighlighterClient client = createHighlighterClient();
        
        PropertyChangeListener pcl = mock(PropertyChangeListener.class);
        client.addPropertyChangeListener(pcl);
        
        Highlighter[] old = client.getHighlighters();
        client.setHighlighters(new ColorHighlighter());
        
        verify(pcl).propertyChange(argThat(is(property("highlighters", old, client.getHighlighters()))));
    }

    /**
     * Sanity: handles empty array.
     */
    @Test
    public void testSetHighlightersEmptyArray() {
        HighlighterClient client = createHighlighterClient();
        int initialCount = client.getHighlighters().length;
        client.setHighlighters(new Highlighter[] {});
        assertEquals(initialCount, client.getHighlighters().length);
    }

    /**
     * 
     * Test that setting zero highlighter removes all.
     */
    @Test
    public void testSetHighlightersNoArgument() {
        HighlighterClient client = createHighlighterClient();
        int initialCount = client.getHighlighters().length;
        client.addHighlighter(new ColorHighlighter());
        // sanity
        assertEquals(initialCount + 1, client.getHighlighters().length);
        client.setHighlighters();
        assertEquals(initialCount + 0, client.getHighlighters().length);
    }

    /**
     * Test strict enforcement of not null allowed in setHighlighters for JXTable.
     * 
     * Here: null highlighter.
     */
    @Test
    public void testSetHighlightersNullHighlighter() {
        try {
            createHighlighterClient().setHighlighters((Highlighter) null);
            fail("illegal to call setHighlighters(null)");
        } catch (NullPointerException e) {
            // expected
        }
    }

    /**
     * Test strict enforcement of not null allowed in setHighlighters for JXTable.
     * 
     * Here: null array
     */
    @Test
    public void testSetHighlightersNullArray() {
        try {
            createHighlighterClient().setHighlighters((Highlighter[]) null);
            fail("illegal to call setHighlighters(null)");
            
        } catch (NullPointerException e) {
            // expected
        }
    }

    /**
     * Test strict enforcement of not null allowed in setHighlighters for JXTable.
     * 
     * Here: null array element.
     */
    @Test
    public void testSetHighlightersArrayNullElement() {
        try {
            createHighlighterClient().setHighlighters(new Highlighter[] {null});
            fail("illegal to call setHighlighters(null)");
            
        } catch (NullPointerException e) {
            // expected
        }
    }

    /**
     * test if removeHighlighter behaves as doc'ed.
     *
     */
    @Test
    public void testRemoveHighlighterTable() {
        HighlighterClient client = createHighlighterClient();
        int initialCount = client.getHighlighters().length;
        // test cope with null
        client.removeHighlighter(null);
        Highlighter presetHighlighter = new ColorHighlighter();
        client.setHighlighters(presetHighlighter);
        Highlighter[] highlighters = client.getHighlighters();
        // sanity
        assertEquals(initialCount + 1, highlighters.length);
        // remove uncontained
        client.removeHighlighter(new ColorHighlighter());
        // assert no change
        assertSameContent(highlighters, client.getHighlighters());
        client.removeHighlighter(presetHighlighter);
        assertEquals(initialCount, client.getHighlighters().length);
    }

    /**
     * Test property change event on removeHighlighter for JXTable.
     */
    @Test
    public void testRemoveHighlightersChangeEvent() {
        HighlighterClient table = createHighlighterClient();
        
        PropertyChangeListener pcl = mock(PropertyChangeListener.class);
        table.addPropertyChangeListener(pcl);
        
        Highlighter highlighter = new ColorHighlighter();
        table.setHighlighters(highlighter);
        Highlighter[] old = table.getHighlighters();
        table.removeHighlighter(highlighter);
        
        verify(pcl).propertyChange(argThat(is(property("highlighters", old, table.getHighlighters()))));
    }

    /**
     * test if addHighlighter behaves as doc'ed for JXTable.
     *
     */
    @Test
    public void testAddHighlighter() {
        HighlighterClient client = createHighlighterClient();
        int initialCount = client.getHighlighters().length;
        Highlighter presetHighlighter = new ColorHighlighter();
        // add the first
        client.addHighlighter(presetHighlighter);
        // assert that it is added
        assertEquals(initialCount + 1, client.getHighlighters().length);
        assertAsLast(client.getHighlighters(), presetHighlighter);
        Highlighter highlighter = new ColorHighlighter();
        // add the second
        client.addHighlighter(highlighter);
        assertEquals(initialCount + 2, client.getHighlighters().length);
        // assert that it is appended
        assertAsLast(client.getHighlighters(), highlighter);
    }

    /**
     * Test property change event on addHighlighter for JXTable.
     */
    @Test
    public void testAddHighlighterChangeEvent() {
        HighlighterClient table = createHighlighterClient();
        
        PropertyChangeListener pcl = mock(PropertyChangeListener.class);
        table.addPropertyChangeListener(pcl);
        
        Highlighter[] old = table.getHighlighters();
        table.addHighlighter(new ColorHighlighter());
        
        verify(pcl).propertyChange(argThat(is(property("highlighters", old, table.getHighlighters()))));
    }

    /**
     * test choking on precondition failure (highlighter must not be null) for JTXTable.
     *
     */
    @Test
    public void testAddNullHighlighter() {
        try {
            createHighlighterClient().addHighlighter(null);
            fail("adding a null highlighter must throw NPE");
        } catch (NullPointerException e) {
            // pass - this is what we expect
        } catch (Exception e) {
            fail("adding a null highlighter throws exception different " +
                        "from the expected NPE \n" + e);
        }
    }


    
    
    /**
     * Same content in both.
     * @param highlighters
     * @param highlighters2
     */
    private void assertSameContent(Highlighter[] highlighters, Highlighter[] highlighters2) {
        assertEquals(highlighters.length, highlighters2.length);
        for (int i = 0; i < highlighters.length; i++) {
            assertSame("must contain same element", highlighters[i], highlighters2[i]);
        }
    }
    
    /**
     * Last in list.
     * 
     * @param highlighters
     * @param highlighter
     */
    private void assertAsLast(Highlighter[] highlighters, Highlighter highlighter) {
        assertTrue("pipeline must not be empty", highlighters.length > 0);
        assertSame("highlighter must be added as last", highlighter, highlighters[highlighters.length - 1]);
    }

    /**
     * This interface defines the common contract of clients which provide
     * support for Highlighters. They must
     * 
     * <ul>
     * <li> have a bound property "highlighters" denoting a collection of
     * Highlighters
     * <li> have methods to modify the collection
     * <li> update the ui of contained Highlighters on LAF changes
     * <li> apply the highlighters as appropriate. This implies that it must
     * listen to highlighter state changes to update itself (or related parties)
     * accordingly.
     * </ul>
     * 
     * The last bullet is a "vague" requirement in that it might vary
     * considerably across client implementations. While JComponents typically
     * will invoke a repaint, a non-component might choose to notify some other
     * listeners. Furthermore, it's not testable, as clients might choose to do
     * the actual update only if really needed, f.i. not if invisible or if
     * there are no external listeners.
     * <p>
     * 
     */
    public static interface HighlighterClient {

        /**
         * Sets the <code>Highlighter</code>s to this client, replacing any old settings.
         * No argument or an empty array removes all <code>Highlighter</code>s. <p>
         * 
         * This is a bound property.
         * 
         * @param highlighters zero or more not null highlighters to use for renderer decoration.
         * @throws NullPointerException if array is null or array contains null values.
         * 
         * @see #getHighlighters()
         * @see #addHighlighter(Highlighter)
         * @see #removeHighlighter(Highlighter)
         * 
         */
        void setHighlighters(Highlighter... highlighters);

        /**
         * Returns the <code>Highlighter</code>s used by this client.
         * Maybe empty, but guarantees to be never null.
         * 
         * @return the Highlighters used by this table, guaranteed to never null.
         * 
         * @see #setHighlighters(Highlighter[])
         */
        Highlighter[] getHighlighters();

        /**
         * Appends a <code>Highlighter</code> to the end of the list of used
         * <code>Highlighter</code>s. The argument must not be null. 
         * <p>
         * 
         * @param highlighter the <code>Highlighter</code> to add, must not be null.
         * @throws NullPointerException if <code>Highlighter</code> is null.
         * 
         * @see #removeHighlighter(Highlighter)
         * @see #setHighlighters(Highlighter[])
         */
        void addHighlighter(Highlighter highlighter);

        /**
         * Removes the given <code>Highlighter</code>. Does nothing if the
         * <code>Highlighter</code> is not contained.
         * 
         * @param highlighter the <code>Highlighter</code> to remove.
         * 
         * @see #addHighlighter(Highlighter)
         * @see #setHighlighters(Highlighter...)
         */
        void removeHighlighter(Highlighter highlighter);

        /**
         * Updates contained Highlighters on LAF changes.
         */
        void updateUI();

        /**
         * Adds a PropertyChangeListener which will be notified on changes of the 
         * "highlighters" property.
         * 
         * @param l the listener to add.
         */
        void addPropertyChangeListener(PropertyChangeListener l);

        /**
         * Removes the <code>PropertyChangeListener</code>. Does nothing if the
         * listener is not contained.
         * 
         * @param l the listener to remove. 
         */
        void removePropertyChangeListener(PropertyChangeListener l);
    }
}
