/*
 *  
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program 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
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package javax.microedition.lcdui;

import com.sun.midp.i3test.TestCase;
import com.sun.midp.util.LcduiTestMIDlet;
import com.sun.midp.util.LcduiTestCanvas;
import com.sun.midp.util.LiveTracer;
import com.sun.midp.util.LiveTraceCallback;
import com.sun.midp.util.LiveTraceListener;
import com.sun.midp.util.SerialCallback;

import javax.microedition.midlet.MIDlet;


/**
 * Tests proper behavior of Display.setCurrent() for a single midlet in the
 * foreground. Various combinations of "normal" (i.e., non-Alert) Displayable
 * instances are tested with the one-arg and two-arg Alert forms of the
 * setCurrent() method.
 *
 * The notation used to name tests is as follows.
 *
 * The operations initiated by the tests are:
 *
 * <code>
 * N  setCurrent(normal [non-Alert] Displayable)
 * A  setCurrent(Alert)
 * 2  setCurrent(Alert, Displayable)
 * D  dismiss the current Alert
 * </code>
 *
 * Codes for context or waits for event processing are:
 * <code>
 * 0  tests Display's initial state (current == null)
 * _  wait for a screen-change event to be processed
 * s  suffix indicating a displayable is the same as a previous one
 * </code>
 *
 * For example, testA_2 first calls setCurrent(alert1), waits for the alert to 
 * become current, then calls setCurrent(alert2, disp1). Note that this 
 * notation doesn't include an initial setCurrentWait() call that is typically 
 * used to establish the initial state for the test. In such cases the initial 
 * displayable will be numbered zero, that is, disp0 or cv0. This is in 
 * contrast to displayables and alerts that are used in the operations being 
 * tested, which are numbered starting at one.
 *
 * Quite often it is necessary to execute code atomically on the event thread,
 * that is, ensuring that no events are processed within a group of 
 * statements. Code running on the test thread is arbitrarily interleaved with 
 * code running on the event thread. For example, suppose Displayable d0 is 
 * current. If the following code were executed on the test thread,
 *
 * <code>
 *     dpy.setCurrent(d1);
 *     dpy.setCurrent(alert);
 * </code>
 *
 * there would be a race condition between d1 becoming current and the call to
 * setCurrent(alert). Runnable blocks are used to group a set of statements to
 * be executed atomically.  Runnable blocks are used in two different ways:
 * using callSerially() and using await(). In both cases the run() method is
 * executed on the event thread. This prevents any other events from being
 * dispatched in the midst of the code of the run() method.  For example:
 *
 * <code>
 *     dpy.callSerially(
 *         new Runnable() {
 *             public void run() {
 *                 // (1)
 *             }
 *         });
 * 
 *     scl.await(
 *         new Runnable() {
 *             public void run() {
 *                 // (2)
 *             }
 *         });
 * </code>
 *
 * The code (1) is executed atomically on the event thread, and the calling
 * thread continues. The code (2) is executed atomically on the event thread
 * immediately after the next screen change occurs. The calling thread is
 * blocked until the screen change occurs and the run() method has completed.
 */
public class TestNewSetCurrent extends TestCase {

    Display dpy;
    LiveTraceCallback scl; // scl = screen change listener
    boolean thrown;

    // ===== utility functions =====


    /**
     * Creates a new alert and sets its timeout to FOREVER so that it must be 
     * dismissed explicitly.
     */
    Alert makeAlert(String title) {
        Alert a = new Alert(title);
        a.setTimeout(Alert.FOREVER);
        return a;
    }


    /**
     * Simulates dismissing an alert from this display. The logic here mimics 
     * the logic of what occurs when the Alert times out and executes its 
     * default (DISMISS) command. This allows tests to set the alert timeout 
     * to FOREVER and to call this function in the right sequence, without 
     * having to deal with timing issues.
     */
    void dismiss(Alert alert) {
        synchronized (Display.LCDUILock) {
            alert.lDismiss();
        }
    }


    /**
     * Dismisses the alert and then waits for the resulting screen-change
     * event to be processed.
     */
    void dismissWait(Alert alert) {
        dismiss(alert);
        scl.await();
    }


    /**
     * Checks a variety of invariants on test canvas cv, depending upon 
     * whether the canvas should or should not be current.
     */
    void checkCurrent(String s, LcduiTestCanvas cv, boolean isCurrent) {
        if (isCurrent) {
            assertTrue(s+".showCalled must be true", cv.showCalled());
            assertTrue(s+".isShown must be true", cv.isShown());
            assertSame(s+" must be current", cv, dpy.getCurrent());
        } else {
            assertFalse(s+".showCalled must be false", cv.showCalled());
            assertFalse(s+".isShown must be false", cv.isShown());
            assertNotSame(s+" must not be current", cv, dpy.getCurrent());
        }
    }


    /**
     * Checks a variety of invariants on an alert, depending upon 
     * whether the alert should or should not be current.
     */
    void checkCurrent(String s, Alert alert, boolean shouldBeCurrent) {
        if (shouldBeCurrent) {
            assertTrue(s+".isShown must be true", alert.isShown());
            assertSame(s+" must be current", alert, dpy.getCurrent());
        } else {
            assertFalse(s+".isShown must be false", alert.isShown());
            assertNotSame(s+" must not be current", alert, dpy.getCurrent());
        }
    }


    /**
     * Sets the Displayable to be current, waits for it to become visible, and
     * returns.
     */
    void setCurrentWait(Displayable d) {
        dpy.setCurrent(d);
        scl.await();
    }


    /**
     * Creates the test MIDlet and get its display.
     */
    void init() throws Throwable {
        dpy = new StubDisplay();
        scl = new LiveTraceCallback();
        dpy.liveTracer.add(Display.LTR_SCREENCHANGE_DONE, scl);
    }


    /**
     * Cleans up the test MIDlet.
     */
    void fini() {
        dpy.liveTracer.clear();
        scl.shutdown();
    }


    // ===== the tests =====


    /**
     * Tests whether the display has been initialized properly.
     */
    void testInit() {
        assertNotNull("dpy must be non-null", dpy);
        assertNull("current must be null", dpy.getCurrent());
    }


    /**
     * Case: 0A0A. Tests whether the system returns to the original (null)
     * state after an alert. Does so twice in order to ensure that the first
     * setCurrent doesn't have any side effects.  WARNING: this must be run on
     * a fresh display.
     */
    void test0A0A() {
        final Alert alert1 = makeAlert("alert1");
        final Alert alert2 = makeAlert("alert2");

        assertNull("current null", dpy.getCurrent());

        setCurrentWait(alert1);
        checkCurrent("alert1", alert1, true);

        dismissWait(alert1);
        checkCurrent("alert1", alert1, false);
        assertNull("current null", dpy.getCurrent());

        setCurrentWait(alert2);
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, true);

        dismissWait(alert2);
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, false);
        assertNull("current null", dpy.getCurrent());
    }


    /**
     * Case: N.
     */
    void testN() {
        LcduiTestCanvas cv1 = new LcduiTestCanvas();
        setCurrentWait(cv1);
        checkCurrent("cv1", cv1, true);
    }


    /**
     * Case: A.
     */
    void testA() {
        LcduiTestCanvas cv0 = new LcduiTestCanvas();
        Alert alert = makeAlert("alert");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        setCurrentWait(alert);
        checkCurrent("alert", alert, true);
        checkCurrent("cv0", cv0, false);

        dismissWait(alert);
        checkCurrent("alert", alert, false);
        checkCurrent("cv0", cv0, true);
    }


    /**
     * Case: 2.
     */
    void test2() {
        LcduiTestCanvas cv0 = new LcduiTestCanvas();
        LcduiTestCanvas cv1 = new LcduiTestCanvas();
        Alert alert = makeAlert("alert");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        dpy.setCurrent(alert, cv1);
        scl.await();
        checkCurrent("cv0", cv0, false);
        checkCurrent("alert", alert, true);

        dismissWait(alert);
        checkCurrent("cv1", cv1, true);
        checkCurrent("alert", alert, false);
    }


    /**
     * Case: NN.
     */
    void testNN() {
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();

        // Use callSerially to make sure both setCurrent() calls
        // occur before the first screen-change event is processed.

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(cv1);
                    dpy.setCurrent(cv2);
                }
            });

        // Use await(Runnable) to ensure that the assertion checks
        // are done after the first screen-change event and before
        // the second screen-change event.

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("cv1", cv1, true);
                    checkCurrent("cv2", cv2, false);
                }
            });

        scl.await();
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);
    }


    /**
     * Case: NNsN.
     */
    void testNNsN() {
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(cv1);
                    dpy.setCurrent(cv1);
                    dpy.setCurrent(cv2);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("cv1", cv1, true);
                    checkCurrent("cv2", cv2, false);
                }
            });

        scl.await();
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);
    }


    /**
     * Case: N_N.
     */
    void testN_N() {
        LcduiTestCanvas cv1 = new LcduiTestCanvas();
        LcduiTestCanvas cv2 = new LcduiTestCanvas();

        setCurrentWait(cv1);
        checkCurrent("cv1", cv1, true);

        setCurrentWait(cv2);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);
    }


    /**
     * Case: N_NNs.
     */
    void testN_NNs() {
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();
        
        setCurrentWait(cv1);
        checkCurrent("cv1", cv1, true);

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(cv2);
                    dpy.setCurrent(cv1);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("cv1", cv1, false);
                    checkCurrent("cv2", cv2, true);
                }
            });

        scl.await();
        checkCurrent("cv1", cv1, true);
        checkCurrent("cv2", cv2, false);
    }

    /**
     * Case: NA.
     */
    void testNA() {
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final Alert alert = makeAlert("alert");

        // establish initial conditions

        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(cv1);
                    dpy.setCurrent(alert);
                }
            });

        // wait for cv1 to become current

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert", alert, false);
                    checkCurrent("cv0", cv0, false);
                    checkCurrent("cv1", cv1, true);
                }
            });

        // wait for alert to become current

        scl.await();
        checkCurrent("alert", alert, true);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, false);

        dismissWait(alert);

        // This checks for the behavior reported as a CR in
        // CR 6225060: after the alert is dismissed, current
        // returns to cv0.
        // checkCurrent("alert", alert, false);
        // checkCurrent("cv0", cv0, true);
        // checkCurrent("cv1", cv1, false);

        // The following checks for behavior expected by most programs: after 
        // the alert is dismissed, cv1 should become current, 
        // because a setCurrent request on it was issued prior to the 
        // setCurrent request on the alert.

        checkCurrent("alert", alert, false);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, true);
    }


    /**
     * Case: N2.
     */
    void testN2() {
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();
        final Alert alert = makeAlert("alert");

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(cv1);
                    dpy.setCurrent(alert, cv2);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert", alert, false);
                    checkCurrent("cv1", cv1, true);
                    checkCurrent("cv2", cv2, false);
                }
            });

        scl.await();
        checkCurrent("alert", alert, true);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, false);

        dismissWait(alert);
        checkCurrent("alert", alert, false);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);
    }


    /**
     * Case: AN.
     */
    void testAN() {
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final Alert alert = makeAlert("alert");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(alert);
                    dpy.setCurrent(cv1);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert", alert, true);
                    checkCurrent("cv0", cv0, false);
                    checkCurrent("cv1", cv1, false);
                }
            });

        dismissWait(alert);
        checkCurrent("alert", alert, false);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, true);
    }


    /**
     * Case: AND.
     * Tests alert timeout while it's shown, while another screen change event 
     * is in the queue.
     */
    void testAND() {
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final Alert alert = makeAlert("alert");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(alert);
                    dpy.setCurrent(cv1);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert", alert, true);
                    checkCurrent("cv0", cv0, false);
                    checkCurrent("cv1", cv1, false);
                    dismiss(alert);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert", alert, false);
                    checkCurrent("cv0", cv0, false);
                    checkCurrent("cv1", cv1, true);
                }
            });

        // The setCurrent(cv1) call supersedes the
        // alert, so dismissing the alert should not
        // return to cv0.

        new SerialCallback(dpy).invokeAndWait();
        checkCurrent("alert", alert, false);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, true);
    }

    /**
     * Case: A_N.
     */
    void testA_N() {
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final Alert alert = makeAlert("alert");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        dpy.setCurrent(alert);
        scl.await();
        checkCurrent("alert", alert, true);
        checkCurrent("cv0", cv0, false);

        setCurrentWait(cv1);
        checkCurrent("alert", alert, false);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, true);

        dismiss(alert);
        new SerialCallback(dpy).invokeAndWait();
        checkCurrent("alert", alert, false);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, true);
    }


    /**
     * Case: AA.
     */
    void testAA() {
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final Alert alert1 = makeAlert("alert1");
        final Alert alert2 = makeAlert("alert2");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        thrown = false;
        new SerialCallback(dpy) {
            public void run() {
                dpy.setCurrent(alert1);
                try {
                    dpy.setCurrent(alert2);
                } catch (IllegalArgumentException iae) {
                    thrown = true;
                }
            }
        }.invokeAndWait();

        scl.await();
        checkCurrent("alert1", alert1, true);
        assertTrue("IAE should be thrown", thrown);

        dismissWait(alert1);
        checkCurrent("cv0", cv0, true);
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, false);
    }


    /**
     * Case: AAs.
     */
    void testAAs() {
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final Alert alert1 = makeAlert("alert1");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        thrown = false;
        new SerialCallback(dpy) {
            public void run() {
                dpy.setCurrent(alert1);
                try {
                    dpy.setCurrent(alert1);
                } catch (IllegalArgumentException iae) {
                    thrown = true;
                }
            }
        }.invokeAndWait();

        scl.await();
        checkCurrent("alert1", alert1, true);
        assertFalse("IAE should not be thrown", thrown);

        dismissWait(alert1);
        checkCurrent("cv0", cv0, true);
        checkCurrent("alert1", alert1, false);
    }


    /**
     * Tests setCurrent(Alert) when an alert is already visible.
     * Case: A_A.
     */
    void testA_A() {
        LcduiTestCanvas cv0 = new LcduiTestCanvas();
        Alert alert1 = makeAlert("alert1");
        Alert alert2 = makeAlert("alert2");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        setCurrentWait(alert1);
        checkCurrent("cv0", cv0, false);
        checkCurrent("alert1", alert1, true);

        thrown = false;
        try {
            dpy.setCurrent(alert2);
        } catch (IllegalArgumentException iae) {
            thrown = true;
        }
        assertTrue("IAE should be thrown", thrown);

        dismissWait(alert1);
        checkCurrent("cv0", cv0, true);
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, false);
    }


    /**
     * Tests setCurrent on the same alert when it is already visible.
     * Case: A_As.
     */
    void testA_As() {
        LcduiTestCanvas cv0 = new LcduiTestCanvas();
        Alert alert1 = makeAlert("alert1");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        setCurrentWait(alert1);
        checkCurrent("cv0", cv0, false);
        checkCurrent("alert1", alert1, true);

        thrown = false;
        try {
            dpy.setCurrent(alert1);
        } catch (IllegalArgumentException iae) {
            thrown = true;
        }
        assertFalse("IAE should not be thrown", thrown);

        dismissWait(alert1);
        checkCurrent("cv0", cv0, true);
        checkCurrent("alert1", alert1, false);
    }


    /**
     * Case: A2.
     */
    void testA2() {
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final Alert alert1 = makeAlert("alert1");
        final Alert alert2 = makeAlert("alert2");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(alert1);
                    dpy.setCurrent(alert2, cv1);
                }
            });
                    
        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert1", alert1, true);
                    checkCurrent("alert2", alert2, false);
                    checkCurrent("cv0", cv0, false);
                    checkCurrent("cv1", cv1, false);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert1", alert1, false);
                    checkCurrent("alert2", alert2, true);
                    checkCurrent("cv0", cv0, false);
                    checkCurrent("cv1", cv1, false);
                    dismiss(alert1);
                }
            });

        // Dismissing alert1 should do nothing, since it has
        // been superseded by alert2.

        new SerialCallback(dpy).invokeAndWait();
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, true);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, false);

        dismissWait(alert2);
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, false);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, true);
    }


    /**
     * Case: A2s.
     */
    void testA2s() {
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final Alert alert1 = makeAlert("alert1");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(alert1);
                    dpy.setCurrent(alert1, cv1);
                }
            });
                    
        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert1", alert1, true);
                    checkCurrent("cv0", cv0, false);
                    checkCurrent("cv1", cv1, false);
                }
            });

        // Ensure that the second setCurrent had no effect.

        new SerialCallback(dpy).invokeAndWait();
        checkCurrent("alert1", alert1, true);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, false);

        dismissWait(alert1);
        checkCurrent("alert1", alert1, false);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, true);
    }


    /**
     * Case: A_2.
     */
    void testA_2() {
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final Alert alert1 = makeAlert("alert1");
        final Alert alert2 = makeAlert("alert2");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        dpy.setCurrent(alert1);
        scl.await();
        checkCurrent("alert1", alert1, true);
        checkCurrent("cv0", cv0, false);

        dpy.setCurrent(alert2, cv1);
        scl.await();
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, true);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, false);

        // Dismiss alert1. Nothing should happen, since it has
        // been superseded by alert2.

        dismiss(alert1);
        new SerialCallback(dpy).invokeAndWait();
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, true);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, false);

        dismissWait(alert2);
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, false);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, true);
    }


    /**
     * Case: A_2s.
     */
    void testA_2s() {
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final Alert alert1 = makeAlert("alert1");

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        setCurrentWait(alert1);
        checkCurrent("alert1", alert1, true);
        checkCurrent("cv0", cv0, false);

        // The two-arg call should update the next displayable
        // but should otherwise do nothing.
        dpy.setCurrent(alert1, cv1);
        new SerialCallback(dpy).invokeAndWait();
        checkCurrent("alert1", alert1, true);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, false);

        dismissWait(alert1);
        checkCurrent("alert1", alert1, false);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, true);
    }


    /**
     * Case: 2N.
     */
    void test2N() {
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();
        final Alert alert = makeAlert("alert");

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(alert, cv1);
                    dpy.setCurrent(cv2);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert", alert, true);
                    checkCurrent("cv1", cv1, false);
                    checkCurrent("cv2", cv2, false);
                }
            });
        
        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert", alert, false);
                    checkCurrent("cv1", cv1, false);
                    checkCurrent("cv2", cv2, true);
                }
            });

        // Dismiss alert. Nothing should happen, since it has
        // been superseded by cv2.

        dismiss(alert);
        new SerialCallback(dpy).invokeAndWait();
        checkCurrent("alert", alert, false);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);
        assertFalse("cv1 should not have been shown", cv1.wasShown());
    }


    /**
     * Case: 2_N.
     */
    void test2_N() {
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();
        final Alert alert = makeAlert("alert");

        dpy.setCurrent(alert, cv1);
        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert", alert, true);
                    checkCurrent("cv1", cv1, false);
                }
            });
        
        setCurrentWait(cv2);
        checkCurrent("alert", alert, false);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);

        // Dismiss alert. Nothing should happen, since it has
        // been superseded by cv2.
        dismiss(alert);
        new SerialCallback(dpy).invokeAndWait();
        checkCurrent("alert", alert, false);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);
        assertFalse("cv1 should not have been shown", cv1.wasShown());
    }


    /**
     * Case: 2A.
     */
    void test2A() {
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();
        final Alert alert1 = makeAlert("alert1");
        final Alert alert2 = makeAlert("alert2");

        thrown = false;
        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(alert1, cv1);
                    try {
                        dpy.setCurrent(alert2);
                    } catch (IllegalArgumentException IAE) {
                        thrown = true;
                    }
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert1", alert1, true);
                    checkCurrent("alert2", alert2, false);
                    checkCurrent("cv1", cv1, false);
                    assertTrue("IAE should be thrown", thrown);
                }
            });

        dismissWait(alert1);
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, false);
        checkCurrent("cv1", cv1, true);
    }

    /**
     * Case: 2_A.
     */
    void test2_A() {
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();
        final Alert alert1 = makeAlert("alert1");
        final Alert alert2 = makeAlert("alert2");

        dpy.setCurrent(alert1, cv1);

        scl.await();
        checkCurrent("alert1", alert1, true);
        checkCurrent("cv1", cv1, false);

        thrown = false;
        try {
            dpy.setCurrent(alert2);
        } catch (IllegalArgumentException IAE) {
            thrown = true;
        }
        assertTrue("IAE should be thrown", thrown);

        dismissWait(alert1);
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, false);
        checkCurrent("cv1", cv1, true);
    }


    /**
     * Case: 22.
     */
    void test22() {
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();
        final Alert alert1 = makeAlert("alert1");
        final Alert alert2 = makeAlert("alert2");

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(alert1, cv1);
                    dpy.setCurrent(alert2, cv2);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert1", alert1, true);
                    checkCurrent("alert2", alert2, false);
                    checkCurrent("cv1", cv1, false);
                    checkCurrent("cv2", cv2, false);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert1", alert1, false);
                    checkCurrent("alert2", alert2, true);
                    checkCurrent("cv1", cv1, false);
                    checkCurrent("cv2", cv2, false);
                }
            });

        // Dismiss alert1. Nothing should happen, since it has
        // been superseded by cv2.
        dismiss(alert1);
        new SerialCallback(dpy).invokeAndWait();
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, true);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, false);

        dismissWait(alert2);
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, false);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);
        assertFalse("cv1 should never have been shown", cv1.wasShown());
    }


    /**
     * Case: 2_2.
     */
    void test2_2() {
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();
        final Alert alert1 = makeAlert("alert1");
        final Alert alert2 = makeAlert("alert2");

        dpy.setCurrent(alert1, cv1);
        scl.await();
        checkCurrent("alert1", alert1, true);
        checkCurrent("cv1", cv1, false);

        dpy.setCurrent(alert2, cv2);
        scl.await();
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, true);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, false);

        // Dismiss alert1. Nothing should happen, since it has
        // been superseded by cv2.

        dismiss(alert1);
        new SerialCallback(dpy).invokeAndWait();
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, true);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, false);

        dismissWait(alert2);
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, false);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);
        assertFalse("cv1 should never have been shown", cv1.wasShown());
    }


    /**
     * Case 22s. Tests the behavior of two, two-arg setCurrent calls, where 
     * the alert in question is the same in both cases.
     */
    void test22s() {
        final Alert alert = makeAlert("alert");
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(alert, cv1);
                    dpy.setCurrent(alert, cv2);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert", alert, true);
                    checkCurrent("cv1", cv1, false);
                    checkCurrent("cv2", cv2, false);
                }
            });

        // Dismiss alert, then cv2 should become current.

        dismissWait(alert);
        checkCurrent("alert", alert, false);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);
        assertFalse("cv1 should never have been shown", cv1.wasShown());
    }


    /**
     * Case 2_2s. Tests the behavior of two, two-arg setCurrent calls, where 
     * the alert in question is the same in both cases.
     */
    void test2_2s() {
        final Alert alert = makeAlert("alert");
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();
        final LcduiTestCanvas cv2 = new LcduiTestCanvas();

        dpy.setCurrent(alert, cv1);
        scl.await();
        checkCurrent("alert", alert, true);
        checkCurrent("cv1", cv1, false);

        // Call setCurrent again and dismiss the alert before
        // the event can be processed.

        dpy.callSerially(
            new Runnable() {
                public void run() {
                    dpy.setCurrent(alert, cv2);
                    dismiss(alert);
                }
            });

        scl.await();
        checkCurrent("alert", alert, false);
        checkCurrent("cv1", cv1, false);
        checkCurrent("cv2", cv2, true);
        assertFalse("cv1 should never have been shown", cv1.wasShown());
    }


    /**
     * Tests setCurrent(alert, alert). This is basically an API test.
     */
    void testAlertAlert() {
        Alert alert1 = makeAlert("alert1");
        Alert alert2 = makeAlert("alert2");

        thrown = false;
        try {
            dpy.setCurrent(alert1, alert2);
        } catch (IllegalArgumentException iae) {
            thrown = true;
        }

        assertTrue("IAE should be thrown", thrown);
    }


    /**
     * Case: A_NA.
     */
    void testA_NA() {
        final Alert alert1 = makeAlert("alert1");
        final Alert alert2 = makeAlert("alert2");
        final LcduiTestCanvas cv0 = new LcduiTestCanvas();
        final LcduiTestCanvas cv1 = new LcduiTestCanvas();

        // establish initial conditions
        setCurrentWait(cv0);
        checkCurrent("cv0", cv0, true);

        dpy.setCurrent(alert1);
        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert1", alert1, true);
                    checkCurrent("cv0", cv0, false);
                    dpy.setCurrent(cv1);
                    dpy.setCurrent(alert2);
                }
            });

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert1", alert1, false);
                    checkCurrent("alert2", alert2, false);
                    checkCurrent("cv0", cv0, false);
                    checkCurrent("cv1", cv1, true);
                    dismiss(alert1);
                }
            });
        
        // Dismissing alert1 should be ignored since it has been
        // superseded by cv1; then alert2 should become current.

        scl.await(
            new Runnable() {
                public void run() {
                    checkCurrent("alert1", alert1, false);
                    checkCurrent("alert2", alert2, true);
                    checkCurrent("cv0", cv0, false);
                    checkCurrent("cv1", cv1, false);
                    dismiss(alert2);
                }
            });
        
        scl.await();
        checkCurrent("alert1", alert1, false);
        checkCurrent("alert2", alert2, false);
        checkCurrent("cv0", cv0, false);
        checkCurrent("cv1", cv1, true);
    }


    /**
     * Runs all tests.
     */
    public void runTests() throws Throwable {
        init();
        try {
            declare("testInit");
            testInit();

            declare("test0A0A");
            test0A0A();

            declare("testN");
            testN();

            declare("testA");
            testA();

            declare("test2");
            test2();

            declare("testNN");
            testNN();

            declare("testNNsN");
            testNNsN();

            declare("testN_N");
            testN_N();

            declare("testN_NNs");
            testN_NNs();

            declare("testNA");
            testNA();

            declare("testN2");
            testN2();

            declare("testAN");
            testAN();

            declare ("testAND");
            testAND();

            declare("testA_N");
            testA_N();

            declare("testAA");
            testAA();

            declare("testAAs");
            testAAs();

            declare("testA_A");
            testA_A();

            declare("testA_As");
            testA_As();

            declare("testA2");
            testA2();

            declare("testA2s");
            testA2s();

            declare("testA_2");
            testA_2();

            declare("testA_2s");
            testA_2s();

            declare("test2N");
            test2N();

            declare("test2_N");
            test2_N();

            declare("test2A");
            test2A();

            declare("test2_A");
            test2_A();

            declare("test22");
            test22();

            declare("test2_2");
            test2_2();

            declare("test22s");
            test22s();

            declare("test2_2s");
            test2_2s();

            declare("testAlertAlert");
            testAlertAlert();

            declare("testA_NA");
            testA_NA();
        } finally {
            fini();
        }
    }
}
