/*
 *
 *
 * 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 com.sun.midp.main;

import java.util.*;

import com.sun.cldc.isolate.Isolate;



/**
 * Implements the mechanism to monitor MIDlet suites isolate.
 * The StartMIDletMonitor provides the isolate references at it gets
 * a MIDlet create notification from the MIDletProxyList. When an isolate
 * terminates it notifies the native application manager. The notification is
 * needed for native auto testers because the notification that the test MIDlet
 * is destoryed does not indicate that the VM has closed it JAR.
 */
class IsolateMonitor implements MIDletProxyListListener {

    /** Reference to the ProxyList. */
    private static MIDletProxyList midletProxyList;

    /** Reference to the Isolate Monitor. */
    private static IsolateMonitor monitor;

    /** Table of isolates indexed by midlet proxies. */
    private Hashtable isolates;

    /** What objects should get list changes. */
    private Vector listeners;

    /**
     * Initializes StartMIDletMonitor class.
     * Shall only be called from AmsUtil.
     * No need in security checks since it is package private method.
     *
     * @param theMIDletProxyList MIDletController's container
     */
    static void initClass(MIDletProxyList theMIDletProxyList) {

        midletProxyList = theMIDletProxyList;

        monitor = new IsolateMonitor();
    }

    /**
     * Construct a new StartMIDletMonitor instance to track the
     * process of starting a MIDlet in a new Isolate.
     * The new instance is appended to the startPending vector
     * and is registered with the MIDletProxyList to receive
     * notifications if/when the MIDlet starts/fails to start.
     */
    private IsolateMonitor() {
        isolates = new Hashtable();
        listeners = new Vector();
        midletProxyList.addListener(this);
    }

    /**
     * Adds the Isolate associated with a started MIDlet.
     *
     * @param proxy proxy of a started MIDlet
     * @param isolate the Isolate of the started MIDlet, for systems that
     *                run multiple Isolates from a suite concurrently this
     *                may be a duplicate isolate
     */
    static void addIsolate(MIDletProxy proxy, Isolate isolate) {
        synchronized (monitor.isolates) {
            monitor.isolates.put(proxy, isolate);
        }
    }

    /**
     * Add a listener for MIDlet suite terminations.
     *
     * @param listener Isolate monitor listener
     */
    public static void addListener(IsolateMonitorListener listener) {
        monitor.listeners.addElement(listener);
    }

    /**
     * Called when a MIDlet is added to the list.
     *
     * @param midlet The proxy of the MIDlet being added
     */
    public void midletAdded(MIDletProxy midlet) {
    }

    /**
     * Called when the state of a MIDlet in the list is updated.
     *
     * @param midlet The proxy of the MIDlet that was updated
     * @param fieldId code for which field of the proxy was updated
     */
    public void midletUpdated(MIDletProxy midlet, int fieldId) {
    }

    /**
     * Called when a MIDlet is removed from the list.
     * If this is the last MIDlet in its isolate then start a termination
     * notification thread to wait on its isolate.
     *
     * @param proxy The proxy of the removed MIDlet
     */
    public void midletRemoved(MIDletProxy proxy) {
        synchronized (isolates) {
            if (lastInIsolate(proxy)) {
                startNotificationThread(proxy);
            }

            isolates.remove(proxy);
        }
    }

    /**
     * Called when error occurred while starting a MIDlet object.
     *
     * @param externalAppId ID assigned by the external application manager
     * @param suiteId Suite ID of the MIDlet
     * @param className Class name of the MIDlet
     * @param errorCode start error code
     * @param errorDetails start error details
     */
    public void midletStartError(int externalAppId, int suiteId,
                                 String className, int errorCode,
                                 String errorDetails) {}

    /**
     * Determine if a MIDlet is the last MIDlet in an isolate.
     *
     * @param proxy proxy of MIDlet
     *
     * @return true if the MIDlet is the last MIDlet in the isolate
     */
    private boolean lastInIsolate(MIDletProxy proxy) {
        Isolate isolate = (Isolate)isolates.get(proxy);
        int midletCount = 0;

        if (isolate != null) {
            Enumeration enum = isolates.elements();

            while (enum.hasMoreElements()) {
                Isolate current = (Isolate)enum.nextElement();

                if (current == isolate) {
                    midletCount++;
                }
            }
        }

        return midletCount == 1;
    }

    /**
     * Start a thread that will wait the MIDlet's isolate to terminate and
     * then notify the native application manager. This method will also
     * remove the MIDlet from the monitor's MIDlet list.
     *
     * @param proxy proxy of MIDlet
     */
    private void startNotificationThread(MIDletProxy proxy) {
        Isolate isolate = (Isolate)isolates.get(proxy);
        TerminationNotifier notifier = new TerminationNotifier();

        notifier.midlet = proxy;
        notifier.isolate = isolate;
        notifier.parent = this;

        new Thread(notifier).start();
    }

    /**
     * Notifies the listeners that a suite has terminated.
     *
     * @param suiteId ID of the MIDlet suite
     */
    void notifyListeners(int suiteId) {
        synchronized (listeners) {
            for (int i = 0; i < listeners.size(); i++) {
                IsolateMonitorListener listener =
                    (IsolateMonitorListener)listeners.elementAt(i);

                listener.suiteTerminated(suiteId);
            }
        }
    }
}

/**
 * Waits for an isolate to terminate and then notifies the native app
 * manager.
 */
class TerminationNotifier implements Runnable {
    /** MIDlet information. */
    MIDletProxy midlet;

    /** Isolate of the MIDlet. */
    Isolate isolate;

    /** Parent monitor. */
    IsolateMonitor parent;

    /** Performs this classes function. */
    public void run() {
        isolate.waitForExit();
        parent.notifyListeners(midlet.getSuiteId());
    }
}
