/*
 *
 *
 * 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.io.j2me;

// Interfaces
import com.sun.cldc.io.ConnectionBaseInterface;
import javax.microedition.io.StreamConnection;
import javax.wireless.messaging.MessageConnection;

// Classes
import com.sun.midp.midlet.MIDletSuite;
import com.sun.midp.midlet.Scheduler;
import com.sun.midp.security.Permissions;
import com.sun.midp.security.SecurityToken;
import com.sun.midp.security.ImplicitlyTrustedClass;
import com.sun.midp.security.SecurityInitializer;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import javax.microedition.io.Connector;
import javax.microedition.io.Connection;
import javax.wireless.messaging.Message;
import javax.wireless.messaging.MessageListener;

// Exceptions
import java.io.IOException;
import java.io.InterruptedIOException;

/**
 * Base class for SMS/CBS/MMS message connection implementation.
 *
 * <code>Protocol</code> itself is not instantiated. Instead, the application
 * calls <code>Connector.open</code> with an URL string and obtains a
 * {@link javax.wireless.messaging.MessageConnection MessageConnection}
 *  object. It is an instance of <code>MessageConnection</code>
 * that is instantiated. The Generic Connection Framework mechanism
 * in CLDC will return a <code>Protocol</code> object, which is the
 * implementation of <code>MessageConnection</code>. The
 * <code>Protocol</code> object represents a connection to a low-level transport
 * mechanism.
 * <p>
 * Optional packages, such as <code>Protocol</code>, cannot reside in
 * small devices.
 * The Generic Connection Framework allows an application to reach the
 * optional packages and classes indirectly. For example, an application
 * can be written with a string that is used to open a connection. Inside
 * the implementation of <code>Connector</code>, the string is mapped to a
 * particular implementation: <code>Protocol</code>, in this case. This allows
 * the implementation to be optional even though
 * the interface, <code>MessageConnection</code>, is required.
 * <p>
 * Closing the connection frees an instance of <code>MessageConnection</code>.
 * <p>
 * The <code>Protocol</code> class contains methods
 * to open and close the connection to the low-level transport mechanism. The
 * messages passed on the transport mechanism are defined by the
 * {@link MessageObject MessageObject}
 * class.
 * Connections can be made in either client mode or server mode.
 * <ul>
 * <li>Client mode connections are for sending messages only. They are
 * created by passing a string identifying a destination address to the
 * <code>Connector.open()</code> method.</li>
 * <li>Server mode connections are for receiving and sending messages. They
 * are created by passing a string that identifies a port, or equivalent,
 * on the local host to the <code>Connector.open()</code> method.</li>
 * </ul>
 * The class also contains methods to send, receive, and construct
 * <code>Message</code> objects.
 * <p>
 * <p>
 * This class declares that it implements <code>StreamConnection</code>
 * so it can intercept calls to <code>Connector.open*Stream()</code>
 * to throw an <code>IllegalArgumentException</code>.
 * </p>
 *
 */

public abstract class ProtocolBase implements MessageConnection,
                                 ConnectionBaseInterface,
                                 StreamConnection {

    /** Prefic for addressed message connections. */
    protected String ADDRESS_PREFIX;

    /** Handle to the MIDlet suite containing this MIDlet. */
    protected MIDletSuite midletSuite;

    /**
     * Indicates whether the connection is open or closed. If it is closed,
     * subsequent operations should throw an exception.
     */
    protected boolean open = false;

    /** Local handle to connection */
    protected int connHandle = 0;

    /** Connection parameter from the URL. */
    protected String appID = null;

    /** Connector mode. */
    protected int m_mode = 0;

    /** Machine name - the parsed target address from the URL. */
    protected String host = null;

    /**
     * Inner class to request security token from SecurityInitializer.
     * SecurityInitializer should be able to check this inner class name.
     */
    private static class SecurityTrusted
        implements ImplicitlyTrustedClass {};

    /** This class has a different security domain than the MIDlet suite */
    private static SecurityToken classSecurityToken =
        SecurityInitializer.requestToken(new SecurityTrusted());

    /** Message listener for async notifications. */
    volatile MessageListener m_listener = null;

    /** Listener thread. */
    Thread m_listenerThread = null;

    /**
     * Indicates whether a trusted application is allowed to open the
     * message connection. Set to true if the permission check passes.
     * Note: return true to override Security Permissions
     */
    protected boolean openPermission = false;

    /**
     * Indicates whether a trusted application is allowed to read from the
     * message connection. Set to true if the permission check passes.
     * Note: return true to override Security Permissions
     */
    protected boolean readPermission = false;

    /**
     * Indicates whether a trusted application is allowed to write to the
     * message connection. Set to true if the permission check passes.
     * Note: return true to override Security Permissions
     */
    protected boolean writePermission = false;

    /** Creates a message connection protocol handler. */
    public ProtocolBase() {
	midletSuite = Scheduler.getScheduler().getMIDletSuite();
    }
    /**
     * Construct a new message object from the given type.
     *
     * @param type <code>MULTIPART_MESSAGE</code> is the only type permitted.
     *
     * @return A new MMS <code>Message</code> object.
     */
    public abstract Message newMessage(String type);

    /**
     * Constructs a new message object from the given type and address.
     *
     * @param type <code>TEXT_MESSAGE</code> or
     *     <code>BINARY_MESSAGE</code>.
     * @param addr the destination address of the message.
     * @return a new <code>Message</code> object.
     */
    public abstract Message newMessage(String type, String addr);

    /**
     * Receives the bytes that have been sent over the connection, constructs a
     * <code>Message</code> object, and returns it.
     * <p>
     * If there are no <code>Message</code>s waiting on the connection, this
     * method will block until a message is received, or the
     * <code>MessageConnection</code> is closed.
     *
     * @return a <code>Message</code> object.
     * @exception java.io.IOException if an error occurs while receiving a
     *     message.
     * @exception java.io.InterruptedIOException if this
     *     <code>MessageConnection</code> object is closed during this receive
     *     method call.
     * @exception java.lang.SecurityException if the application does not have
     *      permission to receive messages using the given port number.
     */
    public abstract Message receive() throws IOException;

    /**
     * Sends a message over the connection. This method extracts the data
     * payload from the <code>Message</code> object so that it can be sent as a
     * datagram.
     *
     * @param     dmsg a <code>Message</code> object
     * @exception java.io.IOException if the message could not be sent or
     *     because of network failure
     * @exception java.lang.IllegalArgumentException if the message is
     *     incomplete or contains invalid information. This exception is also
     *     thrown if the payload of the message exceeds the maximum length for
     *     the given messaging protocol.
     * @exception java.io.InterruptedIOException if a timeout occurs while
     *     either trying to send the message or if this <code>Connection</code>
     *     object is closed during this <code>send</code> operation.
     * @exception java.lang.NullPointerException if the parameter is
     *     <code>null</code>.
     * @exception java.lang.SecurityException if the application does not have
     *      permission to send the message.
     */
    public abstract void send(Message dmsg) throws IOException;

    /**
     * Ensures that the connection is open.
     * @exception IOException if the connection is closed
     */
    public void ensureOpen() throws IOException {
	if (!open) {
	    throw new IOException("Connection closed");
	}
    }

    /**
     * Generates InterruptedIOException when connection is closed.
     * @param ex input IOException
     * @param name name of operation: sending or receiving
     * @exception IOException if the connection is not closed
     */
    protected void io2InterruptedIOExc(IOException ex, String name) 
        throws IOException, InterruptedIOException {
        try {
            ensureOpen();
        } catch (IOException ioe) {
            throw new InterruptedIOException("Connection closed " +
                                         "during " + name);
        }
        throw ex;
    }

    /**
     * Registers a <code>MessageListener</code> object.
     * <p>
     * The platform will notify this listener object when a message has been
     * received to this <code>MessageConnection</code>.
     * </p>
     * <p>If there are incoming messages in the queue of this
     * <code>MessageConnection</code> that have not been retrieved by the
     * the application prior to calling this method, the newly
     * registered listener object will be notified immediately once
     * for each such incoming message in the queue.
     * </p>
     * <p>There can be at most one listener object registered for
     * a <code>MessageConnection</code> object at any given point in time.
     * Setting a new listener will implicitly de-register the possibly
     * previously set listener.
     * </p>
     * <p>Passing <code>null</code> as the parameter de-registers the currently
     * registered listener, if any.
     * </p>
     * @param listener <code>MessageListener</code> object to be registered.
     *                 If <code>null</code>,
     *                 the possibly currently registered listener will be
     *                 de-registered and will not receive notifications.
     * @exception java.lang.SecurityException if the application does not
     *         have a permission to receive messages using the given port
     *         number
     * @exception java.io.IOException if the connection has been closed,
     *         or if an attempt is made to register a listener on
     *         a client connection
     */
    public void setMessageListener(MessageListener listener)
                                                     throws IOException {

        boolean needStopReceiver = false;
        /*
         * Make sure the connection is still open.
	 */
        ensureOpen();

        /*
         * Check if we have permission to recieve.
	 */
        if (listener != null) {
            checkReceivePermission();

            /*
             * Don't let the application waste time listening on a client
             * connection, which can not be used for receive operations.
	     */
            if (host != null && host.length() > 0) {
                throw new IOException("Cannot listen on client connection");
	    }
        }

        synchronized (this) {
	    if ((m_listener != null) && (listener == null)) {
                needStopReceiver = true;
            }
            m_listener = listener;
            /* Start a new receive thread when need */
	    if ((listener != null) && (m_listenerThread == null)) {
                startReceiverThread();
            }
        }

        /* Kill listener when need */
        if (needStopReceiver) {
            String save_appID = getAppID();
            /* Close thread without deregistering */
            close00(connHandle, 0);
            setAppID(null);
            try {
                m_listenerThread.join();
            } catch (InterruptedException ie) {
            }  /* Ignore interrupted exception */
            m_listenerThread = null;

            setAppID(save_appID);
            /* Unblock the low level */
            unblock00(MIDletSuite.UNUSED_SUITE_ID);
        }
    }

    /**
     * Gets the connection parameter in string mode.
     * @return string that contains a parameter 
     */
    protected abstract String getAppID();

    /**
     * Sets the connection parameter in string mode.
     * @param newValue new value of connection parameter 
     */
    protected abstract void setAppID(String newValue);

    /**
     * Unblock the receive thread.
     *
     * @param msid The MIDlet suite ID.
     *
     * @return  returns handle to the connection.
     */
    protected abstract int unblock00(int msid)
         throws IOException;

    /**
     * Close connection.
     *
     * @param connHandle handle returned by open0
     * @param deRegister Deregistration appID when parameter is 1.
     * @return    0 on success, -1 on failure
     */
    protected abstract int close00(int connHandle, int deRegister);

    /**
     * Checks internal setting of receive permission.
     * Called from receive and setMessageListener methods.
     * @exception InterruptedIOException if permission dialog
     * was preempted
     */
    protected abstract void checkReceivePermission() throws InterruptedIOException;

    /** Waits until message available and notify listeners */
    /**
     * Start receiver thread
     */
    private void startReceiverThread() {
	final MessageConnection messageConnection = this;

	if (m_listenerThread == null) {

	    m_listenerThread = new Thread() {

		/**
		 * Run the steps that wait for a new message.
		 */
		public void run() {

		    int messageLength = 0;
		    do {

			/* No message, initially. */
			messageLength = -1;
			try {
		            messageLength =
                                waitUntilMessageAvailable00(connHandle);
                            if (getAppID() == null) {
                                break;
                            }
				/*
				 * If a message is available and there are
				 * listeners, notify all listeners of the
				 * incoming message.
				 */
				if (messageLength >= 0) {
                                    synchronized (this) {
                                        if (m_listener != null) {

					    // Invoke registered listener.
					    m_listener.notifyIncomingMessage(
                                                messageConnection);
                                        }
                                    }
                                }
			} catch (InterruptedIOException iioe) {
                            /*
                             * Terminate, the reader thread has been
                             * interrupted
                             */
                            break;
                        } catch (IOException exception) {
                            break;
                        } catch (IllegalArgumentException iae) {
                            /*
                             * This happens if port has been set to 0;
                             * which indicates that the connection has been
                             * closed. So Terminate.
                             */
                            break;
			}

                    /*
                     * m_iport = 0 on close
                     */
		    } while (getAppID() != null);
		}
            };

            m_listenerThread.start();
	}
    }

    /**
     * Waits until message available
     *
     * @param handle handle to connection
     * @return 0 on success, -1 on failure
     * @exception IOException  if an I/O error occurs
     */
    protected abstract int waitUntilMessageAvailable00(int handle)
                               throws IOException;
}
