SWTMIDlet.java

package com.nokia.example;

/*
 * Copyright © 2011 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners.
 * See LICENSE.TXT for license information.
 */

import javax.microedition.midlet.MIDlet;

import org.eclipse.swt.widgets.Display;

/**
 * <p>
 * This class provides a basis for implementing MIDlets that use eSWT UI.
 * You can write your own MIDlet easily by deriving from this class and
 * overriding either runMain() or runUI().
 * </p><p>
 * A MIDlet that overrides runMain() needs to create the Display and implement
 * the event loop from scratch similarly to a main() application.
 * </p><p>
 * If you don't want to implement the event loop yourself you can choose to
 * override runUI(). When runUI() is called the Display has already been
 * created and the event loop will be automatically started once runUI() is
 * allowed to return. The implementation of runUI() only needs to initialize
 * the desired UI components.
 * </p><p>
 * An example overriding runMain():
 * </p><code><pre>
 * public class MyMIDlet extends SWTMIDlet {
 *     public void runMain() {
 *         Display display = new Display();
 *         Shell shell = new Shell(display);
 *         shell.setLayout(new FillLayout());
 *         Command exitCommand = new Command(shell, Command.EXIT, 0);
 *         exitCommand.setText("Exit");
 *         exitCommand.addSelectionListener(
 *             new SelectionListener() {
 *                 public void widgetDefaultSelected(SelectionEvent e) {
 *             }
 *             public void widgetSelected(SelectionEvent e) {
 *                 exit();
 *             }
 *         });
 *         Label label = new Label(shell, SWT.HORIZONTAL);
 *         label.setText("Label here");
 *         shell.open();
 *         while(!shell.isDisposed()) {
 *             if(!display.readAndDispatch())
 *                display.sleep();
 *           }
 *         display.dispose();
 *     }
 * }
 * </pre></code><p>
 * An example overriding runUI():
 * </p><code><pre>
 * public class MyMIDlet extends SWTMIDlet {
 *     public void runUI(Display display) {
 *         Shell shell = new Shell(display);
 *         Command exitCommand = new Command(shell, Command.EXIT, 0);
 *         exitCommand.setText("Exit");
 *         exitCommand.addSelectionListener(
 *             new SelectionListener() {
 *                 public void widgetDefaultSelected(SelectionEvent e) {
 *             }
 *             public void widgetSelected(SelectionEvent e) {
 *                 exit();
 *             }
 *         });
 *         shell.setLayout(new FillLayout());
 *         Label label = new Label(shell, SWT.HORIZONTAL);
 *         label.setText("Label here");
 *         shell.open();
 *     }
 * }
 * </pre></code>
 * @see #runMain()
 * @see #runUI(Display)
 */
public abstract class SWTMIDlet extends MIDlet {

    private Thread uiThread;
    private boolean started;
    private boolean exiting;

    /**
     * <p>
     * The MIDlet should override either this method or runUI().
     * </p>
     * <p>
     * runMain() is called in the UI thread. A MIDlet that overrides it needs to
     * create the Display and implement the event loop similarly to a main()
     * application.
     * </p>
     * <p>
     * The default implementation creates the Display and then calls runUI() where
     * the application is expected to initialize the UI components. Finally it
     * enters the event loop.
     * </p>
     *
     * @see#runUI
     */
    protected void runMain() {
        // Create the Display.
        final Display display = new Display();

        // Call runUI to construct the UI.
        runUI(display);

        // Enter the event loop.
        while (!display.isDisposed()) {
            if (!display.readAndDispatch()) {
                if (!display.isDisposed()) {
                    display.sleep();
                }
            }
        }
    }

    /**
     * <p>
     * The MIDlet should override either this method or runMain().
     * </p>
     * <p>
     * The Display has been created and the event loop is running in the calling
     * thread. The application should override this to create and initialize the
     * desired UI components and then let it return to allow the event loop to
     * dispatch events.
     * </p>
     * <p>
     * The default implementation does nothing.
     * </p>
     *
     * @see #runMain
     */
    protected void runUI(Display display) {
    }

    /**
     * <p>
     * Exits the MIDlet in a controlled way. Can be called from any thread.
     * </p>
     * <p>
     * This implementation will make the event loop exit by disposing the Display.
     * Then it's waited until the UI thread has finished.
     * </p>
     * <p>
     * Disposing the Display will dispose all the Widgets because they are in a
     * parent-child hierarchy with the Display as the root object. Resources such as
     * Images, Colors, GCs, and Fonts are not disposed by disposing the Display.
     * They should be managed elsewhere.
     * </p>
     */
    public void exit() {
        synchronized (SWTMIDlet.class) {
            exiting = true;
            // Dispose the Display if it exists to make all event loops exit.
            final Display display;
            if ((display = Display.findDisplay(uiThread)) != null) {
                display.syncExec(new Runnable() {

                    public void run() {
                        display.dispose();
                    }
                });
            }

            // If there's no Display then we don't have means to signal the UI
            // thread to exit. Destroy immediately.
            if (display == null) {
                notifyDestroyed();
            }

            // Wait for the UI thread to finish cleaning up unless it's this thread
            // or it already exited.
            try {
                if (uiThread != null && !uiThread.equals(Thread.currentThread())) {
                    // Release the lock and wait
                    SWTMIDlet.class.wait();
                }
            } catch (InterruptedException e) {
            }
        }
    }

    /**
     * <p>
     * Abstract method from javax.microedition.midlet.MIDlet has to be overridden to
     * implement MIDlet startup. The application management software will call this
     * method to start the MIDlet. When the call is allowed to return without
     * throwing any exceptions the MIDlet has successfully transitioned to
     * <i>Active</i> state.
     * </p>
     * <p>
     * This implementation obtains a platform specific UI thread that is executed
     * concurrently with the calling thread and then calls runMain() in the UI
     * thread. The call to this method returns immediately. Subsequent invocations
     * after the first invocation do nothing.
     * </p>
     *
     * @see MIDlet#startApp
     */
    protected void startApp() {
        synchronized (SWTMIDlet.class) {
            if (!started) {
                started = true;
                startInUIThread(new Runnable() {

                    public void run() {
                        try {
                            synchronized (SWTMIDlet.class) {
                                // If already exiting then quit here.
                                // Note that it's possible that exit() has been
                                // called but it didn't yet acquire the lock.
                                if (exiting) {
                                    return;
                                }
                                uiThread = Thread.currentThread();
                            }

                            runMain();

                        } finally {
                            synchronized (SWTMIDlet.class) {
                                // Notify the thread possibly waiting in destroyApp.
                                SWTMIDlet.class.notifyAll();
                                uiThread = null;

                                // If destroyApp() has not been called we need to
                                // destroy the MIDlet explicitly.
                                notifyDestroyed();
                            }
                        }
                    }
                });
            }
        }
    }

    /**
     * <p>
     * Abstract method from javax.microedition.midlet.MIDlet has to be overridden
     * to implement how to pause the MIDlet.
     * </p><p>
     * This implementation does nothing.
     * </p>
     * @see MIDlet#pauseApp
     */
    protected void pauseApp() {
    }

    /**
     * <p>
     * Abstract method from javax.microedition.midlet.MIDlet has to be overridden to
     * implement how to destroy the MIDlet. This method will be called by the
     * application management software when the system requests the application to
     * destroy itself. This can happen e.g. because the user has chosen to terminate
     * the application or if the system is running low on resources.
     * </p>
     * <p>
     * When the call to this method returns back to the application management
     * software the MIDlet has transitioned to <i>Destroyed</i> state.
     * </p>
     *
     * @see MIDlet#destroyApp
     */
    protected void destroyApp(boolean unconditional) {
        exit();
    }

    /*
     * Obtain the UI thread and call the Runnable's run() method in it.
     * If the platform eSWT implementation doesn't provide the UI thread
     * support API for obtaining the UI thread for a MIDlet then try using
     * a random Java thread as the UI thread.
     */
    private void startInUIThread(Runnable runnable) {
        // Try if the UI thread support API for MIDlets is available.
        Class clazz;
        try {
            clazz = Class.forName("org.eclipse.ercp.swt.midp.UIThreadSupport");
        } catch (Exception e) {
            clazz = null;
        }
        if (clazz != null) {
            // UIThreadSupport is available. However, we can't use it directly
            // because we want this class to compile also without it. Let's use
            // it through a wrapper class.
            UIThreadSupportWrapper.startInUIThread(runnable);
        } else {
            // UIThreadSupport is not available. Try launching the UI in
            // a random Java thread.
            uiThread = new Thread(runnable);
            uiThread.start();
        }
    }
}